import { useCallback, useEffect, useMemo, useRef, type KeyboardEvent } from "react";
import { Command, MagnifyingGlass } from "@phosphor-icons/react";
import { commandIds } from "@commands/ids";
import type { Artboard } from "@core/artboard";
import type { ImageDocument } from "@core/document";
import type { GenerationState, SelectionState, ViewportState, CommandPaletteState } from "@editor/state";
import type { AppStore } from "@editor/store";
import type { ToolState } from "@editor/tools";
import { createDocumentReadIndex } from "@editor/document-indexes";
import { createPaletteItems, type PaletteItem } from "./paletteItems";
import type { DocumentActions } from "@app/document-actions";

export type CommandPaletteProps = {
  state: CommandPaletteState;
  document: ImageDocument;
  selection: SelectionState;
  viewport: ViewportState;
  tools: ToolState;
  generation: GenerationState;
  layersOpen: boolean;
  activeArtboard?: Artboard;
  dispatch: AppStore["dispatch"];
  openFilePicker: () => void;
  openGenerate: () => void;
  openLayers: () => void;
  closeLayers: () => void;
  documentActions: DocumentActions;
};

export function CommandPalette({
  state,
  document,
  selection,
  viewport,
  tools,
  generation,
  layersOpen,
  activeArtboard,
  dispatch,
  openFilePicker,
  openGenerate,
  openLayers,
  closeLayers,
  documentActions,
}: CommandPaletteProps) {
  const inputRef = useRef<HTMLInputElement | null>(null);
  const documentIndex = useMemo(() => createDocumentReadIndex(document), [document]);
  const activeArtboardId = selection.artboardId ?? document.artboards[0]?.id;
  const selectedLayerId = selection.layerIds[0];
  const selectedLayer = selectedLayerId ? documentIndex.layerInfoById.get(selectedLayerId) : undefined;
  const canGroup = Boolean(selection.artboardId && selection.layerIds.length > 0);
  const canUngroup = selectedLayer?.layer.type === "group";
  const items = useMemo(
    () =>
      createPaletteItems({
        document,
        documentIndex,
        activeArtboard,
        activeArtboardId,
        selectedLayer,
        canGroup,
        canUngroup,
        selection,
        viewport,
        tools,
        generation,
        layersOpen,
        dispatch,
        openFilePicker,
        openGenerate,
        openLayers,
        closeLayers,
        documentActions,
      }),
    [
      activeArtboard,
      activeArtboardId,
      canGroup,
      canUngroup,
      closeLayers,
      dispatch,
      document,
      documentIndex,
      documentActions,
      generation,
      layersOpen,
      openFilePicker,
      openGenerate,
      openLayers,
      selectedLayer,
      selection,
      tools,
      viewport,
    ],
  );
  const filteredItems = useMemo(() => filterItems(items, state.query), [items, state.query]);
  const activeIndex = filteredItems.length > 0 ? Math.min(state.selectedIndex, filteredItems.length - 1) : 0;

  useEffect(() => {
    if (!state.open) return;
    const frame = window.requestAnimationFrame(() => inputRef.current?.focus());
    return () => window.cancelAnimationFrame(frame);
  }, [state.open]);

  const close = useCallback(() => {
    dispatch(commandIds.commandPaletteClose, undefined);
  }, [dispatch]);

  const runItem = useCallback(
    (item: PaletteItem | undefined) => {
      if (!item || item.disabled) return;
      dispatch(commandIds.commandPaletteClose, undefined);
      item.run();
    },
    [dispatch],
  );

  const handleInputKeyDown = useCallback(
    (event: KeyboardEvent<HTMLInputElement>) => {
      event.stopPropagation();

      if (event.key === "Escape") {
        event.preventDefault();
        close();
        return;
      }

      if (event.key === "ArrowDown") {
        event.preventDefault();
        const nextIndex = filteredItems.length > 0 ? (activeIndex + 1) % filteredItems.length : 0;
        dispatch(commandIds.commandPaletteSetSelectedIndex, { selectedIndex: nextIndex });
        return;
      }

      if (event.key === "ArrowUp") {
        event.preventDefault();
        const nextIndex = filteredItems.length > 0 ? (activeIndex - 1 + filteredItems.length) % filteredItems.length : 0;
        dispatch(commandIds.commandPaletteSetSelectedIndex, { selectedIndex: nextIndex });
        return;
      }

      if (event.key === "Enter") {
        event.preventDefault();
        runItem(filteredItems[activeIndex]);
      }
    },
    [activeIndex, close, dispatch, filteredItems, runItem],
  );

  if (!state.open) return null;

  return (
    <div
      className="pointer-events-auto absolute inset-0 z-50 flex items-start justify-center bg-black/35 px-3 pt-[12vh] backdrop-blur-sm"
      role="dialog"
      aria-modal="true"
      aria-label="Command palette"
      onMouseDown={(event) => {
        if (event.target === event.currentTarget) close();
      }}
    >
      <div className="app-surface w-full max-w-xl overflow-hidden rounded-xl text-white shadow-2xl">
        <div className="flex h-12 items-center gap-2.5 border-b border-white/[0.07] px-3">
          <MagnifyingGlass size={18} className="shrink-0 text-white/35" />
          <input
            ref={inputRef}
            value={state.query}
            onChange={(event) => dispatch(commandIds.commandPaletteSetQuery, { query: event.currentTarget.value })}
            onKeyDown={handleInputKeyDown}
            placeholder="Search commands"
            aria-label="Search commands"
            className="h-full min-w-0 flex-1 bg-transparent text-sm text-white outline-none placeholder:text-white/25"
          />
          <span className="hidden items-center gap-1 rounded bg-white/[0.05] px-1.5 py-0.5 text-[0.65rem] font-semibold text-white/40 sm:inline-flex">
            <Command size={14} weight="bold" /> K
          </span>
        </div>
        <div className="subtle-scrollbar max-h-[min(32rem,70vh)] overflow-auto p-2">
          {filteredItems.length > 0 ? (
            filteredItems.map((item, index) => {
              const active = index === activeIndex;
              return (
                <button
                  key={item.id}
                  type="button"
                  className={paletteItemClass(active, Boolean(item.disabled))}
                  disabled={item.disabled}
                  onMouseEnter={() => dispatch(commandIds.commandPaletteSetSelectedIndex, { selectedIndex: index })}
                  onClick={() => runItem(item)}
                >
                  <span className={paletteIconClass(active)}>{item.icon}</span>
                  <span className="min-w-0 flex-1 text-left">
                    <span className="block truncate text-sm font-semibold">{item.title}</span>
                    <span className={paletteSubtitleClass(active)}>{item.subtitle ?? item.section}</span>
                  </span>
                  <span className={paletteSectionClass(active)}>
                    {item.section}
                  </span>
                </button>
              );
            })
          ) : (
            <div className="grid h-28 place-items-center text-sm text-white/45">No commands</div>
          )}
        </div>
      </div>
    </div>
  );
}

