import { useState, useRef, useCallback, useEffect } from "react";
import { Upload, Download, X, ImageIcon, Crosshair, Maximize2 } from "lucide-react";
import { cn } from "../lib/utils";

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const CHECKERBOARD: React.CSSProperties = {
  backgroundImage: `
    linear-gradient(45deg, #444 25%, transparent 25%),
    linear-gradient(-45deg, #444 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #444 75%),
    linear-gradient(-45deg, transparent 75%, #444 75%)
  `,
  backgroundSize: "16px 16px",
  backgroundPosition: "0 0, 0 8px, 8px -8px, -8px 0px",
  backgroundColor: "#2a2a2a",
};

// Extra space around the output rect in the editor so you can see the image
// when it extends (or will be panned) outside the output boundaries.
const PAD = 120;
const HANDLE_HIT = 8;
const MIN_CROP = 4;

// ---------------------------------------------------------------------------
// Module-level checkerboard tile cache
// Avoids recreating a canvas element on every drawEditor call.
// ---------------------------------------------------------------------------

let _checkerTile: HTMLCanvasElement | null = null;

function checkerTile(): HTMLCanvasElement {
  if (_checkerTile) return _checkerTile;
  const c = document.createElement("canvas");
  c.width = 16; c.height = 16;
  const ctx = c.getContext("2d")!;
  ctx.fillStyle = "#2a2a2a"; ctx.fillRect(0, 0, 16, 16);
  ctx.fillStyle = "#3d3d3d"; ctx.fillRect(0, 0, 8, 8); ctx.fillRect(8, 8, 8, 8);
  _checkerTile = c;
  return c;
}

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

// "pan" = drag inside output rect to move the image
type DragHandle =
  | "nw" | "n" | "ne"
  | "w"           | "e"
  | "sw" | "s" | "se"
  | "pan";

type DragState = {
  handle: DragHandle;
  startX: number;
  startY: number;
  origW: number;
  origH: number;
  origImgX: number;
  origImgY: number;
};

// ---------------------------------------------------------------------------
// Pure helpers
// ---------------------------------------------------------------------------

function getHandlePoints(
  cx0: number, cy0: number, cx1: number, cy1: number
): [DragHandle, number, number][] {
  const mx = (cx0 + cx1) / 2;
  const my = (cy0 + cy1) / 2;
  return [
    ["nw", cx0, cy0], ["n", mx, cy0], ["ne", cx1, cy0],
    ["w",  cx0, my],                  ["e",  cx1, my],
    ["sw", cx0, cy1], ["s", mx, cy1], ["se", cx1, cy1],
  ];
}

function hitHandle(
  px: number, py: number,
  cx0: number, cy0: number, cx1: number, cy1: number
): DragHandle | null {
  for (const [name, hx, hy] of getHandlePoints(cx0, cy0, cx1, cy1)) {
    if (Math.abs(px - hx) <= HANDLE_HIT && Math.abs(py - hy) <= HANDLE_HIT) {
      return name;
    }
  }
  if (px >= cx0 && px <= cx1 && py >= cy0 && py <= cy1) return "pan";
  return null;
}

function handleCursor(h: DragHandle | null, dragging = false): string {
  if (!h) return "default";
  if (h === "pan") return dragging ? "grabbing" : "grab";
  if (h === "nw" || h === "se") return "nwse-resize";
  if (h === "ne" || h === "sw") return "nesw-resize";
  if (h === "n"  || h === "s")  return "ns-resize";
  return "ew-resize";
}

