/**
 * useNodeGestures — Node-level gesture recognition hook
 *
 * Replaces the inline click counting, touch long-press timers, and
 * context menu handling in Node.tsx. Produces gesture events that
 * route through the v2 pipeline (resolve → dispatch).
 *
 * Drag mechanics stay in useNodeDrag — this hook handles:
 * - tap / double-tap / triple-tap (via TimedStateRunner)
 * - right-click (tap with button=2)
 * - long-press (finger/pencil only)
 * - hover enter/leave
 *
 * Returns event handler props to spread on the node DOM element.
 */

import { useRef, useEffect } from 'react';
import { useAtomValue } from 'jotai';
import { screenToWorldAtom } from '../core/viewport-store';
import { isStylusActiveAtom, fingerCountAtom } from '../core/input-store';
import { inputModeAtom, keyboardInteractionModeAtom } from '../core/interaction-store';
import { focusedNodeIdAtom, selectedNodeIdsAtom } from '../core/selection-store';
import { draggingNodeIdAtom, edgeCreationAtom } from '../core/graph-store';
import { isFilterActiveAtom } from '../core/search-store';
import { selectionPathAtom } from '../core/selection-path-store';
import { commandLineVisibleAtom } from '../commands/store';
import { classifyPointer } from '../core/input-classifier';
import type { InputSource } from '../core/input-classifier';

import { TimedStateRunner } from './timed-state-runner';
import { extractModifiers } from './normalize';
import { resolve, type MappingIndex } from './mapper';
import { dispatch } from './dispatcher';
import type {
	BivariantCallback,
	HeldKeysState,
	InputEvent,
	PointerGestureEvent,
	PointerGestureType,
	GuardContext,
	ResolvedAction,
	Button,
} from './types';
import { NO_HELD_KEYS, NO_MODIFIERS } from './types';

export interface UseNodeGesturesConfig {
	nodeId: string;
	/** Pre-built mapping index (shared from useGestureSystem) */
	mappingIndex: MappingIndex;
	/** Called for every resolved gesture */
	onAction?: BivariantCallback<[InputEvent, ResolvedAction]>;
	/** Snapshot of currently held keyboard keys */
	heldKeys?: HeldKeysState;
}

export interface NodeGestureHandlers {
	onPointerDown: (e: React.PointerEvent) => void;
	onPointerUp: (e: React.PointerEvent) => void;
	onPointerCancel: (e: React.PointerEvent) => void;
	onContextMenu: (e: React.MouseEvent) => void;
}