function filterItems(items: PaletteItem[], query: string) {
  const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
  if (terms.length === 0) return items;

  return items.filter((item) => {
    const haystack = [item.title, item.subtitle, item.section, ...(item.keywords ?? [])].filter(Boolean).join(" ").toLowerCase();
    return terms.every((term) => haystack.includes(term));
  });
}

function paletteItemClass(active: boolean, disabled: boolean) {
  const base = "flex min-h-11 w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left transition focus:outline-none";
  if (disabled) return `${base} cursor-not-allowed text-white/30 opacity-45`;
  return active ? `${base} bg-white text-black` : `${base} text-white/75 hover:bg-white/[0.08] hover:text-white`;
}

function paletteIconClass(active: boolean) {
  return active
    ? "grid size-8 shrink-0 place-items-center rounded-md bg-black/10 text-black/60"
    : "grid size-8 shrink-0 place-items-center rounded-md bg-white/[0.05] text-white/55";
}

function paletteSubtitleClass(active: boolean) {
  return active ? "block truncate text-xs text-black/55" : "block truncate text-xs text-white/40";
}

function paletteSectionClass(active: boolean) {
  return active
    ? "shrink-0 rounded bg-black/10 px-1.5 py-0.5 text-[0.6rem] font-semibold uppercase tracking-[0.12em] text-black/45"
    : "shrink-0 rounded bg-white/[0.05] px-1.5 py-0.5 text-[0.6rem] font-semibold uppercase tracking-[0.12em] text-white/30";
}