// ---------------------------------------------------------------------------
// drawEditor
//
// Mental model: the output canvas (cropW × cropH) is pinned at (PAD, PAD).
// The source image sits at (PAD + imgX, PAD + imgY) and can extend outside
// the output in any direction. Areas inside the output not covered by the
// image are transparent (shown as checkerboard).
// ---------------------------------------------------------------------------
function drawEditor(
  canvas: HTMLCanvasElement,
  img: HTMLImageElement,
  imgW: number, imgH: number,
  cropW: number, cropH: number,
  imgX: number, imgY: number,
) {
  const cW = Math.max(cropW, imgW) + 2 * PAD;
  const cH = Math.max(cropH, imgH) + 2 * PAD;
  if (canvas.width !== cW || canvas.height !== cH) {
    canvas.width  = cW;
    canvas.height = cH;
  }

  const ctx = canvas.getContext("2d")!;

  // 1. Checkerboard background — use cached tile to avoid createElement every frame
  ctx.fillStyle = ctx.createPattern(checkerTile(), "repeat")!;
  ctx.fillRect(0, 0, cW, cH);

  // 2. Faint image boundary indicator (dashed border around full source image)
  ctx.save();
  ctx.strokeStyle = "rgba(255,255,255,0.2)";
  ctx.lineWidth = 1;
  ctx.setLineDash([4, 4]);
  ctx.strokeRect(PAD + imgX + 0.5, PAD + imgY + 0.5, imgW - 1, imgH - 1);
  ctx.setLineDash([]);
  ctx.restore();

  // 3. Draw source image
  ctx.drawImage(img, PAD + imgX, PAD + imgY);

  // 4. Dark overlay outside the output rect
  const ox0 = PAD, oy0 = PAD, ox1 = PAD + cropW, oy1 = PAD + cropH;
  ctx.fillStyle = "rgba(0,0,0,0.55)";
  ctx.fillRect(0,   0,   cW,          oy0);            // top
  ctx.fillRect(0,   oy1, cW,          cH - oy1);       // bottom
  ctx.fillRect(0,   oy0, ox0,         oy1 - oy0);      // left
  ctx.fillRect(ox1, oy0, cW - ox1,    oy1 - oy0);      // right

  // 5. Rule-of-thirds grid
  ctx.strokeStyle = "rgba(255,255,255,0.18)";
  ctx.lineWidth = 0.75;
  for (const t of [1 / 3, 2 / 3]) {
    ctx.beginPath(); ctx.moveTo(ox0 + cropW * t, oy0); ctx.lineTo(ox0 + cropW * t, oy1); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(ox0, oy0 + cropH * t); ctx.lineTo(ox1, oy0 + cropH * t); ctx.stroke();
  }

  // 6. Output rect border
  ctx.strokeStyle = "#fff";
  ctx.lineWidth = 1.5;
  ctx.setLineDash([5, 3]);
  ctx.strokeRect(ox0 + 0.5, oy0 + 0.5, cropW - 1, cropH - 1);
  ctx.setLineDash([]);

  // 7. Resize handles
  ctx.fillStyle = "#fff";
  ctx.strokeStyle = "#555";
  ctx.lineWidth = 1;
  for (const [, hx, hy] of getHandlePoints(ox0, oy0, ox1, oy1)) {
    ctx.beginPath();
    ctx.rect(hx - 4, hy - 4, 8, 8);
    ctx.fill();
    ctx.stroke();
  }
}

// ---------------------------------------------------------------------------
// CropTool
// ---------------------------------------------------------------------------

