import { useEffect, useState, type Dispatch, type SetStateAction } from "react";
import { ArrowsOut, BoundingBox, Copy, Crop, MaskHappy } from "@phosphor-icons/react";
import { commandIds } from "@commands/ids";
import type { Rect } from "@core/geometry";
import type { AppStore } from "@editor/store";
import type { TransformTarget } from "@editor/transform";
import type { DocumentReadIndex, IndexedLayerInfo } from "@editor/document-indexes";
import { addLayerMask, duplicateLayer } from "@operations/document/layerActions";
import { getLayerMask } from "@core/layer-mask-utils";
import { BottomControlDivider } from "./Divider";
import { bottomControlFieldClass, bottomControlIconSlotClass, bottomControlInputClass, bottomControlLabelClass, bottomControlMenuClass } from "./styles";

export type TransformControlsProps = {
  bounds: Rect;
  target: TransformTarget;
  documentIndex: DocumentReadIndex;
  layerInfo?: IndexedLayerInfo;
  dispatch: AppStore["dispatch"];
};

type BoundsField = keyof Rect;

export function TransformControls({ bounds, target, documentIndex, layerInfo, dispatch }: TransformControlsProps) {
  const [draft, setDraft] = useState(() => draftFromBounds(bounds));
  const [opacityDraft, setOpacityDraft] = useState(() => String(Math.round((layerInfo?.layer.opacity ?? 1) * 100)));
  const [rotationDraft, setRotationDraft] = useState(() => rotationDegrees(layerInfo));
  const [cropOpen, setCropOpen] = useState(false);
  const [cropDraft, setCropDraft] = useState(() => cropDraftFor(layerInfo, documentIndex));
  const [resizeOpen, setResizeOpen] = useState(false);
  const [resizeDraft, setResizeDraft] = useState(() => ({ w: String(Math.round(bounds.w)), h: String(Math.round(bounds.h)), scaleContents: false }));
  const layer = layerInfo?.layer;
  const locked = layer?.locked ?? false;
  const mask = layer ? getLayerMask(layer) : undefined;
  const rotatedMaskEditingUnsupported = Boolean(layer && (layer.type === "image" || layer.type === "raster") && layer.transform.rotation !== 0);

  useEffect(() => {
    setDraft(draftFromBounds(bounds));
  }, [bounds.x, bounds.y, bounds.w, bounds.h]);

  useEffect(() => setOpacityDraft(String(Math.round((layer?.opacity ?? 1) * 100))), [layer?.id, layer?.opacity]);
  useEffect(() => setRotationDraft(rotationDegrees(layerInfo)), [layer?.id, layer?.transform.rotation]);
  useEffect(() => setCropDraft(cropDraftFor(layerInfo, documentIndex)), [documentIndex, layer?.id, layer?.type === "image" || layer?.type === "raster" ? layer.sourceRect : undefined]);
  useEffect(() => setResizeDraft((current) => ({ ...current, w: String(Math.round(bounds.w)), h: String(Math.round(bounds.h)) })), [bounds.w, bounds.h, target]);

  const commitField = (field: BoundsField) => {
    const value = Number.parseFloat(draft[field]);
    if (!Number.isFinite(value)) {
      setDraft(draftFromBounds(bounds));
      return;
    }

    if (locked) return;
    dispatch(commandIds.transformSetBounds, {
      target,
      bounds: {
        ...bounds,
        [field]: field === "w" || field === "h" ? Math.max(1, value) : value,
      },
    });
  };

  return (
    <div className={bottomControlMenuClass()}>
      <span className={bottomControlIconSlotClass()}>
        <BoundingBox size={24} weight="regular" />
      </span>
      <BottomControlDivider />
      <BoundsInput label="X" field="x" draft={draft.x} disabled={locked} setDraft={setDraft} commitField={commitField} />
      <BoundsInput label="Y" field="y" draft={draft.y} disabled={locked} setDraft={setDraft} commitField={commitField} />
      <BottomControlDivider />
      <BoundsInput label="W" field="w" draft={draft.w} disabled={locked} setDraft={setDraft} commitField={commitField} />
      <BoundsInput label="H" field="h" draft={draft.h} disabled={locked} setDraft={setDraft} commitField={commitField} />
      {layer ? (
        <>
          <BottomControlDivider />
          <SimpleInput
            label="Opacity"
            suffix="%"
            value={opacityDraft}
            disabled={locked}
            onChange={setOpacityDraft}
            onCommit={() => {
              const value = Number.parseFloat(opacityDraft);
              if (Number.isFinite(value)) dispatch(commandIds.documentSetLayerOpacity, { layerId: layer.id, opacity: value / 100 });
              else setOpacityDraft(String(Math.round(layer.opacity * 100)));
            }}
          />
          <SimpleInput
            label="Rotation"
            suffix="°"
            value={rotationDraft}
            disabled={locked || layer.type === "group" || layer.type === "adjustment"}
            title={layer.type === "group" || layer.type === "adjustment" ? "Group rotation is not supported" : undefined}
            onChange={setRotationDraft}
            onCommit={() => {
              const value = Number.parseFloat(rotationDraft);
              if (Number.isFinite(value)) dispatch(commandIds.transformSetRotation, { target, rotation: value * Math.PI / 180 });
              else setRotationDraft(rotationDegrees(layerInfo));
            }}
          />
          <BottomControlDivider />
          <button type="button" className={actionButtonClass()} disabled={locked} title={locked ? "Unlock the layer to duplicate it" : "Duplicate layer"} onClick={() => duplicateLayer(documentIndex, layerInfo!, dispatch)}>
            <Copy size={20} /> Duplicate
          </button>
          {layer.type === "image" || layer.type === "raster" ? (
            <button type="button" className={actionButtonClass()} disabled={locked || layer.transform.rotation !== 0} title={layer.transform.rotation !== 0 ? "Reset rotation before cropping" : "Crop visible source pixels"} onClick={() => setCropOpen((open) => !open)}>
              <Crop size={20} /> Crop
            </button>
          ) : null}
          {layer.type === "image" || layer.type === "raster" ? (
            <button
              type="button"
              className={actionButtonClass()}
              disabled={locked || rotatedMaskEditingUnsupported}
              title={locked ? "Unlock the layer to edit its mask" : rotatedMaskEditingUnsupported ? "Reset rotation to edit the layer mask" : mask ? "Edit layer mask" : "Add layer mask"}
              onClick={() => {
                if (mask) dispatch(commandIds.toolEnterMaskEdit, { targetLayerId: layer.id, maskLayerId: mask.maskLayerId });
                else addLayerMask(documentIndex, layerInfo!, dispatch);
              }}
            >
              <MaskHappy size={20} /> {mask ? "Edit mask" : "Add mask"}
            </button>
          ) : null}
          {locked ? <span className="text-xs text-amber-200/70">Unlock to edit</span> : null}
          {cropOpen && (layer.type === "image" || layer.type === "raster") ? <CropEditor draft={cropDraft} setDraft={setCropDraft} onCancel={() => { setCropDraft(cropDraftFor(layerInfo, documentIndex)); setCropOpen(false); }} onReset={() => { dispatch(commandIds.documentSetLayerSourceRect, { layerId: layer.id }); setCropOpen(false); }} onApply={() => { const sourceRect = parseRectDraft(cropDraft); if (sourceRect) { dispatch(commandIds.documentSetLayerSourceRect, { layerId: layer.id, sourceRect }); setCropOpen(false); } }} /> : null}
        </>
      ) : target.type === "artboard" ? (
        <>
          <BottomControlDivider />
          <button type="button" className={actionButtonClass()} disabled={locked} onClick={() => setResizeOpen((open) => !open)}><ArrowsOut size={20} /> Resize canvas</button>
          {resizeOpen ? <ArtboardResizeEditor draft={resizeDraft} setDraft={setResizeDraft} onCancel={() => setResizeOpen(false)} onApply={() => { const w = Number.parseFloat(resizeDraft.w); const h = Number.parseFloat(resizeDraft.h); if (Number.isFinite(w) && Number.isFinite(h) && w >= 1 && h >= 1) { dispatch(commandIds.documentResizeArtboard, { id: target.id, bounds: { ...bounds, w, h }, scaleContents: resizeDraft.scaleContents }); setResizeOpen(false); } }} /> : null}
        </>
      ) : null}
    </div>
  );
}

