import { type ReactNode, useEffect, useRef } from "react";
import { ZOOM_STEP } from "./useZoomPan.js";
import { useZoom } from "./ZoomContext.js";

interface Props {
	children: ReactNode;
	/**
	 * Whether single-pointer drag pans the canvas. Pass false in tool mode so
	 * 1-finger events are owned by the tool; 2-finger pinch/drag still works.
	 * Defaults to true.
	 */
	panEnabled?: boolean;
}

/**
 * Fills the canvas area, applies zoom/pan transform to its children, and
 * captures pointer events for gestures.
 *
 * • Ctrl/Cmd + scroll: zoom anchored at cursor (desktop)
 * • 2-finger pinch + translate: simultaneous zoom + pan anchored at midpoint
 * • 1-finger drag (when panEnabled): pan
 */
export function ZoomCanvas({ children, panEnabled = true }: Props) {
	let { zoom, panX, panY, pan, panAndZoomAtPoint, zoomByDeltaAtPoint } = useZoom();

	let containerRef = useRef<HTMLDivElement>(null);
	let pointers = useRef(new Map<number, { x: number; y: number }>());
	let lastPinchDist = useRef<number | null>(null);
	let lastPinchMid = useRef<{ x: number; y: number } | null>(null);

	// Stable refs so the DOM wheel listener never needs re-registration.
	let panRef = useRef(pan);
	let panAndZoomAtPointRef = useRef(panAndZoomAtPoint);
	let zoomByDeltaAtPointRef = useRef(zoomByDeltaAtPoint);
	panRef.current = pan;
	panAndZoomAtPointRef.current = panAndZoomAtPoint;
	zoomByDeltaAtPointRef.current = zoomByDeltaAtPoint;

	function focalFromClient(clientX: number, clientY: number) {
		let rect = containerRef.current?.getBoundingClientRect();
		if (!rect) return { fx: 0, fy: 0 };
		return { fx: clientX - rect.left - rect.width / 2, fy: clientY - rect.top - rect.height / 2 };
	}

	useEffect(() => {
		let el = containerRef.current;
		if (!el) return;

		function onWheel(e: WheelEvent) {
			if (!e.ctrlKey && !e.metaKey) return;
			e.preventDefault();
			let { fx, fy } = focalFromClient(e.clientX, e.clientY);
			zoomByDeltaAtPointRef.current(e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP, fx, fy);
		}

		el.addEventListener("wheel", onWheel, { passive: false });
		return () => el.removeEventListener("wheel", onWheel);
	}, []);

	function onPointerDown(e: React.PointerEvent) {
		// Only capture the pointer for panning. In tool mode (panEnabled=false) the inner
		// tool elements (DragHandle, redact SVG) call setPointerCapture themselves after
		// this event bubbles up — don't override their capture.
		if (panEnabled) {
			e.currentTarget.setPointerCapture(e.pointerId);
		}
		pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
		lastPinchDist.current = null;
		lastPinchMid.current = null;
	}

	function onPointerMove(e: React.PointerEvent) {
		// Ignore hover — only process pointers that went through onPointerDown.
		if (!pointers.current.has(e.pointerId)) return;

		let prev = pointers.current.get(e.pointerId);
		pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });

		if (pointers.current.size === 2) {
			let [a, b] = [...pointers.current.values()];
			let dist = Math.hypot(a.x - b.x, a.y - b.y);
			let midX = (a.x + b.x) / 2;
			let midY = (a.y + b.y) / 2;

			if (lastPinchDist.current !== null && lastPinchDist.current > 0) {
				let { fx, fy } = focalFromClient(midX, midY);
				let scaleFactor = dist / lastPinchDist.current;
				// Translate from midpoint movement + zoom from pinch — both in one state update.
				let dx = lastPinchMid.current ? midX - lastPinchMid.current.x : 0;
				let dy = lastPinchMid.current ? midY - lastPinchMid.current.y : 0;
				panAndZoomAtPointRef.current(dx, dy, scaleFactor, fx, fy);
			}

			lastPinchDist.current = dist;
			lastPinchMid.current = { x: midX, y: midY };
		} else if (pointers.current.size === 1 && prev && panEnabled) {
			panRef.current(e.clientX - prev.x, e.clientY - prev.y);
		}
	}

	function onPointerUp(e: React.PointerEvent) {
		pointers.current.delete(e.pointerId);
		if (pointers.current.size < 2) {
			lastPinchDist.current = null;
			lastPinchMid.current = null;
		}
	}

	return (
		<div
			ref={containerRef}
			className="zoom-canvas-root"
			onPointerDown={onPointerDown}
			onPointerMove={onPointerMove}
			onPointerUp={onPointerUp}
			onPointerCancel={onPointerUp}
		>
			<div className="zoom-canvas-inner" style={{ transform: `translate(${panX}px, ${panY}px) scale(${zoom})` }}>
				{children}
			</div>
		</div>
	);
}
