import { useRef, useState, type DragEvent, type MutableRefObject } from "react";
import { DownloadSimple, Eye, EyeSlash, FolderPlus, Lock, LockOpen, Plus, Stack, Trash, X } from "@phosphor-icons/react";
import { commandIds } from "@commands/ids";
import type { ImageDocument } from "@core/document";
import type { Layer } from "@core/layer";
import type { ArtboardId } from "@core/id";
import type { SelectionState } from "@editor/state";
import type { AppStore } from "@editor/store";
import { findGroup, findLayerInfoInDocument, resolveLayerDrop, type LayerInfo } from "@input/index";
import { downloadArtboardPng } from "./exportArtboardPng";

export type LayersSheetProps = {
  document: ImageDocument;
  selection: SelectionState;
  open: boolean;
  dispatch: AppStore["dispatch"];
  onClose: () => void;
};

export function LayersSheet({ document, selection, open, dispatch, onClose }: LayersSheetProps) {
  const selectedArtboardId = selection.artboardId ?? document.artboards[0]?.id;
  const selectedLayer = findLayerInfoInDocument(document, selection.layerIds[0]);
  const canGroup = Boolean(selection.artboardId && selection.layerIds.length > 0);
  const canUngroup = selectedLayer?.layer.type === "group";
  const draggedLayerId = useRef<string>();
  const [editingTitle, setEditingTitle] = useState<EditingTitle>();

  return (
    <aside
      aria-hidden={!open}
      className={`pointer-events-auto absolute right-4 top-12 z-20 w-96 overflow-hidden rounded-2xl border border-white/10 bg-zinc-950/85 text-sm text-white shadow-2xl shadow-black/40 backdrop-blur-xl transition-all duration-200 ${
        open ? "translate-x-0 opacity-100" : "pointer-events-none translate-x-4 opacity-0"
      }`}
    >
      <header className="flex h-14 items-center justify-between border-b border-white/10 bg-white/[0.03] px-4">
        <div>
          <div className="font-medium tracking-wide">Layers</div>
          <div className="text-xs text-white/40">Press L to open</div>
        </div>
        <button type="button" className={iconButtonClass()} aria-label="Close layers" onClick={onClose}>
          <X size={18} weight="regular" />
        </button>
      </header>
      <div className="flex flex-wrap gap-2 border-b border-white/10 bg-black/20 p-3">
        <button type="button" className={toolbarButtonClass()} onClick={() => addArtboard(document, dispatch)}>
          <Plus size={16} /> Artboard
        </button>
        <button type="button" className={toolbarButtonClass()} disabled={!selectedArtboardId} onClick={() => selectedArtboardId && addLayer(document, selectedArtboardId, selectedLayer, dispatch)}>
          <Plus size={16} /> Layer
        </button>
        <button type="button" className={toolbarButtonClass()} disabled={!selectedArtboardId} onClick={() => selectedArtboardId && addGroup(selectedArtboardId, dispatch)}>
          <FolderPlus size={16} /> Group
        </button>
        <button type="button" className={toolbarButtonClass()} disabled={!canGroup} onClick={() => selection.artboardId && groupSelection(selection.artboardId, selection.layerIds, dispatch)}>
          <Stack size={16} /> Group selected
        </button>
        <button type="button" className={toolbarButtonClass()} disabled={!canUngroup} onClick={() => selectedLayer && dispatch(commandIds.documentUngroupLayer, { groupId: selectedLayer.layer.id })}>
          Ungroup
        </button>
        <button type="button" className={toolbarButtonClass()} disabled={!selectedLayer} onClick={() => selectedLayer && moveLayer(document, selectedLayer, -1, dispatch)}>
          Up
        </button>
        <button type="button" className={toolbarButtonClass()} disabled={!selectedLayer} onClick={() => selectedLayer && moveLayer(document, selectedLayer, 1, dispatch)}>
          Down
        </button>
        <button type="button" className={toolbarButtonClass()} disabled={!selectedLayer && !selection.artboardId} onClick={() => deleteSelection(selection, selectedLayer, dispatch)}>
          <Trash size={16} /> Delete
        </button>
      </div>
      <div className="max-h-[32rem] overflow-auto p-3">
        {document.artboards.map((artboard) => (
          <section key={artboard.id} className="mb-4 last:mb-0">
            <div
              className={`flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-left transition ${selection.artboardId === artboard.id && selection.layerIds.length === 0 ? "bg-sky-400/15 text-sky-100 ring-1 ring-sky-300/20" : "text-white/65 hover:bg-white/[0.06] hover:text-white"}`}
              onDragOver={(event) => {
                if (draggedLayerId.current) event.preventDefault();
              }}
              onDrop={(event) => {
                event.preventDefault();
                if (draggedLayerId.current) dispatch(commandIds.documentMoveLayer, { layerId: draggedLayerId.current, toArtboardId: artboard.id, toIndex: artboard.layers.length });
                draggedLayerId.current = undefined;
              }}
            >
              <button type="button" className="text-white/55 transition hover:text-white" aria-label={artboard.visible ? "Hide artboard" : "Show artboard"} onClick={() => dispatch(commandIds.documentSetArtboardVisible, { id: artboard.id, visible: !artboard.visible })}>
                {artboard.visible ? <Eye size={17} weight="regular" /> : <EyeSlash size={17} weight="regular" />}
              </button>
              <button type="button" className="text-white/55 transition hover:text-white" aria-label={artboard.locked ? "Unlock artboard" : "Lock artboard"} onClick={() => dispatch(commandIds.documentSetArtboardLocked, { id: artboard.id, locked: !artboard.locked })}>
                {artboard.locked ? <Lock size={17} weight="regular" /> : <LockOpen size={17} weight="regular" />}
              </button>
              {editingTitle?.type === "artboard" && editingTitle.id === artboard.id ? (
                <RenameInput
                  value={editingTitle.draft}
                  onChange={(draft) => setEditingTitle({ ...editingTitle, draft })}
                  onCancel={() => setEditingTitle(undefined)}
                  onCommit={() => {
                    dispatch(commandIds.documentRenameArtboard, { id: artboard.id, name: editingTitle.draft });
                    setEditingTitle(undefined);
                  }}
                />
              ) : (
                <button
                  type="button"
                  className="min-w-0 flex-1 truncate text-left font-medium"
                  onClick={() => dispatch(commandIds.selectionSet, { artboardId: artboard.id, layerIds: [] })}
                  onDoubleClick={() => setEditingTitle({ type: "artboard", id: artboard.id, draft: artboard.name })}
                >
                  {artboard.name}
                </button>
              )}
              <button
                type="button"
                className="text-white/55 transition hover:text-white"
                aria-label={`Export ${artboard.name} as PNG`}
                title="Export PNG"
                onClick={(event) => {
                  event.stopPropagation();
                  void downloadArtboardPng(artboard, document.assets);
                }}
              >
                <DownloadSimple size={17} weight="regular" />
              </button>
              <span className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/45">{artboard.layers.length}</span>
            </div>
            <div className="mt-2 space-y-1.5 pl-3">
              {artboard.layers.length === 0 ? (
                <div className="rounded-xl border border-dashed border-white/10 px-3 py-4 text-center text-white/35">No layers yet</div>
              ) : (
                artboard.layers.map((layer) => (
                  <LayerRow
                    key={layer.id}
                    document={document}
                    artboardId={artboard.id}
                    layer={layer}
                    depth={0}
                    selectedLayerIds={selection.layerIds}
                    draggedLayerId={draggedLayerId}
                    editingTitle={editingTitle}
                    setEditingTitle={setEditingTitle}
                    selectedMaskLayer={selectedLayer}
                    dispatch={dispatch}
                  />
                ))
              )}
            </div>
          </section>
        ))}
      </div>
    </aside>
  );
}