type CropDraft = Record<BoundsField, string>;

function CropEditor({ draft, setDraft, onApply, onCancel, onReset }: { draft: CropDraft; setDraft: Dispatch<SetStateAction<CropDraft>>; onApply: () => void; onCancel: () => void; onReset: () => void }) {
  return <div className="flex items-center gap-2 border-l border-white/[0.08] pl-3" aria-label="Crop source pixels">
    {(["x", "y", "w", "h"] as const).map((field) => <label key={field} className={bottomControlFieldClass()}><span className={bottomControlLabelClass()}>{field.toUpperCase()}</span><input className={bottomControlInputClass()} inputMode="decimal" value={draft[field]} aria-label={`Crop ${field}`} onChange={(event) => setDraft((current) => ({ ...current, [field]: event.target.value }))} onKeyDown={(event) => event.stopPropagation()} /></label>)}
    <button type="button" className={actionButtonClass()} onClick={onApply}>Apply</button><button type="button" className={actionButtonClass()} onClick={onCancel}>Cancel</button><button type="button" className={actionButtonClass()} onClick={onReset}>Reset</button>
  </div>;
}

function ArtboardResizeEditor({ draft, setDraft, onApply, onCancel }: { draft: { w: string; h: string; scaleContents: boolean }; setDraft: Dispatch<SetStateAction<{ w: string; h: string; scaleContents: boolean }>>; onApply: () => void; onCancel: () => void }) {
  return <div className="flex items-center gap-2 border-l border-white/[0.08] pl-3" aria-label="Resize artboard canvas">
    {(["w", "h"] as const).map((field) => <label key={field} className={bottomControlFieldClass()}><span className={bottomControlLabelClass()}>{field.toUpperCase()}</span><input className={bottomControlInputClass()} inputMode="decimal" value={draft[field]} aria-label={`Canvas ${field}`} onChange={(event) => setDraft((current) => ({ ...current, [field]: event.target.value }))} onKeyDown={(event) => event.stopPropagation()} /></label>)}
    <label className="flex items-center gap-2 text-xs text-white/70"><input type="checkbox" checked={draft.scaleContents} onChange={(event) => setDraft((current) => ({ ...current, scaleContents: event.target.checked }))} />Scale contents</label>
    <span className="max-w-40 text-[10px] text-white/45">Off changes canvas bounds only.</span>
    <button type="button" className={actionButtonClass()} onClick={onApply}>Apply</button><button type="button" className={actionButtonClass()} onClick={onCancel}>Cancel</button>
  </div>;
}