export function CropTool() {
  const [imageSrc,   setImageSrc]   = useState<string | null>(null);
  const [imageFile,  setImageFile]  = useState<File   | null>(null);
  const [imgW,       setImgW]       = useState(0);
  const [imgH,       setImgH]       = useState(0);
  const [imageReady, setImageReady] = useState(false);
  const [dragOver,   setDragOver]   = useState(false);

  // Output canvas dimensions
  const [cropW, setCropW] = useState(128);
  const [cropH, setCropH] = useState(128);

  // Where the source image's top-left sits within the output canvas.
  const [imgX, setImgX] = useState(0);
  const [imgY, setImgY] = useState(0);

  // Padding used by "Fit to Content"
  const [padding, setPadding] = useState(0);

  const fileInputRef     = useRef<HTMLInputElement>(null);
  const displayCanvasRef = useRef<HTMLCanvasElement>(null);
  const sourceCanvasRef  = useRef<HTMLCanvasElement>(null);
  // Direct canvas preview — avoids toDataURL on every drag frame
  const previewCanvasRef = useRef<HTMLCanvasElement>(null);
  const imgElRef         = useRef<HTMLImageElement | null>(null);
  const dragStateRef     = useRef<DragState | null>(null);

  // Always-fresh values for use inside stable callbacks
  const outputRef = useRef({ w: cropW, h: cropH, imgX, imgY, padding });
  useEffect(() => { outputRef.current = { w: cropW, h: cropH, imgX, imgY, padding }; });

  // ── Load image onto hidden source canvas ────────────────────────────────────
  useEffect(() => {
    if (!imageSrc) return;
    setImageReady(false);
    const img = new Image();
    img.onload = () => {
      const src = sourceCanvasRef.current!;
      src.width  = img.naturalWidth;
      src.height = img.naturalHeight;
      src.getContext("2d")!.drawImage(img, 0, 0);
      imgElRef.current = img;
      setImgW(img.naturalWidth);
      setImgH(img.naturalHeight);
      setCropW(img.naturalWidth);
      setCropH(img.naturalHeight);
      setImgX(0);
      setImgY(0);
      setImageReady(true);
    };
    img.src = imageSrc;
  }, [imageSrc]);

  // ── Redraw editor whenever state changes ─────────────────────────────────────
  useEffect(() => {
    if (!imageReady || !displayCanvasRef.current || !imgElRef.current) return;
    drawEditor(displayCanvasRef.current, imgElRef.current, imgW, imgH, cropW, cropH, imgX, imgY);
  }, [imageReady, imgW, imgH, cropW, cropH, imgX, imgY]);

  // ── Live preview — draw directly into previewCanvasRef (no toDataURL) ────────
  useEffect(() => {
    if (!imageReady || !previewCanvasRef.current) return;
    const src  = sourceCanvasRef.current!;
    const prev = previewCanvasRef.current;
    prev.width  = Math.max(1, Math.round(cropW));
    prev.height = Math.max(1, Math.round(cropH));
    // Clear to transparent, then composite the cropped region
    const ctx = prev.getContext("2d")!;
    ctx.clearRect(0, 0, prev.width, prev.height);
    ctx.drawImage(src, Math.round(imgX), Math.round(imgY));
  }, [imageReady, cropW, cropH, imgX, imgY]);

  // ── Auto-center ──────────────────────────────────────────────────────────────
  const autoCenter = useCallback(() => {
    if (!imageReady) return;
    const src = sourceCanvasRef.current!;
    const { data, width, height } = src.getContext("2d")!.getImageData(0, 0, src.width, src.height);

    let minX = width, minY = height, maxX = -1, maxY = -1;
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        if (data[(y * width + x) * 4 + 3] > 0) {
          if (x < minX) minX = x;
          if (x > maxX) maxX = x;
          if (y < minY) minY = y;
          if (y > maxY) maxY = y;
        }
      }
    }
    if (maxX === -1) return;

    const contentCX = (minX + maxX) / 2;
    const contentCY = (minY + maxY) / 2;
    const { w, h } = outputRef.current;
    setImgX(Math.round(w / 2 - contentCX));
    setImgY(Math.round(h / 2 - contentCY));
  }, [imageReady]); // eslint-disable-line react-hooks/exhaustive-deps

  // ── Fit to content ───────────────────────────────────────────────────────────
  const fitToContent = useCallback(() => {
    if (!imageReady) return;
    const src = sourceCanvasRef.current!;
    const { data, width, height } = src.getContext("2d")!.getImageData(0, 0, src.width, src.height);

    let minX = width, minY = height, maxX = -1, maxY = -1;
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        if (data[(y * width + x) * 4 + 3] > 0) {
          if (x < minX) minX = x;
          if (x > maxX) maxX = x;
          if (y < minY) minY = y;
          if (y > maxY) maxY = y;
        }
      }
    }
    if (maxX === -1) return;

    const pad = outputRef.current.padding;
    const contentW = maxX - minX + 1;
    const contentH = maxY - minY + 1;
    setCropW(contentW + 2 * pad);
    setCropH(contentH + 2 * pad);
    setImgX(pad - minX);
    setImgY(pad - minY);
  }, [imageReady]); // eslint-disable-line react-hooks/exhaustive-deps

  // ── File loading ─────────────────────────────────────────────────────────────
  const loadFile = useCallback((file: File) => {
    if (imageSrc) URL.revokeObjectURL(imageSrc);
    setImageSrc(URL.createObjectURL(file));
    setImageFile(file);
  }, [imageSrc]);

  const clearAll = useCallback(() => {
    if (imageSrc) URL.revokeObjectURL(imageSrc);
    setImageSrc(null);
    setImageFile(null);
    setImageReady(false);
    if (fileInputRef.current) fileInputRef.current.value = "";
  }, [imageSrc]);

  // Download: toBlob from previewCanvasRef — only at explicit user request
  const handleDownload = () => {
    const prev = previewCanvasRef.current;
    if (!prev || !imageReady || !imageFile) return;
    prev.toBlob((blob) => {
      if (!blob) return;
      const url = URL.createObjectURL(blob);
      const a   = document.createElement("a");
      a.download = imageFile.name.replace(/\.[^.]+$/, "") + "_cropped.png";
      a.href = url;
      a.click();
      URL.revokeObjectURL(url);
    }, "image/png");
  };

  // ── Canvas interaction ───────────────────────────────────────────────────────
  const getCanvasXY = (e: React.MouseEvent<HTMLCanvasElement>): [number, number] => {
    const canvas = displayCanvasRef.current!;
    const rect = canvas.getBoundingClientRect();
    return [
      (e.clientX - rect.left) * (canvas.width  / rect.width),
      (e.clientY - rect.top)  * (canvas.height / rect.height),
    ];
  };

  const onMouseDown = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    const [px, py] = getCanvasXY(e);
    const { w, h, imgX: ix, imgY: iy } = outputRef.current;
    const handle = hitHandle(px, py, PAD, PAD, PAD + w, PAD + h);
    if (!handle) return;
    dragStateRef.current = {
      handle,
      startX: px, startY: py,
      origW: w, origH: h,
      origImgX: ix, origImgY: iy,
    };
    if (displayCanvasRef.current) {
      displayCanvasRef.current.style.cursor = handleCursor(handle, true);
    }
    e.preventDefault();
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const onMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    const canvas = displayCanvasRef.current;
    if (!canvas) return;
    const [px, py] = getCanvasXY(e);

    if (!dragStateRef.current) {
      const { w, h } = outputRef.current;
      canvas.style.cursor = handleCursor(hitHandle(px, py, PAD, PAD, PAD + w, PAD + h));
      return;
    }

    const { handle, startX, startY, origW, origH, origImgX, origImgY } = dragStateRef.current;
    const dx = Math.round(px - startX);
    const dy = Math.round(py - startY);

    let nw = origW, nh = origH, nix = origImgX, niy = origImgY;

    if (handle === "pan") {
      nix = origImgX + dx;
      niy = origImgY + dy;
    } else {
      if (handle.includes("e")) nw = Math.max(MIN_CROP, origW + dx);
      if (handle.includes("s")) nh = Math.max(MIN_CROP, origH + dy);
      if (handle.includes("w")) {
        const d = Math.min(dx, origW - MIN_CROP);
        nw  = origW  - d;
        nix = origImgX - d;
      }
      if (handle.includes("n")) {
        const d = Math.min(dy, origH - MIN_CROP);
        nh  = origH  - d;
        niy = origImgY - d;
      }
    }

    setCropW(nw);
    setCropH(nh);
    setImgX(nix);
    setImgY(niy);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const onMouseUp = useCallback(() => {
    dragStateRef.current = null;
    if (displayCanvasRef.current) displayCanvasRef.current.style.cursor = "default";
  }, []);

  const parseIntSafe = (v: string, fallback: number) => {
    const n = parseInt(v, 10);
    return isNaN(n) ? fallback : n;
  };

  // ── Upload screen ────────────────────────────────────────────────────────────
  if (!imageSrc) {
    return (
      <div className="max-w-2xl mx-auto py-8 space-y-4">
        <div>
          <h2 className="text-lg font-semibold text-foreground mb-1">Crop Tool</h2>
          <p className="text-sm text-text-secondary">
            Upload an image, then define an output canvas. Drag inside the canvas
            to pan the image within it, or drag the edge handles to resize. The
            output canvas can extend beyond the image to add transparent padding.
          </p>
        </div>

        <div
          onDrop={(e) => {
            e.preventDefault();
            setDragOver(false);
            const f = e.dataTransfer.files[0];
            if (f?.type.startsWith("image/")) loadFile(f);
          }}
          onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
          onDragLeave={() => setDragOver(false)}
          onClick={() => fileInputRef.current?.click()}
          className={cn(
            "border-2 border-dashed rounded-xl p-16 flex flex-col items-center gap-4 cursor-pointer transition-all select-none",
            dragOver
              ? "border-primary bg-primary/5"
              : "border-border hover:border-primary/50 hover:bg-primary/3",
          )}
        >
          <Upload className={cn("w-10 h-10 transition-colors", dragOver ? "text-primary" : "text-text-tertiary")} />
          <div className="text-center">
            <p className="text-sm font-medium text-text-secondary">
              {dragOver ? "Drop to upload" : "Drop an image here"}
            </p>
            <p className="text-xs text-text-tertiary mt-1">
              or click to browse · PNG, JPEG, WebP · max 15 MB
            </p>
          </div>
        </div>

        <input
          ref={fileInputRef}
          type="file"
          accept="image/png,image/jpeg,image/webp"
          onChange={(e) => { const f = e.target.files?.[0]; if (f) loadFile(f); }}
          className="hidden"
        />
      </div>
    );
  }

  // ── Editor screen ────────────────────────────────────────────────────────────
  return (
    <div className="space-y-4 pb-8">
      <canvas ref={sourceCanvasRef} className="hidden" />

      {/* Toolbar */}
      <div className="flex items-center gap-3 flex-wrap">
        <h2 className="text-lg font-semibold text-foreground flex-1">Crop Tool</h2>
        <span className="text-xs text-text-tertiary font-mono truncate max-w-[200px]">
          {imageFile?.name}
        </span>
        <button
          onClick={clearAll}
          className={cn(
            "flex items-center gap-1.5 px-3 py-1.5 rounded-md border border-border text-xs text-text-tertiary",
            "hover:text-destructive hover:border-destructive transition-colors",
          )}
        >
          <X className="w-3.5 h-3.5" /> Clear
        </button>
        <button
          onClick={handleDownload}
          disabled={!imageReady}
          className={cn(
            "flex items-center gap-1.5 px-4 py-1.5 rounded-md text-xs font-semibold transition-colors",
            imageReady
              ? "bg-primary text-white hover:bg-primary/90"
              : "bg-raised border border-border text-text-tertiary cursor-not-allowed",
          )}
        >
          <Download className="w-3.5 h-3.5" /> Download PNG
        </button>
      </div>

      {/* Controls */}
      <div className="bg-card border border-border rounded-xl p-4 space-y-3">
        <div className="flex flex-wrap gap-4 items-end">
          <label className="space-y-1.5">
            <span className="text-xs font-medium text-text-secondary block">Output W (px)</span>
            <input
              type="number"
              min={MIN_CROP}
              value={cropW}
              onChange={(e) => setCropW(Math.max(MIN_CROP, parseIntSafe(e.target.value, MIN_CROP)))}
              className={cn(
                "w-24 bg-input border border-border rounded-md px-2 py-1.5 text-sm text-foreground font-mono",
                "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/30",
              )}
            />
          </label>
          <label className="space-y-1.5">
            <span className="text-xs font-medium text-text-secondary block">Output H (px)</span>
            <input
              type="number"
              min={MIN_CROP}
              value={cropH}
              onChange={(e) => setCropH(Math.max(MIN_CROP, parseIntSafe(e.target.value, MIN_CROP)))}
              className={cn(
                "w-24 bg-input border border-border rounded-md px-2 py-1.5 text-sm text-foreground font-mono",
                "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/30",
              )}
            />
          </label>
          <label className="space-y-1.5">
            <span className="text-xs font-medium text-text-secondary block">Image X</span>
            <input
              type="number"
              value={imgX}
              onChange={(e) => setImgX(parseIntSafe(e.target.value, 0))}
              className={cn(
                "w-20 bg-input border border-border rounded-md px-2 py-1.5 text-sm text-foreground font-mono",
                "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/30",
              )}
            />
          </label>
          <label className="space-y-1.5">
            <span className="text-xs font-medium text-text-secondary block">Image Y</span>
            <input
              type="number"
              value={imgY}
              onChange={(e) => setImgY(parseIntSafe(e.target.value, 0))}
              className={cn(
                "w-20 bg-input border border-border rounded-md px-2 py-1.5 text-sm text-foreground font-mono",
                "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/30",
              )}
            />
          </label>
          <span className="text-xs text-text-tertiary self-center font-mono pb-1">
            src: {imgW} × {imgH}
          </span>
        </div>

        <div className="flex flex-wrap items-center gap-3 pt-1 border-t border-border">
          <label className="space-y-1.5">
            <span className="text-xs font-medium text-text-secondary block">Padding (px)</span>
            <input
              type="number"
              min={0}
              value={padding}
              onChange={(e) => setPadding(Math.max(0, parseIntSafe(e.target.value, 0)))}
              className={cn(
                "w-20 bg-input border border-border rounded-md px-2 py-1.5 text-sm text-foreground font-mono",
                "focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/30",
              )}
            />
          </label>
          <div className="flex gap-2 self-end pb-0.5">
            <button
              onClick={autoCenter}
              className={cn(
                "flex items-center gap-1.5 px-3 py-2 rounded-md border border-border text-xs font-medium transition-colors",
                "bg-input text-text-secondary hover:text-primary hover:border-primary/60",
              )}
              title="Pan the image so non-transparent content is centered within the current output canvas"
            >
              <Crosshair className="w-3.5 h-3.5" /> Auto-center
            </button>
            <button
              onClick={fitToContent}
              className={cn(
                "flex items-center gap-1.5 px-3 py-2 rounded-md border border-border text-xs font-medium transition-colors",
                "bg-input text-text-secondary hover:text-primary hover:border-primary/60",
              )}
              title="Resize output to the non-transparent content bounding box + padding, then center"
            >
              <Maximize2 className="w-3.5 h-3.5" /> Fit to Content
            </button>
          </div>
          <p className="text-xs text-text-tertiary self-end pb-1 ml-auto">
            Drag inside the canvas to pan · drag handles to resize
          </p>
        </div>
      </div>

      {/* Editor + Preview */}
      <div className="grid grid-cols-[1fr_260px] gap-4">
        <div className="space-y-2">
          <p className="text-xs font-semibold text-text-tertiary uppercase tracking-wider">
            Editor
          </p>
          <div
            className="bg-card border border-border rounded-lg overflow-auto"
            style={{ maxHeight: "62vh" }}
          >
            <canvas
              ref={displayCanvasRef}
              className="block"
              style={{ maxWidth: "100%" }}
              onMouseDown={onMouseDown}
              onMouseMove={onMouseMove}
              onMouseUp={onMouseUp}
              onMouseLeave={onMouseUp}
            />
          </div>
        </div>

        <div className="space-y-2">
          <p className="text-xs font-semibold text-text-tertiary uppercase tracking-wider">
            Preview
          </p>
          <div className="bg-card border border-border rounded-lg overflow-hidden">
            {imageReady ? (
              <div style={CHECKERBOARD}>
                <canvas
                  ref={previewCanvasRef}
                  className="w-full block"
                  style={{ imageRendering: (cropW < 64 || cropH < 64) ? "pixelated" : "auto" }}
                />
              </div>
            ) : (
              <div className="aspect-square flex items-center justify-center">
                <ImageIcon className="w-10 h-10 text-text-tertiary opacity-20" />
              </div>
            )}
          </div>
          {imageReady && (
            <p className="text-xs text-text-tertiary text-center font-mono">
              {Math.round(cropW)} × {Math.round(cropH)} px
            </p>
          )}
        </div>
      </div>
    </div>
  );
}
