import {
  useCallback,
  useEffect,
  useRef,
  useState,
  type PointerEvent as ReactPointerEvent,
} from "react";
import type { ToolUiProps } from "../../core/plugins/plugin-types";
import { Button } from "../../components/ui/Button";
import { Card } from "../../components/ui/Card";
import { Dropzone } from "../../components/ui/Dropzone";
import { Select } from "../../components/ui/Select";
import { Slider } from "../../components/ui/Slider";
import { ToolProgress } from "../../components/tool/ToolProgress";
import { useToolExecution } from "../../components/tool/useToolExecution";
import { disposePersistentWorker } from "../../core/plugins/plugin-runner";
import { clearImglyCache } from "./imgly-cache";
import type { BackgroundRemoverOptions } from "./remove";

type EditMode = "automatic" | "select-area";
type EditorAction = "sample" | "remove" | "restore";

interface Point {
  x: number;
  y: number;
}

interface MaskStroke {
  type: "stroke";
  action: Exclude<EditorAction, "sample">;
  points: Point[];
  size: number;
}

interface SampleSelection {
  type: "sample";
  point: Point;
  tolerance: number;
}

type MaskOperation = MaskStroke | SampleSelection;

function drawStrokePath(
  context: CanvasRenderingContext2D,
  stroke: MaskStroke,
  width: number,
  height: number
): void {
  const points = stroke.points;
  if (points.length === 0) return;

  const scale = Math.min(width, height);
  context.lineWidth = stroke.size * scale;
  context.lineCap = "round";
  context.lineJoin = "round";
  context.beginPath();
  context.moveTo(points[0].x * width, points[0].y * height);

  if (points.length === 1) {
    context.lineTo(points[0].x * width + 0.01, points[0].y * height);
  } else {
    for (const point of points.slice(1)) {
      context.lineTo(point.x * width, point.y * height);
    }
  }
  context.stroke();
}

function applyStroke(
  output: HTMLCanvasElement,
  source: HTMLCanvasElement,
  scratch: HTMLCanvasElement,
  stroke: MaskStroke
): void {
  const outputContext = output.getContext("2d");
  const scratchContext = scratch.getContext("2d");
  if (!outputContext || !scratchContext) return;

  if (stroke.action === "remove") {
    outputContext.save();
    outputContext.globalCompositeOperation = "destination-out";
    outputContext.strokeStyle = "#000";
    drawStrokePath(outputContext, stroke, output.width, output.height);
    outputContext.restore();
    return;
  }

  scratchContext.clearRect(0, 0, scratch.width, scratch.height);
  scratchContext.globalCompositeOperation = "source-over";
  scratchContext.drawImage(source, 0, 0);
  scratchContext.globalCompositeOperation = "destination-in";
  scratchContext.strokeStyle = "#000";
  drawStrokePath(scratchContext, stroke, scratch.width, scratch.height);
  scratchContext.globalCompositeOperation = "source-over";

  outputContext.drawImage(scratch, 0, 0);
}

function applySampleSelection(
  output: HTMLCanvasElement,
  source: HTMLCanvasElement,
  selection: SampleSelection
): void {
  const outputContext = output.getContext("2d", { willReadFrequently: true });
  const sourceContext = source.getContext("2d", { willReadFrequently: true });
  if (!outputContext || !sourceContext) return;

  const { width, height } = output;
  const sourceData = sourceContext.getImageData(0, 0, width, height);
  const outputData = outputContext.getImageData(0, 0, width, height);
  const startX = Math.min(width - 1, Math.floor(selection.point.x * width));
  const startY = Math.min(height - 1, Math.floor(selection.point.y * height));
  const startIndex = startY * width + startX;
  const startOffset = startIndex * 4;
  const targetR = sourceData.data[startOffset];
  const targetG = sourceData.data[startOffset + 1];
  const targetB = sourceData.data[startOffset + 2];
  const threshold = (selection.tolerance / 100) * Math.sqrt(3 * 255 * 255);
  const thresholdSquared = threshold * threshold;
  const visited = new Uint8Array(width * height);
  const stack = new Uint32Array(width * height);
  let stackSize = 0;
  visited[startIndex] = 1;
  stack[stackSize++] = startIndex;

  while (stackSize > 0) {
    const index = stack[--stackSize];
    const offset = index * 4;
    const deltaR = sourceData.data[offset] - targetR;
    const deltaG = sourceData.data[offset + 1] - targetG;
    const deltaB = sourceData.data[offset + 2] - targetB;
    const distanceSquared =
      deltaR * deltaR + deltaG * deltaG + deltaB * deltaB;
    if (distanceSquared > thresholdSquared) continue;

    outputData.data[offset + 3] = 0;
    const x = index % width;
    const y = Math.floor(index / width);
    const neighbors = [
      x > 0 ? index - 1 : -1,
      x + 1 < width ? index + 1 : -1,
      y > 0 ? index - width : -1,
      y + 1 < height ? index + width : -1,
    ];
    for (const neighbor of neighbors) {
      if (neighbor >= 0 && !visited[neighbor]) {
        visited[neighbor] = 1;
        stack[stackSize++] = neighbor;
      }
    }
  }

  outputContext.putImageData(outputData, 0, 0);
}