function LayerRow({
  document,
  artboardId,
  layer,
  depth,
  selectedLayerIds,
  draggedLayerId,
  editingTitle,
  setEditingTitle,
  selectedMaskLayer,
  dispatch,
}: {
  document: ImageDocument;
  artboardId: ArtboardId;
  layer: Layer;
  depth: number;
  selectedLayerIds: string[];
  draggedLayerId: MutableRefObject<string | undefined>;
  editingTitle: EditingTitle | undefined;
  setEditingTitle: (editingTitle: EditingTitle | undefined) => void;
  selectedMaskLayer?: LayerInfo;
  dispatch: AppStore["dispatch"];
}) {
  const selected = selectedLayerIds.includes(layer.id);
  const layerInfo = findLayerInfoInDocument(document, layer.id);
  const maskLayer = layer.clippingMask ? findLayerInfoInDocument(document, layer.clippingMask.maskLayerId)?.layer : undefined;
  const maskIndent = maskLayer ? 24 : 0;
  const canSetMask =
    Boolean(selectedMaskLayer && layerInfo && selectedMaskLayer.layer.id !== layer.id && selectedMaskLayer.artboardId === layerInfo.artboardId && selectedMaskLayer.parentGroupId === layerInfo.parentGroupId);
  return (
    <div>
      <div
        draggable
        className={`group flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left transition ${selected ? "bg-white text-black shadow-sm" : "text-white/75 hover:bg-white/[0.07] hover:text-white"}`}
        style={{ paddingLeft: 12 + depth * 16 + maskIndent }}
        onDragStart={(event) => {
          event.dataTransfer.effectAllowed = "move";
          event.dataTransfer.setData("text/plain", layer.id);
          draggedLayerId.current = layer.id;
        }}
        onDragEnd={() => {
          draggedLayerId.current = undefined;
        }}
        onDragOver={(event) => {
          if (draggedLayerId.current && draggedLayerId.current !== layer.id) event.preventDefault();
        }}
        onDrop={(event) => {
          event.preventDefault();
          const sourceLayerId = draggedLayerId.current ?? event.dataTransfer.getData("text/plain");
          if (sourceLayerId && sourceLayerId !== layer.id) dropLayer(document, sourceLayerId, { artboardId, layer }, event, dispatch);
          draggedLayerId.current = undefined;
        }}
      >
        {maskLayer ? <span className={selected ? "text-black/40" : "text-sky-200/55"}>↳</span> : null}
        <button type="button" className={selected ? "text-black/55" : "text-white/45 transition hover:text-white"} aria-label={layer.visible ? "Hide layer" : "Show layer"} onClick={() => dispatch(commandIds.documentSetLayerVisible, { layerId: layer.id, visible: !layer.visible })}>
          {layer.visible ? <Eye size={17} weight="regular" /> : <EyeSlash size={17} weight="regular" />}
        </button>
        {editingTitle?.type === "layer" && editingTitle.id === layer.id ? (
          <RenameInput
            value={editingTitle.draft}
            onChange={(draft) => setEditingTitle({ ...editingTitle, draft })}
            onCancel={() => setEditingTitle(undefined)}
            onCommit={() => {
              dispatch(commandIds.documentRenameLayer, { layerId: layer.id, name: editingTitle.draft });
              setEditingTitle(undefined);
            }}
          />
        ) : (
          <button
            type="button"
            className="min-w-0 flex-1 truncate text-left font-medium"
            onClick={() => dispatch(commandIds.selectionSet, { artboardId, layerIds: [layer.id] })}
            onDoubleClick={() => setEditingTitle({ type: "layer", id: layer.id, draft: layer.name })}
          >
            {layer.name}
          </button>
        )}
        <button
          type="button"
          className={selected ? "text-black/45" : "text-white/25 transition hover:text-white/70"}
          aria-label={layer.clippingMask ? "Clear layer mask" : "Use selected layer as mask"}
          title={layer.clippingMask ? "Clear mask" : "Use selected layer as mask"}
          disabled={!layer.clippingMask && !canSetMask}
          onClick={() => dispatch(commandIds.documentSetLayerClippingMask, { layerId: layer.id, maskLayerId: layer.clippingMask ? undefined : selectedMaskLayer?.layer.id })}
        >
          <Stack size={17} weight={layer.clippingMask ? "fill" : "regular"} />
        </button>
        <button type="button" className={selected ? "text-black/45" : "text-white/25 transition hover:text-white/70"} aria-label={layer.locked ? "Unlock layer" : "Lock layer"} onClick={() => dispatch(commandIds.documentSetLayerLocked, { layerId: layer.id, locked: !layer.locked })}>
          {layer.locked ? <Lock size={17} weight="regular" /> : <LockOpen size={17} weight="regular" />}
        </button>
      </div>
      {maskLayer ? (
        <div className="mt-1 flex items-center gap-2 text-xs text-sky-100/60" style={{ paddingLeft: 52 + depth * 16 + maskIndent }}>
          <span className="h-px w-5 bg-sky-200/25" />
          <span>masked by {maskLayer.name}</span>
        </div>
      ) : null}
      {layer.type === "group"
        ? layer.children.map((child) => (
            <LayerRow
              key={child.id}
              document={document}
              artboardId={artboardId}
              layer={child}
              depth={depth + 1}
              selectedLayerIds={selectedLayerIds}
              draggedLayerId={draggedLayerId}
              editingTitle={editingTitle}
              setEditingTitle={setEditingTitle}
              selectedMaskLayer={selectedMaskLayer}
              dispatch={dispatch}
            />
          ))
        : null}
    </div>
  );
}

