import { useMemo, useRef, useState, type DragEvent, type MutableRefObject } from "react";
import { ArrowDown, ArrowUp, DownloadSimple, Eye, EyeSlash, FolderPlus, Lock, LockOpen, Plus, Stack, Trash } from "@phosphor-icons/react";
import { commandIds } from "@commands/ids";
import type { ImageDocument } from "@core/document";
import type { Layer } from "@core/layer";
import { getLayerMask } from "@core/layer-mask-utils";
import type { ArtboardId } from "@core/id";
import { createDocumentReadIndex, type DocumentReadIndex } from "@editor/document-indexes";
import type { MaskEditState, SelectionState } from "@editor/state";
import type { AppStore } from "@editor/store";
import { resolveLayerDrop } from "@input/index";
import type { DocumentActions } from "@app/document-actions";
import { addArtboard, addEmptyLayer, addGroupLayer, addLayerMask, deleteSelection, groupLayers, moveLayer } from "@operations/document/layerActions";
import { MaskOperationButtons, MaskStatus } from "./layers/MaskControls";

export type LayersSheetProps = {
  document: ImageDocument;
  selection: SelectionState;
  maskEdit?: MaskEditState;
  open: boolean;
  dispatch: AppStore["dispatch"];
  documentActions: DocumentActions;
};

export function LayersSheet({ document, selection, maskEdit, open, dispatch, documentActions }: LayersSheetProps) {
  const draggedLayerId = useRef<string | undefined>(undefined);
  const [editingTitle, setEditingTitle] = useState<EditingTitle>();

  return (
    <aside
      id="layers-sheet"
      aria-hidden={!open}
      aria-label="Layers"
      className={`pointer-events-auto absolute bottom-6 right-4 top-24 z-20 flex w-[28rem] flex-col overflow-hidden rounded-[2.5rem] px-4 text-sm text-white backdrop-blur-xl transition-all duration-200 ${
        open ? "translate-x-0 opacity-100" : "pointer-events-none translate-x-8 opacity-0"
      }`}
    >
      {open ? (
        <LayersSheetBody
          document={document}
          selection={selection}
          maskEdit={maskEdit}
          draggedLayerId={draggedLayerId}
          editingTitle={editingTitle}
          setEditingTitle={setEditingTitle}
          dispatch={dispatch}
          documentActions={documentActions}
        />
      ) : null}
    </aside>
  );
}

