/**
 * useWheelZoom — Wheel zoom handler extracted from useCanvasGestures
 *
 * Pure function returning an `onWheel` callback for @use-gesture/react.
 * Handles:
 * - Zoom calculation with configurable sensitivity
 * - Snap-to-100% via snapZoom
 * - Min/max bounds clamping
 * - Scroll passthrough for scrollable content inside selected nodes
 *
 * @since 1.3.0
 */

import type { NodePosition } from '../core/types';
import { snapZoom } from './useInertia';

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

export interface WheelZoomConfig {
	/** Ref to the viewport DOM element */
	ref: React.RefObject<HTMLDivElement | null>;
	minZoom: number;
	maxZoom: number;
	zoomSensitivity: number;
	enableZoom: boolean;
	/** Current zoom value */
	zoom: number;
	/** Current pan value */
	pan: { x: number; y: number };
	/** Currently selected node IDs */
	selectedNodeIds: Set<string>;
	/** Zoom setter */
	setZoom: (zoom: number) => void;
	/** Pan setter */
	setPan: (pan: { x: number; y: number }) => void;
}

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

/**
 * Creates an onWheel handler for use with @use-gesture/react.
 *
 * Extracted from useCanvasGestures to enable independent testing
 * and reuse in custom gesture compositions.
 */
export function createWheelHandler(config: WheelZoomConfig) {
	return ({ event, pinching, delta: [, dy], memo }: {
		event: WheelEvent;
		pinching?: boolean;
		delta: [number, number];
		memo?: unknown;
	}) => {
		if (memo === true || pinching || !config.enableZoom || !config.ref.current) return;

		// Check scrollable element inside selected node
		const target = event.target as HTMLElement;
		const noDrag = target.closest('[data-no-drag="true"]');
		if (noDrag) {
			const draggableNode = target.closest('[data-draggable-node="true"]');
			if (draggableNode) {
				const nodeId = draggableNode.getAttribute('data-node-id');
				if (nodeId && config.selectedNodeIds.has(nodeId)) {
					let el: HTMLElement | null = target;
					while (el && noDrag.contains(el)) {
						if (el.scrollHeight > el.clientHeight) {
							const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
							const atTop = el.scrollTop <= 1;
							if ((!atBottom && dy > 0) || (!atTop && dy < 0) || (atBottom && dy < 0) || (atTop && dy > 0)) {
								return;
							}
						}
						el = el.parentElement;
					}
				}
			}
		}

		event.preventDefault();
		const rect = config.ref.current.getBoundingClientRect();
		const mouseX = event.clientX - rect.left;
		const mouseY = event.clientY - rect.top;
		const worldX = (mouseX - config.pan.x) / config.zoom;
		const worldY = (mouseY - config.pan.y) / config.zoom;
		const zoomDelta = -dy * config.zoomSensitivity * config.zoom;
		const newZoom = snapZoom(Math.max(config.minZoom, Math.min(config.maxZoom, config.zoom + zoomDelta)));

		config.setZoom(newZoom);
		config.setPan({
			x: mouseX - worldX * newZoom,
			y: mouseY - worldY * newZoom,
		});
	};
}