type EditingTitle =
  | { type: "artboard"; id: ArtboardId; draft: string }
  | { type: "layer"; id: string; draft: string };

function RenameInput({ value, onChange, onCommit, onCancel }: { value: string; onChange: (value: string) => void; onCommit: () => void; onCancel: () => void }) {
  return (
    <input
      autoFocus
      className="min-w-0 flex-1 rounded-md bg-white px-2 py-1 font-medium text-black outline-none ring-1 ring-black/10 focus:ring-sky-300/50"
      value={value}
      onChange={(event) => onChange(event.target.value)}
      onBlur={onCommit}
      onFocus={(event) => event.currentTarget.select()}
      onClick={(event) => event.stopPropagation()}
      onDoubleClick={(event) => event.stopPropagation()}
      onKeyDown={(event) => {
        event.stopPropagation();
        if (event.key === "Enter") event.currentTarget.blur();
        if (event.key === "Escape") onCancel();
      }}
    />
  );
}

function dropLayer(
  document: ImageDocument,
  sourceLayerId: string,
  target: { artboardId: ArtboardId; layer: Layer },
  event: DragEvent<HTMLElement>,
  dispatch: AppStore["dispatch"],
) {
  const rect = event.currentTarget.getBoundingClientRect();
  const command = resolveLayerDrop({
    document,
    sourceLayerId,
    target,
    verticalRatio: (event.clientY - rect.top) / Math.max(1, rect.height),
  });
  if (command) dispatch(commandIds.documentMoveLayer, command);
}