function cropDraftFor(layerInfo: IndexedLayerInfo | undefined, index: DocumentReadIndex): CropDraft {
  if (!layerInfo || (layerInfo.layer.type !== "image" && layerInfo.layer.type !== "raster")) return { x: "0", y: "0", w: "1", h: "1" };
  const asset = index.assetById.get(layerInfo.layer.assetId);
  const rect = layerInfo.layer.sourceRect ?? { x: 0, y: 0, w: asset?.intrinsicSize.w ?? 1, h: asset?.intrinsicSize.h ?? 1 };
  return draftFromBounds(rect);
}

function parseRectDraft(draft: CropDraft): Rect | undefined {
  const rect = { x: Number.parseFloat(draft.x), y: Number.parseFloat(draft.y), w: Number.parseFloat(draft.w), h: Number.parseFloat(draft.h) };
  return Object.values(rect).every(Number.isFinite) && rect.w >= 1 && rect.h >= 1 ? rect : undefined;
}

function SimpleInput({ label, suffix, value, disabled, title, onChange, onCommit }: { label: string; suffix: string; value: string; disabled?: boolean; title?: string; onChange: (value: string) => void; onCommit: () => void }) {
  return (
    <label className={`${bottomControlFieldClass()} ${disabled ? "opacity-40" : ""}`} title={title}>
      <span className={bottomControlLabelClass()}>{label}</span>
      <span className="flex items-center">
        <input className={`${bottomControlInputClass()} w-16`} inputMode="decimal" value={value} disabled={disabled} aria-label={label} onChange={(event) => onChange(event.target.value)} onBlur={onCommit} onFocus={(event) => event.currentTarget.select()} onKeyDown={(event) => { event.stopPropagation(); if (event.key === "Enter") event.currentTarget.blur(); if (event.key === "Escape") event.currentTarget.blur(); }} />
        <span className="-ml-2 text-xs text-white/40">{suffix}</span>
      </span>
    </label>
  );
}

function rotationDegrees(layerInfo?: IndexedLayerInfo) {
  return String(Math.round((layerInfo?.layer.transform.rotation ?? 0) * 180 / Math.PI));
}

function actionButtonClass() {
  return "inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs font-semibold text-white/65 transition hover:bg-white/[0.07] hover:text-white disabled:pointer-events-none disabled:opacity-35";
}

function BoundsInput({
  label,
  field,
  draft,
  disabled,
  setDraft,
  commitField,
}: {
  label: string;
  field: BoundsField;
  draft: string;
  disabled?: boolean;
  setDraft: Dispatch<SetStateAction<Record<BoundsField, string>>>;
  commitField: (field: BoundsField) => void;
}) {
  return (
    <label className={`${bottomControlFieldClass()} ${disabled ? "opacity-40" : ""}`}>
      <span className={bottomControlLabelClass()}>{label}</span>
      <input
        className={bottomControlInputClass()}
        inputMode="decimal"
        value={draft}
        aria-label={label}
        disabled={disabled}
        onChange={(event) => setDraft((current) => ({ ...current, [field]: event.target.value }))}
        onBlur={() => commitField(field)}
        onFocus={(event) => event.currentTarget.select()}
        onKeyDown={(event) => {
          event.stopPropagation();
          if (event.key === "Enter") {
            commitField(field);
            event.currentTarget.blur();
          }
          if (event.key === "Escape") event.currentTarget.blur();
        }}
      />
    </label>
  );
}

function draftFromBounds(bounds: Rect): Record<BoundsField, string> {
  return {
    x: String(Math.round(bounds.x)),
    y: String(Math.round(bounds.y)),
    w: String(Math.round(bounds.w)),
    h: String(Math.round(bounds.h)),
  };
}
