/**
 * usePinchZoom — Pinch zoom handlers extracted from useCanvasGestures
 *
 * Pure function returning `onPinchStart`, `onPinch`, `onPinchEnd` callbacks
 * for @use-gesture/react. Handles:
 * - Simultaneous pan during pinch (finger midpoint tracking)
 * - Zoom velocity sampling for inertia
 * - Snap-to-100% via snapZoom
 * - Inertia trigger on pinch end
 *
 * @since 1.3.0
 */

import { snapZoom } from './useInertia';
import type { VelocitySampler } from './inertia';
import type { Vec2 } from './types';

// =============================================================================
// Config
// =============================================================================

export interface PinchZoomConfig {
	/** Ref to the viewport DOM element */
	ref: React.RefObject<HTMLDivElement | null>;
	minZoom: number;
	maxZoom: number;
	enableZoom: boolean;
	/** Whether the user prefers reduced motion */
	reducedMotion: boolean;
	/** Current zoom value */
	zoom: number;
	/** Current pan value */
	pan: { x: number; y: number };
	/** Zoom setter (accepts value or updater function) */
	setZoom: ((zoom: number) => void) & ((fn: (prev: number) => number) => void);
	/** Pan setter (accepts value or updater function) */
	setPan: ((pan: { x: number; y: number }) => void) & ((fn: (prev: { x: number; y: number }) => { x: number; y: number }) => void);
	/** Inertia state */
	inertia: {
		cancelAll: () => void;
		pinchPrevOrigin: React.MutableRefObject<{ x: number; y: number } | null>;
		zoomSampler: VelocitySampler;
		lastPinchZoom: React.MutableRefObject<{ zoom: number; t: number } | null>;
		startZoomInertia: (
			velocity: number,
			origin: Vec2,
			currentZoom: number,
			minZoom: number,
			maxZoom: number,
			setZoom: (fn: (prev: number) => number) => void,
			setPan: (fn: (prev: Vec2) => Vec2) => void,
		) => void;
	};
}

// =============================================================================
// Factory
// =============================================================================

/**
 * Creates onPinchStart, onPinch, onPinchEnd handlers for use with @use-gesture/react.
 *
 * Extracted from useCanvasGestures to enable independent testing
 * and reuse in custom gesture compositions.
 */
export function createPinchHandlers(config: PinchZoomConfig) {
	const onPinchStart = ({ origin: [ox, oy] }: { origin: [number, number] }) => {
		if (!config.enableZoom || !config.ref.current) return true;
		config.inertia.cancelAll();
		config.inertia.pinchPrevOrigin.current = { x: ox, y: oy };
		config.inertia.zoomSampler.reset();
		config.inertia.lastPinchZoom.current = { zoom: config.zoom, t: performance.now() };
		return false;
	};

	const onPinch = ({ offset: [d], origin: [ox, oy], event, memo }: {
		offset: [number, number];
		origin: [number, number];
		event: Event;
		memo?: unknown;
	}) => {
		if (memo === true || !config.enableZoom || !config.ref.current) return;
		event.preventDefault();

		const rect = config.ref.current.getBoundingClientRect();
		const pinchX = ox - rect.left;
		const pinchY = oy - rect.top;

		// Simultaneous pan
		let pdx = 0;
		let pdy = 0;
		if (config.inertia.pinchPrevOrigin.current) {
			pdx = ox - config.inertia.pinchPrevOrigin.current.x;
			pdy = oy - config.inertia.pinchPrevOrigin.current.y;
		}
		config.inertia.pinchPrevOrigin.current = { x: ox, y: oy };

		const worldX = (pinchX - config.pan.x) / config.zoom;
		const worldY = (pinchY - config.pan.y) / config.zoom;
		const newZoom = snapZoom(Math.max(config.minZoom, Math.min(config.maxZoom, d)));

		// Track zoom velocity
		const now = performance.now();
		if (config.inertia.lastPinchZoom.current) {
			const dt = (now - config.inertia.lastPinchZoom.current.t) / 1000;
			if (dt > 0) {
				const v = (newZoom - config.inertia.lastPinchZoom.current.zoom) / dt;
				config.inertia.zoomSampler.sample(v, 0, now);
			}
		}
		config.inertia.lastPinchZoom.current = { zoom: newZoom, t: now };

		config.setZoom(newZoom);
		config.setPan({
			x: pinchX - worldX * newZoom + pdx,
			y: pinchY - worldY * newZoom + pdy,
		});
	};

	const onPinchEnd = () => {
		const avg = config.inertia.zoomSampler.average();
		const perFrameV = avg.x / 60;
		if (!config.reducedMotion && Math.abs(perFrameV) > 0.001 && config.ref.current) {
			const rect = config.ref.current.getBoundingClientRect();
			const origin = config.inertia.pinchPrevOrigin.current ?? { x: rect.width / 2, y: rect.height / 2 };
			config.inertia.startZoomInertia(perFrameV, {
				x: origin.x - rect.left,
				y: origin.y - rect.top,
			}, config.zoom, config.minZoom, config.maxZoom, config.setZoom, config.setPan);
		}
		config.inertia.pinchPrevOrigin.current = null;
		config.inertia.zoomSampler.reset();
		config.inertia.lastPinchZoom.current = null;
	};

	return { onPinchStart, onPinch, onPinchEnd };
}
