"use client";

import { useCallback, useEffect, useRef, useState } from "react";

export type CropBox = { x: number; y: number; w: number; h: number };

const HANDLES = ["nw", "ne", "sw", "se"] as const;
type Handle = (typeof HANDLES)[number];

/**
 * A crop box you drag and resize over the photo, the way every social app
 * does it — corners to resize, the middle to move, the area outside dimmed.
 *
 * Everything is a fraction of the photo rather than pixels, so the box
 * survives the photo being displayed at any size.
 */
export function PhotoCrop({
  box,
  ratio,
  onChange,
}: {
  box: CropBox;
  /** Locks the shape when a preset is chosen; free when null. */
  ratio: number | null;
  onChange: (box: CropBox) => void;
}) {
  const frameRef = useRef<HTMLDivElement | null>(null);
  const drag = useRef<{
    mode: "move" | Handle;
    startX: number;
    startY: number;
    start: CropBox;
  } | null>(null);
  const [active, setActive] = useState(false);

  const begin = (e: React.PointerEvent, mode: "move" | Handle) => {
    e.preventDefault();
    e.stopPropagation();
    const frame = frameRef.current?.getBoundingClientRect();
    if (!frame) return;

    drag.current = {
      mode,
      startX: (e.clientX - frame.left) / frame.width,
      startY: (e.clientY - frame.top) / frame.height,
      start: { ...box },
    };
    setActive(true);
  };

  const move = useCallback(
    (e: PointerEvent) => {
      const d = drag.current;
      const frame = frameRef.current?.getBoundingClientRect();
      if (!d || !frame) return;

      const px = (e.clientX - frame.left) / frame.width;
      const py = (e.clientY - frame.top) / frame.height;
      const dx = px - d.startX;
      const dy = py - d.startY;

      // The smallest useful crop, so the box can't be dragged to nothing.
      const MIN = 0.12;
      let next = { ...d.start };

      if (d.mode === "move") {
        next.x = Math.min(1 - d.start.w, Math.max(0, d.start.x + dx));
        next.y = Math.min(1 - d.start.h, Math.max(0, d.start.y + dy));
      } else {
        // Which corner is being pulled decides which edges move.
        const west = d.mode === "nw" || d.mode === "sw";
        const north = d.mode === "nw" || d.mode === "ne";

        if (west) {
          const right = d.start.x + d.start.w;
          next.x = Math.min(right - MIN, Math.max(0, d.start.x + dx));
          next.w = right - next.x;
        } else {
          next.w = Math.min(1 - d.start.x, Math.max(MIN, d.start.w + dx));
        }

        if (north) {
          const bottom = d.start.y + d.start.h;
          next.y = Math.min(bottom - MIN, Math.max(0, d.start.y + dy));
          next.h = bottom - next.y;
        } else {
          next.h = Math.min(1 - d.start.y, Math.max(MIN, d.start.h + dy));
        }

        // A locked shape follows the width, adjusting the height to match.
        if (ratio) {
          const frameRatio = frame.width / frame.height;
          const wanted = next.w / (ratio / frameRatio);
          if (north) next.y = Math.max(0, d.start.y + d.start.h - wanted);
          next.h = Math.min(1 - next.y, wanted);
        }
      }

      onChange(next);
    },
    [box, ratio, onChange]
  );

  useEffect(() => {
    const up = () => {
      drag.current = null;
      setActive(false);
    };
    window.addEventListener("pointermove", move);
    window.addEventListener("pointerup", up);
    return () => {
      window.removeEventListener("pointermove", move);
      window.removeEventListener("pointerup", up);
    };
  }, [move]);

  return (
    <div ref={frameRef} className="cr-frame">
      {/* Everything outside the box is dimmed, so the crop reads at a glance. */}
      <div className="cr-shade" style={{ height: `${box.y * 100}%`, top: 0 }} />
      <div
        className="cr-shade"
        style={{ top: `${(box.y + box.h) * 100}%`, bottom: 0 }}
      />
      <div
        className="cr-shade"
        style={{
          top: `${box.y * 100}%`,
          height: `${box.h * 100}%`,
          width: `${box.x * 100}%`,
        }}
      />
      <div
        className="cr-shade"
        style={{
          top: `${box.y * 100}%`,
          height: `${box.h * 100}%`,
          left: `${(box.x + box.w) * 100}%`,
          right: 0,
        }}
      />

      <div
        className={`cr-box ${active ? "on" : ""}`}
        onPointerDown={(e) => begin(e, "move")}
        style={{
          left: `${box.x * 100}%`,
          top: `${box.y * 100}%`,
          width: `${box.w * 100}%`,
          height: `${box.h * 100}%`,
        }}
      >
        {/* Thirds, which is how people frame a shot. */}
        <span className="cr-third v" style={{ left: "33.33%" }} />
        <span className="cr-third v" style={{ left: "66.66%" }} />
        <span className="cr-third h" style={{ top: "33.33%" }} />
        <span className="cr-third h" style={{ top: "66.66%" }} />

        {HANDLES.map((h) => (
          <span
            key={h}
            className={`cr-handle ${h}`}
            onPointerDown={(e) => begin(e, h)}
          />
        ))}
      </div>
    </div>
  );
}

/** A box centred in the photo at the given shape. */
export function boxForRatio(ratio: number | null, frameRatio: number): CropBox {
  if (!ratio) return { x: 0, y: 0, w: 1, h: 1 };

  // Worked out against the displayed frame so the box looks like the shape
  // it claims to be.
  const wanted = ratio / frameRatio;
  if (wanted >= 1) {
    const h = 1 / wanted;
    return { x: 0, y: (1 - h) / 2, w: 1, h };
  }
  return { x: (1 - wanted) / 2, y: 0, w: wanted, h: 1 };
}
