import { useCallback, useEffect, useRef, useState } from "react";
import { DownloadSimple, FolderOpen, Sparkle, Stack } from "@phosphor-icons/react";
import type { ImageStudioApp } from "@app/app";
import { commandIds } from "@commands/ids";
import { BottomControlsIsland } from "./BottomControlsIsland";
import { brushUnavailableHint } from "./canvas/brush";
import { CanvasViewport } from "./CanvasViewport";
import { CommandPalette } from "./CommandPalette";
import { GenerateSheet } from "./GenerateSheet";
import { LayersSheet } from "./LayersSheet";
import { ShortcutsDisplay } from "./ShortcutsDisplay";
import { ToolOverlay } from "./ToolOverlay";
import { resolveTransformTargetBounds, selectedTransformTarget } from "@editor/transform-targets";
import type { AppState } from "@editor/state";
import type { ToolId } from "@editor/tools";
import { handleCommandPaletteKey, handleDeleteSelectionKey, handleHistoryKey, handleToolKey, keybindEventFromKeyboardEvent } from "@input/index";
import { shallowEqual, useAppState } from "./useAppState";
import { downloadArtboardPng } from "./exportArtboardPng";
import { useImageImport } from "./useImageImport";
import { useViewportActivityIsland } from "./useViewportActivityIsland";
import "./index.css";

export type AppProps = {
  app: ImageStudioApp;
};