function downloadBlob(blob: Blob, name: string): void {
  const url = URL.createObjectURL(blob);
  const anchor = document.createElement("a");
  anchor.href = url;
  anchor.download = name;
  document.body.appendChild(anchor);
  anchor.click();
  anchor.remove();
  URL.revokeObjectURL(url);
}

export default function BackgroundRemoverUi({
  plugin,
}: ToolUiProps<BackgroundRemoverOptions>) {
  const [files, setFiles] = useState<File[]>([]);
  const [sourceUrl, setSourceUrl] = useState("");
  const [resultUrl, setResultUrl] = useState("");
  const [editMode, setEditMode] = useState<EditMode>("automatic");
  const [quality, setQuality] =
    useState<BackgroundRemoverOptions["quality"]>("small");
  const [useGpu, setUseGpu] = useState(true);
  const [editorAction, setEditorAction] = useState<EditorAction>("sample");
  const [tolerance, setTolerance] = useState(14);
  const [brushSize, setBrushSize] = useState(4);
  const [operations, setOperations] = useState<MaskOperation[]>([]);
  const [isDrawing, setIsDrawing] = useState(false);
  const [warning, setWarning] = useState<string | null>(null);
  const [pendingOptions, setPendingOptions] =
    useState<BackgroundRemoverOptions | null>(null);
  const [cacheMessage, setCacheMessage] = useState("");
  const [isClearingCache, setIsClearingCache] = useState(false);

  const canvasRef = useRef<HTMLCanvasElement>(null);
  const sourceCanvasRef = useRef<HTMLCanvasElement | null>(null);
  const baseCanvasRef = useRef<HTMLCanvasElement | null>(null);
  const scratchCanvasRef = useRef<HTMLCanvasElement | null>(null);
  const activeStrokeRef = useRef<MaskStroke | null>(null);
  const resultUrlRef = useRef("");

  const { run, result, isExecuting, progress, error, reset } =
    useToolExecution(plugin);

  const resultFile =
    result?.type === "files" && result.files.length > 0
      ? result.files[0]
      : null;

  useEffect(() => {
    return () => {
      if (sourceUrl) URL.revokeObjectURL(sourceUrl);
    };
  }, [sourceUrl]);

  useEffect(
    () => () => {
      if (resultUrlRef.current) URL.revokeObjectURL(resultUrlRef.current);
    },
    []
  );

  const renderOperations = useCallback((nextOperations: MaskOperation[]) => {
    const output = canvasRef.current;
    const base = baseCanvasRef.current;
    const source = sourceCanvasRef.current;
    const scratch = scratchCanvasRef.current;
    if (!output || !base || !source || !scratch) return;

    const context = output.getContext("2d");
    if (!context) return;
    context.clearRect(0, 0, output.width, output.height);
    context.drawImage(base, 0, 0);
    for (const operation of nextOperations) {
      if (operation.type === "sample") {
        applySampleSelection(output, source, operation);
      } else {
        applyStroke(output, source, scratch, operation);
      }
    }
  }, []);

  useEffect(() => {
    if (editMode !== "select-area" || files.length === 0) return;

    let cancelled = false;
    const initialize = async () => {
      const sourceBitmap = await createImageBitmap(files[0]);
      if (cancelled) {
        sourceBitmap.close();
        return;
      }

      const width = sourceBitmap.width;
      const height = sourceBitmap.height;
      const sourceCanvas = document.createElement("canvas");
      const baseCanvas = document.createElement("canvas");
      const scratchCanvas = document.createElement("canvas");
      for (const canvas of [sourceCanvas, baseCanvas, scratchCanvas]) {
        canvas.width = width;
        canvas.height = height;
      }

      sourceCanvas.getContext("2d")?.drawImage(sourceBitmap, 0, 0, width, height);
      baseCanvas.getContext("2d")?.drawImage(sourceBitmap, 0, 0, width, height);
      sourceCanvasRef.current = sourceCanvas;
      baseCanvasRef.current = baseCanvas;
      scratchCanvasRef.current = scratchCanvas;

      const output = canvasRef.current;
      if (output) {
        output.width = width;
        output.height = height;
        output.getContext("2d")?.drawImage(baseCanvas, 0, 0);
      }

      sourceBitmap.close();
      setOperations([]);
    };

    void initialize();
    return () => {
      cancelled = true;
    };
  }, [editMode, files]);

  const clearResult = useCallback(() => {
    if (resultUrlRef.current) {
      URL.revokeObjectURL(resultUrlRef.current);
      resultUrlRef.current = "";
    }
    setResultUrl("");
    setOperations([]);
    activeStrokeRef.current = null;
    reset();
  }, [reset]);

  const handleFilesDrop = useCallback(
    (nextFiles: File[]) => {
      setFiles(nextFiles);
      setSourceUrl(
        nextFiles.length > 0 ? URL.createObjectURL(nextFiles[0]) : ""
      );
      clearResult();
    },
    [clearResult]
  );

  const execute = useCallback(
    async (options: BackgroundRemoverOptions) => {
      if (files.length === 0) return;
      const nextResult = await run({ files }, options);
      if (nextResult?.type === "files" && nextResult.files.length > 0) {
        if (resultUrlRef.current) URL.revokeObjectURL(resultUrlRef.current);
        const url = URL.createObjectURL(nextResult.files[0].blob);
        resultUrlRef.current = url;
        setResultUrl(url);
      }
    },
    [files, run]
  );

  const handleRun = useCallback(async () => {
    const options = { quality, useGpu };
    if (plugin.getRunWarning) {
      const nextWarning = await plugin.getRunWarning({ files }, options);
      if (nextWarning) {
        setPendingOptions(options);
        setWarning(nextWarning);
        return;
      }
    }
    await execute(options);
  }, [execute, files, plugin, quality, useGpu]);

  const pointFromEvent = (
    event: ReactPointerEvent<HTMLCanvasElement>
  ): Point => {
    const rect = event.currentTarget.getBoundingClientRect();
    return {
      x: Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width)),
      y: Math.max(0, Math.min(1, (event.clientY - rect.top) / rect.height)),
    };
  };

  const handlePointerDown = (event: ReactPointerEvent<HTMLCanvasElement>) => {
    if (!canvasRef.current || !sourceCanvasRef.current || !scratchCanvasRef.current) {
      return;
    }
    event.preventDefault();
    if (editorAction === "sample") {
      const selection: SampleSelection = {
        type: "sample",
        point: pointFromEvent(event),
        tolerance,
      };
      applySampleSelection(
        canvasRef.current,
        sourceCanvasRef.current,
        selection
      );
      setOperations((current) => [...current, selection]);
      return;
    }

    event.currentTarget.setPointerCapture(event.pointerId);
    const stroke: MaskStroke = {
      type: "stroke",
      action: editorAction,
      points: [pointFromEvent(event)],
      size: brushSize / 100,
    };
    activeStrokeRef.current = stroke;
    setIsDrawing(true);
    applyStroke(
      canvasRef.current,
      sourceCanvasRef.current,
      scratchCanvasRef.current,
      stroke
    );
  };

  const handlePointerMove = (event: ReactPointerEvent<HTMLCanvasElement>) => {
    const active = activeStrokeRef.current;
    const output = canvasRef.current;
    const source = sourceCanvasRef.current;
    const scratch = scratchCanvasRef.current;
    if (!isDrawing || !active || !output || !source || !scratch) return;

    const point = pointFromEvent(event);
    const segment: MaskStroke = {
      ...active,
      points: [active.points.at(-1) ?? point, point],
    };
    active.points.push(point);
    applyStroke(output, source, scratch, segment);
  };

  const finishStroke = (event: ReactPointerEvent<HTMLCanvasElement>) => {
    const active = activeStrokeRef.current;
    if (!active) return;
    if (event.currentTarget.hasPointerCapture(event.pointerId)) {
      event.currentTarget.releasePointerCapture(event.pointerId);
    }
    activeStrokeRef.current = null;
    setIsDrawing(false);
    setOperations((current) => [...current, active]);
  };

  const handleUndo = () => {
    setOperations((current) => {
      const next = current.slice(0, -1);
      renderOperations(next);
      return next;
    });
  };

  const handleResetMask = () => {
    setOperations([]);
    renderOperations([]);
  };

  const handleDownloadEdited = async () => {
    const canvas = canvasRef.current;
    if (!canvas || files.length === 0) return;
    const blob = await new Promise<Blob | null>((resolve) =>
      canvas.toBlob(resolve, "image/png")
    );
    if (blob) {
      const name =
        files[0].name.replace(/\.[^./\\]+$/, "") + "_manual-no-bg.png";
      downloadBlob(blob, name);
    }
  };

  const handleModeChange = (nextMode: EditMode) => {
    setEditMode(nextMode);
    clearResult();
  };

  const handleClearModels = async () => {
    setIsClearingCache(true);
    setCacheMessage("");
    try {
      const deleted = await clearImglyCache();
      const released = disposePersistentWorker(plugin.manifest.id);
      setCacheMessage(
        deleted || released
          ? "Cached models cleared. Automatic mode will download them again."
          : "No cached models were found."
      );
    } catch {
      setCacheMessage("Could not clear cached models.");
    } finally {
      setIsClearingCache(false);
    }
  };

  return (
    <div className="mx-auto max-w-6xl p-[18px] md:p-[28px]">
      <div className="grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_300px]">
        <div className="space-y-6">
          {files.length === 0 ? (
            <Card title="Upload Image">
              <Dropzone
                accept="image/jpeg,image/png,image/webp"
                multiple={false}
                maxSizeMb={25}
                onFilesDrop={handleFilesDrop}
              />
            </Card>
          ) : (
            <Card
              title={
                editMode === "select-area"
                  ? "Select Background"
                  : resultFile
                    ? "Result"
                    : "Source Image"
              }
            >
              <div
                className="flex min-h-[320px] items-center justify-center overflow-hidden rounded-xl border border-[var(--p-border)]"
                style={{
                  backgroundColor: "#fff",
                  backgroundImage:
                    "linear-gradient(45deg,#ddd 25%,transparent 25%),linear-gradient(-45deg,#ddd 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#ddd 75%),linear-gradient(-45deg,transparent 75%,#ddd 75%)",
                  backgroundPosition: "0 0,0 8px,8px -8px,-8px 0",
                  backgroundSize: "16px 16px",
                }}
              >
                {editMode === "select-area" ? (
                  <canvas
                    ref={canvasRef}
                    onPointerDown={handlePointerDown}
                    onPointerMove={handlePointerMove}
                    onPointerUp={finishStroke}
                    onPointerCancel={finishStroke}
                    className="max-h-[65vh] max-w-full cursor-crosshair object-contain"
                    style={{ touchAction: "none" }}
                    aria-label="Background mask editor"
                  />
                ) : (
                  <img
                    src={resultUrl || sourceUrl}
                    alt={resultFile ? "Background removed" : "Source"}
                    className="max-h-[65vh] max-w-full object-contain"
                  />
                )}
              </div>
              {editMode === "select-area" && (
                <p className="mb-0 mt-3 text-sm text-[var(--p-muted)]">
                  Choose Select background and click a representative point.
                  Only the connected region with a similar color is removed.
                </p>
              )}
            </Card>
          )}

          {isExecuting && (
            <ToolProgress
              percentage={progress.percentage ?? 0}
              message={progress.message}
            />
          )}

          {error && (
            <Card className="border-red-300">
              <p className="m-0 text-sm text-red-700">{error}</p>
            </Card>
          )}
        </div>

        <div className="space-y-6">
          <Card title="Mode">
            <div className="grid grid-cols-2 gap-2">
              <Button
                variant={editMode === "automatic" ? "primary" : "secondary"}
                onClick={() => handleModeChange("automatic")}
                disabled={isExecuting}
              >
                Automatic
              </Button>
              <Button
                variant={editMode === "select-area" ? "primary" : "secondary"}
                onClick={() => handleModeChange("select-area")}
                disabled={isExecuting}
              >
                Select area
              </Button>
            </div>
            <p className="mb-0 mt-3 text-xs leading-relaxed text-[var(--p-muted)]">
              Select area uses a sampled background color and does not run the
              AI model.
            </p>
          </Card>

          <Card title="Settings">
            <div className="space-y-5">
              {editMode === "automatic" ? (
                <>
                  <Select
                    label="Model"
                    value={quality}
                    disabled={isExecuting}
                    onChange={(event) => {
                      setQuality(
                        event.target.value as BackgroundRemoverOptions["quality"]
                      );
                      clearResult();
                    }}
                    options={[
                      { label: "Smallest download (quantized)", value: "small" },
                      { label: "Balanced (FP16)", value: "medium" },
                      { label: "Best quality (FP32)", value: "large" },
                    ]}
                  />

                  <label className="flex items-center gap-2 text-sm text-[var(--p-text)]">
                    <input
                      type="checkbox"
                      checked={useGpu}
                      disabled={isExecuting}
                      onChange={(event) => {
                        setUseGpu(event.target.checked);
                        clearResult();
                      }}
                      className="h-4 w-4 accent-[var(--p-accent)]"
                    />
                    Use GPU acceleration
                  </label>
                </>
              ) : (
                <div className="space-y-4">
                  <div className="grid grid-cols-3 gap-2">
                    <Button
                      variant={editorAction === "sample" ? "primary" : "secondary"}
                      onClick={() => setEditorAction("sample")}
                    >
                      Select
                    </Button>
                    <Button
                      variant={editorAction === "remove" ? "primary" : "secondary"}
                      onClick={() => setEditorAction("remove")}
                    >
                      Remove
                    </Button>
                    <Button
                      variant={editorAction === "restore" ? "primary" : "secondary"}
                      onClick={() => setEditorAction("restore")}
                    >
                      Restore
                    </Button>
                  </div>

                  {editorAction === "sample" ? (
                    <Slider
                      label={`Color tolerance: ${tolerance}%`}
                      min={1}
                      max={40}
                      value={tolerance}
                      onChange={(event) =>
                        setTolerance(Number(event.target.value))
                      }
                    />
                  ) : (
                    <Slider
                      label={`Brush size: ${brushSize}%`}
                      min={1}
                      max={20}
                      value={brushSize}
                      onChange={(event) =>
                        setBrushSize(Number(event.target.value))
                      }
                    />
                  )}

                  <div className="grid grid-cols-2 gap-2">
                    <Button
                      variant="secondary"
                      disabled={operations.length === 0}
                      onClick={handleUndo}
                    >
                      Undo
                    </Button>
                    <Button
                      variant="secondary"
                      disabled={operations.length === 0}
                      onClick={handleResetMask}
                    >
                      Reset
                    </Button>
                  </div>
                </div>
              )}

              <div className="space-y-2 border-t border-[var(--p-border)] pt-4">
                {editMode === "select-area" ? (
                  <Button
                    className="w-full"
                    disabled={files.length === 0}
                    onClick={handleDownloadEdited}
                  >
                    Download selected result
                  </Button>
                ) : !resultFile ? (
                  <Button
                    className="w-full"
                    disabled={files.length === 0 || isExecuting}
                    onClick={handleRun}
                  >
                    {isExecuting ? "Processing..." : "Remove background"}
                  </Button>
                ) : (
                  <Button
                    className="w-full"
                    onClick={() => downloadBlob(resultFile.blob, resultFile.name)}
                  >
                    Download PNG
                  </Button>
                )}
                {files.length > 0 && (
                  <Button
                    variant="secondary"
                    className="w-full"
                    disabled={isExecuting}
                    onClick={() => {
                      setFiles([]);
                      setSourceUrl("");
                      clearResult();
                    }}
                  >
                    Choose another image
                  </Button>
                )}
              </div>

              <div className="space-y-2 border-t border-[var(--p-border)] pt-4">
                <Button
                  variant="danger"
                  className="w-full"
                  disabled={isExecuting || isClearingCache}
                  onClick={handleClearModels}
                >
                  {isClearingCache ? "Clearing..." : "Clear models"}
                </Button>
                <p className="m-0 text-xs leading-relaxed text-[var(--p-muted)]">
                  Removes downloaded AI models and ONNX runtime files from this
                  browser.
                </p>
                {cacheMessage && (
                  <p
                    role="status"
                    className="m-0 text-xs font-medium text-[var(--p-text)]"
                  >
                    {cacheMessage}
                  </p>
                )}
              </div>
            </div>
          </Card>
        </div>
      </div>

      {warning && pendingOptions && (
        <div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/50 p-4">
          <div
            role="alertdialog"
            aria-modal="true"
            className="w-full max-w-md rounded-2xl border border-[var(--p-border)] bg-[var(--p-surface)] p-6"
          >
            <h2 className="m-0 text-lg font-semibold text-[var(--p-text)]">
              One-time download
            </h2>
            <p className="my-4 text-sm leading-relaxed text-[var(--p-muted)]">
              {warning}
            </p>
            <div className="flex justify-end gap-2">
              <Button
                variant="secondary"
                onClick={() => {
                  setWarning(null);
                  setPendingOptions(null);
                }}
              >
                Cancel
              </Button>
              <Button
                onClick={() => {
                  const options = pendingOptions;
                  setWarning(null);
                  setPendingOptions(null);
                  void execute(options);
                }}
              >
                Download &amp; run
              </Button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