function deleteSelection(selection: SelectionState, selectedLayer: LayerInfo | undefined, dispatch: AppStore["dispatch"]) {
  if (selectedLayer) {
    dispatch(commandIds.documentRemoveLayer, { layerId: selectedLayer.layer.id });
    return;
  }
  if (selection.artboardId) dispatch(commandIds.documentRemoveArtboard, { id: selection.artboardId });
}

function addArtboard(document: ImageDocument, dispatch: AppStore["dispatch"]) {
  const index = document.artboards.length + 1;
  dispatch(commandIds.documentAddArtboard, {
    id: crypto.randomUUID(),
    name: `Artboard ${index}`,
    bounds: { x: (index - 1) * 40, y: (index - 1) * 40, w: 800, h: 600 },
  });
}

function addLayer(document: ImageDocument, artboardId: ArtboardId, selectedLayer: LayerInfo | undefined, dispatch: AppStore["dispatch"]) {
  const artboard = document.artboards.find((candidate) => candidate.id === artboardId);
  if (!artboard) return;

  const assetId = crypto.randomUUID();
  const layerId = crypto.randomUUID();
  const width = Math.max(1, Math.round(artboard.bounds.w));
  const height = Math.max(1, Math.round(artboard.bounds.h));
  const source = `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"></svg>`)}`;

  dispatch(commandIds.documentAddAsset, {
    asset: {
      id: assetId,
      name: "Empty Layer",
      mimeType: "image/svg+xml",
      source,
      intrinsicSize: { w: width, h: height },
    },
  });
  dispatch(commandIds.documentAddImageLayer, {
    artboardId,
    parentGroupId: selectedLayer?.layer.type === "group" ? selectedLayer.layer.id : undefined,
    layer: {
      id: layerId,
      type: "image",
      name: "Layer",
      visible: true,
      locked: false,
      opacity: 1,
      assetId,
      transform: { position: { x: artboard.bounds.x, y: artboard.bounds.y }, scale: { x: 1, y: 1 }, rotation: 0 },
    },
  });
  dispatch(commandIds.selectionSet, { artboardId, layerIds: [layerId] });
}

function addGroup(artboardId: ArtboardId, dispatch: AppStore["dispatch"]) {
  dispatch(commandIds.documentAddGroupLayer, { artboardId, group: createGroup("Group") });
}

function groupSelection(artboardId: ArtboardId, layerIds: string[], dispatch: AppStore["dispatch"]) {
  dispatch(commandIds.documentGroupLayers, { artboardId, layerIds, group: createGroup("Group") });
}

function moveLayer(document: ImageDocument, info: LayerInfo, direction: -1 | 1, dispatch: AppStore["dispatch"]) {
  const siblings = info.parentGroupId ? findGroup(document, info.parentGroupId)?.children : document.artboards.find((artboard) => artboard.id === info.artboardId)?.layers;
  if (!siblings) return;
  const currentIndex = siblings.findIndex((layer) => layer.id === info.layer.id);
  if (currentIndex < 0) return;
  dispatch(commandIds.documentMoveLayer, {
    layerId: info.layer.id,
    toArtboardId: info.artboardId,
    toParentGroupId: info.parentGroupId,
    toIndex: currentIndex + direction,
  });
}

function createGroup(name: string): Layer {
  return {
    id: crypto.randomUUID(),
    type: "group",
    name,
    visible: true,
    locked: false,
    opacity: 1,
    transform: { position: { x: 0, y: 0 }, scale: { x: 1, y: 1 }, rotation: 0 },
    children: [],
  };
}

function toolbarButtonClass() {
  return "inline-flex h-8 items-center gap-1.5 rounded-full border border-white/10 bg-white/[0.04] px-3 text-white/75 transition hover:border-white/20 hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-35";
}

function iconButtonClass() {
  return "grid size-8 place-items-center rounded-full text-white/70 transition hover:bg-white/10 hover:text-white focus:outline-none focus-visible:outline-none";
}