export function App({ app }: AppProps) {
  const shellState = useAppState(app.store, selectAppShellState, shallowEqual);
  const { document, selection, viewport, tools, generation, commandPalette, transformSession, maskEdit } = shellState;
  const viewportActivityIsland = useViewportActivityIsland(viewport);
  const imageImport = useImageImport(app.store);
  const [layersOpen, setLayersOpen] = useState(false);
  const previousGenerateTool = useRef<ToolId>("select");
  const transformTarget = transformSession?.target ?? selectedTransformTarget(document, selection);
  const activeArtboard = document.artboards.find((artboard) => artboard.id === selection.artboardId) ?? document.artboards[0];
  const generateOpen = tools.activeTool === "generate";

  useEffect(() => {
    if (tools.activeTool !== "generate") {
      previousGenerateTool.current = tools.activeTool;
      return;
    }

    setLayersOpen(false);
  }, [tools.activeTool]);

  const getGenerateReturnTool = useCallback((): ToolId => {
    return previousGenerateTool.current === "generate" ? "select" : previousGenerateTool.current;
  }, []);

  const openGenerate = useCallback(() => {
    const activeTool = app.store.getState().editor.tools.activeTool;
    if (activeTool !== "generate") previousGenerateTool.current = activeTool;
    app.store.dispatch(commandIds.toolSetActive, { tool: "generate" });
    setLayersOpen(false);
  }, [app.store]);

  const closeGenerate = useCallback(() => {
    if (app.store.getState().editor.tools.activeTool === "generate") {
      app.store.dispatch(commandIds.toolSetActive, { tool: getGenerateReturnTool() });
    }
  }, [app.store, getGenerateReturnTool]);

  const toggleGenerate = useCallback(() => {
    if (generateOpen) closeGenerate();
    else openGenerate();
  }, [closeGenerate, generateOpen, openGenerate]);

  const openLayers = useCallback(() => {
    closeGenerate();
    setLayersOpen(true);
  }, [closeGenerate]);

  const closeLayers = useCallback(() => {
    setLayersOpen(false);
  }, []);

  const toggleLayers = useCallback(() => {
    const nextOpen = !layersOpen;
    if (nextOpen) closeGenerate();
    setLayersOpen(nextOpen);
  }, [closeGenerate, layersOpen]);

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      const keybindEvent = keybindEventFromKeyboardEvent(event);
      const paletteConsumed = handleCommandPaletteKey({ event: keybindEvent, dispatch: app.store.dispatch });
      if (paletteConsumed) {
        event.preventDefault();
        return;
      }

      if (app.store.getState().editor.commandPalette.open) {
        if (event.key === "Escape") {
          app.store.dispatch(commandIds.commandPaletteClose, undefined);
          event.preventDefault();
        }
        return;
      }

      const target = event.target;
      const editableTarget =
        target instanceof HTMLElement &&
        (target.isContentEditable || target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement);

      if (editableTarget) return;

      const historyConsumed = handleHistoryKey({ event: keybindEvent, dispatch: app.store.dispatch });
      if (historyConsumed) {
        event.preventDefault();
        return;
      }

      if (event.altKey || event.ctrlKey || event.metaKey) return;

      if (event.key === "Escape" && app.store.getState().editor.maskEdit) {
        app.store.dispatch(commandIds.toolExitMaskEdit, undefined);
        event.preventDefault();
        return;
      }

      const key = event.key.toLowerCase();

      const toolConsumed = handleToolKey({ event: keybindEvent, dispatch: app.store.dispatch });
      if (toolConsumed) {
        event.preventDefault();
        return;
      }

      if (key === "l") {
        toggleLayers();
        event.preventDefault();
        return;
      }

      const consumed = handleDeleteSelectionKey({
        event: keybindEvent,
        selection: app.store.getState().editor.selection,
        dispatch: app.store.dispatch,
      });
      if (consumed) event.preventDefault();
    };

    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [app.store, toggleLayers]);
  const transformBounds = transformTarget ? resolveTransformTargetBounds(document, transformTarget) : undefined;
  const brushHint = brushUnavailableHint(document, { selection, tools, maskEdit });

  return (
    <main className="relative h-full overflow-hidden bg-[radial-gradient(circle_at_20%_18%,rgba(148,163,184,0.18),transparent_34%),radial-gradient(circle_at_82%_22%,rgba(71,85,105,0.22),transparent_36%),radial-gradient(circle_at_48%_88%,rgba(30,41,59,0.28),transparent_40%),linear-gradient(135deg,#020617_0%,#0f172a_46%,#111827_100%)] text-foreground">
      {imageImport.input}
      <CommandPalette
        state={commandPalette}
        document={document}
        selection={selection}
        viewport={viewport}
        tools={tools}
        generation={generation}
        layersOpen={layersOpen}
        activeArtboard={activeArtboard}
        dispatch={app.store.dispatch}
        openFilePicker={imageImport.openFilePicker}
        openGenerate={openGenerate}
        openLayers={openLayers}
        closeLayers={closeLayers}
      />
      <header className="pointer-events-none absolute inset-x-3 top-3 z-10 flex h-20 items-center justify-end gap-4 rounded-full px-4 text-white backdrop-blur-xl">
        <div className="pointer-events-auto flex items-center gap-2">
          <button type="button" className={topBarButtonClass()} onClick={imageImport.openFilePicker}>
            <FolderOpen size={24} />
          </button>
          <button type="button" className={topBarButtonClass(generateOpen)} aria-pressed={generateOpen} onClick={toggleGenerate}>
            <Sparkle size={24} weight={generateOpen ? "fill" : "regular"} />
          </button>
          <button type="button" className={topBarButtonClass()} disabled={!activeArtboard} onClick={() => activeArtboard && void downloadArtboardPng(activeArtboard, document.assets)}>
            <DownloadSimple size={24} />
          </button>
          <button type="button" className={topBarButtonClass(layersOpen)} aria-pressed={layersOpen} onClick={toggleLayers}>
            <Stack size={24} weight={layersOpen ? "fill" : "regular"} />
          </button>
        </div>
      </header>
      <div className="absolute left-3 top-1/2 z-10 -translate-y-1/2">
        <ToolOverlay
          activeTool={tools.activeTool}
          interactionMode={tools.interactionMode}
          dispatch={app.store.dispatch}
        />
      </div>
      <GenerateSheet
        settings={tools.generate}
        open={generateOpen}
        dispatch={app.store.dispatch}
      />
      <LayersSheet
        document={document}
        selection={selection}
        maskEdit={maskEdit}
        open={layersOpen}
        dispatch={app.store.dispatch}
      />
      <div className="absolute inset-x-0 bottom-4 z-10 flex justify-center">
        <BottomControlsIsland
          document={document}
          selection={selection}
          viewport={viewport}
          visible={tools.activeTool === "generate" || tools.activeTool === "brush" || tools.activeTool === "eraser" || tools.activeTool === "chromaKey" || tools.activeTool === "magicWand" || Boolean(transformBounds) || viewportActivityIsland.visible}
          action={viewportActivityIsland.action}
          activeTool={tools.activeTool}
          brushSettings={tools.brush}
          generateSettings={tools.generate}
          generation={generation}
          chromaKeySettings={tools.chromaKey}
          magicWandSettings={tools.magicWand}
          editingMask={Boolean(maskEdit)}
          maskViewMode={maskEdit?.viewMode ?? "composite"}
          brushHint={brushHint}
          transformBounds={viewportActivityIsland.visible ? undefined : transformBounds}
          transformTarget={viewportActivityIsland.visible ? undefined : transformTarget}
          dispatch={app.store.dispatch}
        />
      </div>
      <div className="absolute bottom-4 left-4 z-10">
        <ShortcutsDisplay />
      </div>
      <CanvasViewport store={app.store} />
    </main>
  );
}

function topBarButtonClass(active = false) {
  const base = "inline-flex size-12 items-center justify-center rounded-full text-sm font-medium transition disabled:pointer-events-none disabled:opacity-35 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30";
  return active ? `${base} bg-white text-black hover:bg-white hover:text-black` : `${base} text-white/75 hover:bg-white/10 hover:text-white`;
}

type AppShellState = {
  document: AppState["document"];
  selection: AppState["editor"]["selection"];
  viewport: AppState["editor"]["viewport"];
  tools: AppState["editor"]["tools"];
  generation: AppState["editor"]["generation"];
  commandPalette: AppState["editor"]["commandPalette"];
  transformSession: AppState["editor"]["transformSession"];
  maskEdit: AppState["editor"]["maskEdit"];
};

function selectAppShellState(state: AppState): AppShellState {
  return {
    document: state.document,
    selection: state.editor.selection,
    viewport: state.editor.viewport,
    tools: state.editor.tools,
    generation: state.editor.generation,
    commandPalette: state.editor.commandPalette,
    transformSession: state.editor.transformSession,
    maskEdit: state.editor.maskEdit,
  };
}

export default App;
