/**
 * useInertia — Pan & zoom inertia management
 *
 * Encapsulates the rAF animation loops, VelocitySamplers, and
 * pinch tracking refs used by useCanvasGestures. Returns stable
 * functions for starting/cancelling inertia and recording samples.
 */

import { useRef, useEffect } from 'react';
import { PanInertia, ZoomInertia, VelocitySampler } from './inertia';
import type { Vec2 } from './types';

// =============================================================================
// Constants
// =============================================================================

export const ZOOM_SNAP_THRESHOLD = 0.03;
export const INERTIA_MIN_SPEED = 2;
const VELOCITY_SAMPLE_COUNT = 5;

// =============================================================================
// Types
// =============================================================================

export interface InertiaAPI {
	/** Start pan inertia from a velocity vector */
	startPanInertia: (
		velocity: Vec2,
		setPan: (fn: (prev: Vec2) => Vec2) => void,
	) => void;

	/** Start zoom inertia from a per-frame velocity */
	startZoomInertia: (
		velocity: number,
		origin: Vec2,
		currentZoom: number,
		minZoom: number,
		maxZoom: number,
		setZoom: (fn: (prev: number) => number) => void,
		setPan: (fn: (prev: Vec2) => Vec2) => void,
	) => void;

	/** Cancel all running inertia animations */
	cancelAll: () => void;
	/** Cancel only pan inertia */
	cancelPan: () => void;
	/** Cancel only zoom inertia */
	cancelZoom: () => void;

	/** Pan velocity sampler */
	panSampler: VelocitySampler;
	/** Zoom velocity sampler */
	zoomSampler: VelocitySampler;

	/** Pinch origin tracking ref */
	pinchPrevOrigin: React.RefObject<Vec2 | null>;
	/** Last pinch zoom state for velocity calculation */
	lastPinchZoom: React.RefObject<{ zoom: number; t: number } | null>;
}

// =============================================================================
// Helpers
// =============================================================================

export function snapZoom(z: number): number {
	return Math.abs(z - 1.0) < ZOOM_SNAP_THRESHOLD ? 1.0 : z;
}

// =============================================================================
// Hook
// =============================================================================

export function useInertia(): InertiaAPI {
	const panInertiaRef = useRef<{ anim: number; engine: PanInertia } | null>(null);
	const zoomInertiaRef = useRef<{ anim: number; engine: ZoomInertia } | null>(null);
	const panSamplerRef = useRef(new VelocitySampler(VELOCITY_SAMPLE_COUNT));
	const zoomSamplerRef = useRef(new VelocitySampler(VELOCITY_SAMPLE_COUNT));
	const pinchPrevOrigin = useRef<Vec2 | null>(null);
	const lastPinchZoom = useRef<{ zoom: number; t: number } | null>(null);

	const cancelPan = () => {
		if (panInertiaRef.current) {
			cancelAnimationFrame(panInertiaRef.current.anim);
			panInertiaRef.current = null;
		}
	};

	const cancelZoom = () => {
		if (zoomInertiaRef.current) {
			cancelAnimationFrame(zoomInertiaRef.current.anim);
			zoomInertiaRef.current = null;
		}
	};

	const cancelAll = () => {
		cancelPan();
		cancelZoom();
	};

	const startPanInertia = (
		velocity: Vec2,
		setPan: (fn: (prev: Vec2) => Vec2) => void,
	) => {
		cancelPan();
		const engine = new PanInertia(velocity);
		const animate = () => {
			const tick = engine.tick();
			if (!tick) {
				panInertiaRef.current = null;
				return;
			}
			setPan((prev) => ({ x: prev.x + tick.x, y: prev.y + tick.y }));
			panInertiaRef.current!.anim = requestAnimationFrame(animate);
		};
		panInertiaRef.current = {
			anim: requestAnimationFrame(animate),
			engine,
		};
	};

	const startZoomInertia = (
		velocity: number,
		origin: Vec2,
		currentZoom: number,
		minZoom: number,
		maxZoom: number,
		setZoom: (fn: (prev: number) => number) => void,
		setPan: (fn: (prev: Vec2) => Vec2) => void,
	) => {
		cancelZoom();
		const engine = new ZoomInertia(velocity, origin);
		const animate = () => {
			const tick = engine.tick(currentZoom);
			if (!tick) {
				zoomInertiaRef.current = null;
				return;
			}
			setZoom((prevZoom) => {
				const newZoom = Math.max(minZoom, Math.min(maxZoom, prevZoom + tick.delta));
				setPan((prevPan) => {
					const worldX = (tick.origin.x - prevPan.x) / prevZoom;
					const worldY = (tick.origin.y - prevPan.y) / prevZoom;
					return {
						x: tick.origin.x - worldX * newZoom,
						y: tick.origin.y - worldY * newZoom,
					};
				});
				return snapZoom(newZoom);
			});
			zoomInertiaRef.current!.anim = requestAnimationFrame(animate);
		};
		zoomInertiaRef.current = {
			anim: requestAnimationFrame(animate),
			engine,
		};
	};

	// Cleanup on unmount
	useEffect(() => cancelAll, []);

	return {
		startPanInertia,
		startZoomInertia,
		cancelAll,
		cancelPan,
		cancelZoom,
		panSampler: panSamplerRef.current,
		zoomSampler: zoomSamplerRef.current,
		pinchPrevOrigin,
		lastPinchZoom,
	};
}