export function useNodeGestures({
	nodeId,
	mappingIndex,
	onAction,
	heldKeys = NO_HELD_KEYS,
}: UseNodeGesturesConfig): NodeGestureHandlers {
	const screenToWorld = useAtomValue(screenToWorldAtom);
	const isStylusActive = useAtomValue(isStylusActiveAtom);
	const fingerCount = useAtomValue(fingerCountAtom);
	const inputMode = useAtomValue(inputModeAtom);
	const keyboardInteractionMode = useAtomValue(keyboardInteractionModeAtom);
	const selectedNodeIds = useAtomValue(selectedNodeIdsAtom);
	const focusedNodeId = useAtomValue(focusedNodeIdAtom);
	const draggingNodeId = useAtomValue(draggingNodeIdAtom);
	const edgeCreation = useAtomValue(edgeCreationAtom);
	const selectionPath = useAtomValue(selectionPathAtom);
	const isSearchActive = useAtomValue(isFilterActiveAtom);
	const commandLineVisible = useAtomValue(commandLineVisibleAtom);

	const runner = useRef(new TimedStateRunner());
	const sourceRef = useRef<InputSource>('mouse');
	const buttonRef = useRef<Button>(0);
	const posRef = useRef({ x: 0, y: 0 });
	const modifiersRef = useRef(NO_MODIFIERS);
	// Track whether a right-button drag happened (suppress context menu)
	const rightDragSeen = useRef(false);

	// Guard context ref — kept fresh each render
	const guardRef = useRef<GuardContext>({
		isStylusActive: false,
		fingerCount: 0,
		isDragging: false,
		isResizing: false,
		isSplitting: false,
		inputMode: { type: 'normal' },
		keyboardInteractionMode: 'navigate',
		selectedNodeIds: new Set(),
		focusedNodeId: null,
		isSearchActive: false,
		commandLineVisible: false,
		heldKeys: NO_HELD_KEYS,
		custom: {},
	});
	guardRef.current = {
		isStylusActive,
		fingerCount,
		isDragging: draggingNodeId !== null,
		isResizing: false,
		isSplitting: false,
		inputMode,
		keyboardInteractionMode,
		selectedNodeIds,
		focusedNodeId,
		isSearchActive,
		commandLineVisible,
		heldKeys,
		custom: {},
	};
	guardRef.current.custom = {
		isSelecting: selectionPath !== null,
		isCreatingEdge: edgeCreation.isCreating,
	};

	// Cleanup
	useEffect(() => {
		return () => runner.current.destroy();
	}, []);

	const resolveAndDispatch = (event: PointerGestureEvent): ResolvedAction | null => {
		const resolution = resolve(event, mappingIndex, guardRef.current);
		if (resolution) {
			dispatch(event, resolution);
			onAction?.(event, resolution);
		}
		return resolution;
	};

	const makeEvent = (
		type: PointerGestureType,
		phase: PointerGestureEvent['phase'],
		screenX: number,
		screenY: number,
	): PointerGestureEvent => {
		const worldPos = screenToWorld(screenX, screenY);
		return {
			kind: 'pointer',
			type,
			phase,
			subject: { kind: 'node', nodeId },
			source: sourceRef.current,
			button: buttonRef.current,
			modifiers: modifiersRef.current,
			heldKeys,
			screenPosition: { x: screenX, y: screenY },
			worldPosition: worldPos,
		};
	};

	// Wire timer-based emits (long-press)
	useEffect(() => {
		runner.current.onEmit = (gestureType) => {
			if (gestureType === 'long-press') {
				const event = makeEvent('long-press', 'instant', posRef.current.x, posRef.current.y);
				resolveAndDispatch(event);
			}
		};
	});

	const onPointerDown = (e: React.PointerEvent) => {
		const classified = classifyPointer(e.nativeEvent);
		sourceRef.current = classified.source;
		buttonRef.current = (e.button ?? 0) as Button;
		posRef.current = { x: e.clientX, y: e.clientY };
		modifiersRef.current = extractModifiers(e);
		rightDragSeen.current = false;
		runner.current.feed('down');
	};

	const onPointerUp = (e: React.PointerEvent) => {
		const emitted = runner.current.feed('up');
		if (emitted) {
			// Synchronous tap emit — build and resolve
			const event = makeEvent(emitted, 'instant', e.clientX, e.clientY);
			resolveAndDispatch(event);
		}
	};

	const onPointerCancel = (_e: React.PointerEvent) => {
		runner.current.feed('cancel');
	};

	const onContextMenu = (e: React.MouseEvent) => {
		// Always prevent default — we handle right-click through the pipeline
		e.preventDefault();
		e.stopPropagation();

		// If a right-drag was in progress, suppress the context menu
		if (rightDragSeen.current) {
			rightDragSeen.current = false;
			return;
		}

		// Right-click is handled as tap with button=2 via the timed state
		// machine's 'up' event. If the runner already emitted a tap,
		// it was dispatched in onPointerUp. If not (e.g., contextmenu
		// fires without paired pointer events on some browsers), we
		// fire a synthetic tap here as a fallback.
		const event = makeEvent('tap', 'instant', e.clientX, e.clientY);
		event.button = 2;
		event.modifiers = extractModifiers(e);
		resolveAndDispatch(event);
	};

	return {
		onPointerDown,
		onPointerUp,
		onPointerCancel,
		onContextMenu,
	};
}