function LayersSheetBody({
  document,
  selection,
  maskEdit,
  draggedLayerId,
  editingTitle,
  setEditingTitle,
  dispatch,
  documentActions,
}: Omit<LayersSheetProps, "open"> & {
  draggedLayerId: MutableRefObject<string | undefined>;
  editingTitle: EditingTitle | undefined;
  setEditingTitle: (editingTitle: EditingTitle | undefined) => void;
}) {
  const documentIndex = useMemo(() => createDocumentReadIndex(document), [document]);
  const selectedArtboardId = 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 maskLayerIds = documentIndex.maskLayerIds;

  return (
    <>
      <header className="flex h-20 items-center">
        <div className="flex w-full items-center gap-2">
          <button type="button" className={labeledToolbarButtonClass()} aria-label="Add artboard" title="Add artboard" onClick={() => addArtboard(document, dispatch)}>
            <span className="grid w-12 flex-none place-items-center"><Plus size={24} /></span>
            <span className="min-w-0 flex-1 text-center">Artboard</span>
          </button>
          <button type="button" className={labeledToolbarButtonClass()} aria-label="Add layer" title="Add layer" disabled={!selectedArtboardId} onClick={() => selectedArtboardId && addEmptyLayer(document, selectedArtboardId, selectedLayer, dispatch)}>
            <span className="grid w-12 flex-none place-items-center"><Plus size={24} /></span>
            <span className="min-w-0 flex-1 text-center">Layer</span>
          </button>
          <button type="button" className={toolbarButtonClass()} aria-label="Add group" title="Add group" disabled={!selectedArtboardId} onClick={() => selectedArtboardId && addGroupLayer(selectedArtboardId, dispatch)}>
            <FolderPlus size={24} />
          </button>
        </div>
      </header>
      <div className="mb-4 grid grid-cols-5 gap-2">
        <button type="button" className={toolbarButtonClass()} aria-label="Group selected" title="Group selected" disabled={!canGroup} onClick={() => selection.artboardId && groupLayers(selection.artboardId, selection.layerIds, dispatch)}>
          <Stack size={24} />
        </button>
        <button type="button" className={toolbarButtonClass()} aria-label="Ungroup" title="Ungroup" disabled={!canUngroup} onClick={() => selectedLayer && dispatch(commandIds.documentUngroupLayer, { groupId: selectedLayer.layer.id })}>
          <Stack size={24} weight="fill" />
        </button>
        <button type="button" className={toolbarButtonClass()} aria-label="Move layer up" title="Move layer up" disabled={!selectedLayer} onClick={() => selectedLayer && moveLayer(documentIndex, selectedLayer, -1, dispatch)}>
          <ArrowUp size={24} />
        </button>
        <button type="button" className={toolbarButtonClass()} aria-label="Move layer down" title="Move layer down" disabled={!selectedLayer} onClick={() => selectedLayer && moveLayer(documentIndex, selectedLayer, 1, dispatch)}>
          <ArrowDown size={24} />
        </button>
        <button type="button" className={toolbarButtonClass()} aria-label="Delete selection" title="Delete selection" disabled={!selectedLayer && !selection.artboardId} onClick={() => deleteSelection(selection, selectedLayer, dispatch)}>
          <Trash size={24} />
        </button>
      </div>
      <div className="min-h-0 flex-1 overflow-auto pb-2">
        {document.artboards.map((artboard) => {
          const displayLayerCount = documentIndex.displayLayerCountByArtboardId.get(artboard.id) ?? 0;

          return (
          <section key={artboard.id} className="mb-5 last:mb-0">
            <div
              className={`flex h-12 w-full items-center gap-3 rounded-full px-4 text-left transition ${selection.artboardId === artboard.id && selection.layerIds.length === 0 ? "bg-white text-black" : "text-white/70 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={selection.artboardId === artboard.id && selection.layerIds.length === 0 ? "text-black/55 transition hover:text-black" : "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={24} weight="regular" /> : <EyeSlash size={24} weight="regular" />}
              </button>
              <button type="button" className={selection.artboardId === artboard.id && selection.layerIds.length === 0 ? "text-black/55 transition hover:text-black" : "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={24} weight="regular" /> : <LockOpen size={24} 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={selection.artboardId === artboard.id && selection.layerIds.length === 0 ? "text-black/55 transition hover:text-black" : "text-white/55 transition hover:text-white"}
                aria-label={`Export ${artboard.name} as PNG`}
                title="Export PNG"
                onClick={(event) => {
                  event.stopPropagation();
                  void documentActions.exportArtboard(artboard.id);
                }}
              >
                <DownloadSimple size={24} weight="regular" />
              </button>
              <span className={selection.artboardId === artboard.id && selection.layerIds.length === 0 ? "min-w-8 rounded-full bg-black/10 px-2 py-1 text-center text-xs text-black/45" : "min-w-8 rounded-full bg-white/10 px-2 py-1 text-center text-xs text-white/45"}>{displayLayerCount}</span>
            </div>
            <div className="mt-2 space-y-2 pl-5">
              {displayLayerCount === 0 ? (
                <div className="rounded-[1.5rem] border border-dashed border-white/10 px-4 py-5 text-center text-white/35">No layers yet</div>
              ) : (
                artboard.layers.map((layer) => (
                  <LayerRow
                    key={layer.id}
                    document={document}
                    documentIndex={documentIndex}
                    artboardId={artboard.id}
                    layer={layer}
                    depth={0}
                    selectedLayerIds={selection.layerIds}
                    draggedLayerId={draggedLayerId}
                    editingTitle={editingTitle}
                    setEditingTitle={setEditingTitle}
                    maskLayerIds={maskLayerIds}
                    maskEdit={maskEdit}
                    dispatch={dispatch}
                  />
                ))
              )}
            </div>
          </section>
          );
        })}
      </div>
    </>
  );
}

function LayerRow({
  document,
  documentIndex,
  artboardId,
  layer,
  depth,
  selectedLayerIds,
  draggedLayerId,
  editingTitle,
  setEditingTitle,
  maskLayerIds,
  maskEdit,
  dispatch,
}: {
  document: ImageDocument;
  documentIndex: DocumentReadIndex;
  artboardId: ArtboardId;
  layer: Layer;
  depth: number;
  selectedLayerIds: string[];
  draggedLayerId: MutableRefObject<string | undefined>;
  editingTitle: EditingTitle | undefined;
  setEditingTitle: (editingTitle: EditingTitle | undefined) => void;
  maskLayerIds: ReadonlySet<string>;
  maskEdit?: MaskEditState;
  dispatch: AppStore["dispatch"];
}) {
  if (maskLayerIds.has(layer.id)) return null;

  const selected = selectedLayerIds.includes(layer.id);
  const layerInfo = documentIndex.layerInfoById.get(layer.id);
  const layerMask = getLayerMask(layer);
  const maskLayer = layerMask ? documentIndex.layerById.get(layerMask.maskLayerId) : undefined;
  const maskAsset = maskLayer && maskLayer.type !== "group" ? documentIndex.assetById.get(maskLayer.assetId) : undefined;
  const canAddMask = Boolean(layerInfo && layer.type !== "group" && !layerMask);
  const editingMask = Boolean(maskEdit && layerMask && maskEdit.targetLayerId === layer.id && maskEdit.maskLayerId === layerMask.maskLayerId);
  const rowPadding = 12 + depth * 16;

  return (
    <div>
      <div
        draggable
        className={`group flex h-12 w-full items-center gap-3 rounded-full px-4 text-left transition ${editingMask ? "bg-sky-300 text-black" : selected ? "bg-white text-black" : "text-white/75 hover:bg-white/[0.07] hover:text-white"}`}
        style={{ paddingLeft: rowPadding }}
        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;
        }}
      >
        <button type="button" className={editingMask || 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={24} weight="regular" /> : <EyeSlash size={24} 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>
        )}
        {layerMask ? (
          <span className={`inline-flex items-center gap-1 rounded-full px-3 py-1 text-xs ${editingMask || selected ? "bg-black/10 text-black/65" : "bg-sky-400/10 text-sky-100/70"}`}>
            <Stack size={13} weight="fill" /> Mask
          </span>
        ) : canAddMask && layerInfo ? (
          <button
            type="button"
            className={editingMask || selected ? "rounded-full bg-black/10 px-3 py-1 text-xs text-black/65 transition hover:bg-black/15" : "rounded-full bg-white/5 px-3 py-1 text-xs text-white/45 transition hover:bg-sky-400/15 hover:text-sky-100"}
            onClick={() => addLayerMask(documentIndex, layerInfo, dispatch)}
          >
            Add mask
          </button>
        ) : null}
        <button type="button" className={editingMask || 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={24} weight="regular" /> : <LockOpen size={24} weight="regular" />}
        </button>
      </div>
      {layerMask ? (
        <div className="mt-2 flex min-h-14 flex-wrap items-center gap-2 rounded-[1.5rem] py-2 pl-4 pr-2 text-sm text-sky-100/70 hover:bg-white/[0.04]">
          <Stack size={24} weight="fill" className="shrink-0" />
          <span className="min-w-28 flex-1 truncate">{maskLayer ? "Layer mask" : "Layer mask missing"}</span>
          {maskAsset ? <MaskStatus asset={maskAsset} /> : null}
          {maskLayer ? (
            <div className="flex flex-wrap items-center gap-1">
              <button
                type="button"
                className={`h-9 rounded-full px-4 text-sm font-medium transition ${editingMask ? "bg-sky-300 text-black" : "text-sky-100/75 hover:bg-white/10 hover:text-sky-50"}`}
                onClick={() =>
                  editingMask
                    ? dispatch(commandIds.toolExitMaskEdit, undefined)
                    : dispatch(commandIds.toolEnterMaskEdit, { targetLayerId: layer.id, maskLayerId: layerMask.maskLayerId })
                }
              >
                {editingMask ? "Done" : "Edit"}
              </button>
              <button
                type="button"
                className={maskActionButtonClass()}
                title="Paint reveal"
                onClick={() => {
                  dispatch(commandIds.toolEnterMaskEdit, { targetLayerId: layer.id, maskLayerId: layerMask.maskLayerId });
                  dispatch(commandIds.toolSetActive, { tool: "brush" });
                }}
              >
                Reveal
              </button>
              <button
                type="button"
                className={maskActionButtonClass()}
                title="Paint hide"
                onClick={() => {
                  dispatch(commandIds.toolEnterMaskEdit, { targetLayerId: layer.id, maskLayerId: layerMask.maskLayerId });
                  dispatch(commandIds.toolSetActive, { tool: "eraser" });
                }}
              >
                Hide
              </button>
              {maskAsset && maskLayer.type !== "group" ? (
                <MaskOperationButtons maskLayerId={maskLayer.id} maskAsset={maskAsset} dispatch={dispatch} />
              ) : null}
            </div>
          ) : null}
          <button
            type="button"
            className="h-9 rounded-full px-4 text-sm font-medium text-sky-100/55 transition hover:bg-red-400/15 hover:text-red-100"
            onClick={() => dispatch(commandIds.documentRemoveLayerMask, { layerId: layer.id })}
          >
            Remove
          </button>
        </div>
      ) : null}
      {layer.type === "group"
        ? layer.children.map((child) => (
            <LayerRow
              key={child.id}
              document={document}
              documentIndex={documentIndex}
              artboardId={artboardId}
              layer={child}
              depth={depth + 1}
              selectedLayerIds={selectedLayerIds}
              draggedLayerId={draggedLayerId}
              editingTitle={editingTitle}
              setEditingTitle={setEditingTitle}
              maskLayerIds={maskLayerIds}
              maskEdit={maskEdit}
              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
      aria-label="Rename item"
      className="min-w-0 flex-1 rounded-full bg-white px-3 py-1.5 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 toolbarButtonClass() {
  return "inline-flex size-12 items-center justify-center rounded-full text-white/75 transition hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-35 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30";
}

function labeledToolbarButtonClass() {
  return "inline-flex h-12 flex-1 items-center rounded-full pr-4 text-sm font-medium text-white/75 transition hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-35 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30";
}

function maskActionButtonClass() {
  return "rounded-full bg-white/5 px-2.5 py-1 text-[0.7rem] font-semibold text-white/60 transition hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-35";
}
