/**
 * InputProvider — React context for the shared input system
 *
 * Holds the shared mapping index, action callback, and the single
 * keyboard listener for the active canvas instance.
 */

import React, {
	createContext,
	useContext,
	useEffect,
	useRef,
	useState,
} from 'react';
import { useStore } from 'jotai';
import type { Store } from 'jotai/vanilla/store';
import { dispatch } from './dispatcher';
import { resolve } from './mapper';
import type { GestureSubject, InputEvent, KeyInputEvent, MappingContext, ResolvedAction, Vec2 } from './types';
import type { BivariantCallback } from './types';
import { useInputModeGestureContext } from './useInputModeGestureContext';
import { useRegisterInputActions } from './useRegisterInputActions';
import { useInputSystem, type InputSystemAPI } from './useGestureSystem';
import {
	isEditableTarget,
	applyHeldKeyDelta,
	getCurrentSubject,
	getSubjectPosition,
	buildGuardContext,
} from './gesture-provider-utils';

// Re-export utilities for consumers
export {
	isEditableTarget,
	setHeldKeyValue,
	applyHeldKeyDelta,
	getCurrentSubject,
	getSubjectPosition,
	buildGuardContext,
} from './gesture-provider-utils';
export type { HeldKeysState } from './gesture-provider-utils';

// Singleton: keyboard focus routing uses "last focused wins" across all
// InputProvider instances in the page. This is intentional — only one
// canvas should handle keyboard events at a time.
let nextOwnerId = 1;
let activeOwnerId: number | null = null;

export interface InputConfig {
	contexts?: MappingContext[];
	palmRejection?: boolean;
	debug?: boolean;
}

export type GestureConfig = InputConfig;

interface InputContextValue {
	system: InputSystemAPI;
	onAction?: BivariantCallback<[InputEvent, ResolvedAction]>;
	registerCanvasRoot: (node: HTMLDivElement | null) => void;
}

const InputContext = createContext<InputContextValue | null>(null);

export interface InputProviderProps {
	children: React.ReactNode;
	gestureConfig?: InputConfig;
	onAction?: BivariantCallback<[InputEvent, ResolvedAction]>;
}

export type GestureProviderProps = InputProviderProps;

// Pure utility functions moved to gesture-provider-utils.ts in v1.8.0

export function InputProvider({ children, gestureConfig, onAction }: InputProviderProps) {
	const store = useStore();
	const system = useInputSystem({
		contexts: gestureConfig?.contexts,
		palmRejection: gestureConfig?.palmRejection ?? true,
	});

	useInputModeGestureContext(system);
	useRegisterInputActions();

	const [ownerId] = useState(() => nextOwnerId++);
	const [canvasRoot, setCanvasRoot] = useState<HTMLDivElement | null>(null);
	const rootRef = useRef<HTMLDivElement | null>(null);
	const systemRef = useRef(system);
	systemRef.current = system;

	const registerCanvasRoot = (node: HTMLDivElement | null) => {
		rootRef.current = node;
		setCanvasRoot(node);
	};

	useEffect(() => {
		const root = canvasRoot;
		if (!root) {
			return;
		}

		const activate = (event: Event) => {
			activeOwnerId = ownerId;
			if (!isEditableTarget(event.target)) {
				root.focus({ preventScroll: true });
			}
		};

		root.addEventListener('pointerdown', activate, true);
		root.addEventListener('focusin', activate, true);

		return () => {
			root.removeEventListener('pointerdown', activate, true);
			root.removeEventListener('focusin', activate, true);
			if (activeOwnerId === ownerId) {
				activeOwnerId = null;
			}
		};
	}, [canvasRoot, ownerId]);

	useEffect(() => {
		const handleKeyboard = (nativeEvent: KeyboardEvent) => {
			if (activeOwnerId !== ownerId) {
				return;
			}

			const root = rootRef.current;
			if (!root || !root.isConnected) {
				return;
			}

			const currentSystem = systemRef.current;
			const editableTarget = isEditableTarget(nativeEvent.target);
			const shouldTrackHeldKey = nativeEvent.type === 'keyup' || (!nativeEvent.isComposing && !editableTarget);
			const nextHeldKeys = shouldTrackHeldKey
				? applyHeldKeyDelta(nativeEvent, currentSystem.heldKeys)
				: currentSystem.heldKeys;
			if (nextHeldKeys !== currentSystem.heldKeys) {
				currentSystem.setHeldKeys(nextHeldKeys);
			}

			if (nativeEvent.isComposing || editableTarget) {
				return;
			}

			const subject = getCurrentSubject(store);
			const { screenPosition, worldPosition } = getSubjectPosition(store, root, subject);
			const inputEvent: KeyInputEvent = {
				kind: 'key',
				phase: nativeEvent.type === 'keydown' ? 'down' : 'up',
				key: nativeEvent.key,
				code: nativeEvent.code,
				repeat: nativeEvent.repeat,
				modifiers: {
					shift: nativeEvent.shiftKey,
					ctrl: nativeEvent.ctrlKey,
					alt: nativeEvent.altKey,
					meta: nativeEvent.metaKey,
				},
				heldKeys: nextHeldKeys,
				subject,
				screenPosition,
				worldPosition,
				originalEvent: nativeEvent,
			};

			const resolution = resolve(
				inputEvent,
				currentSystem.mappingIndex,
				buildGuardContext(store, { heldKeys: nextHeldKeys }),
			);

			if (!resolution) {
				return;
			}

			nativeEvent.preventDefault();
			dispatch(inputEvent, resolution);
			onAction?.(inputEvent, resolution);
		};

		const clearHeldKeys = () => {
			systemRef.current.clearHeldKeys();
		};

		window.addEventListener('keydown', handleKeyboard, true);
		window.addEventListener('keyup', handleKeyboard, true);
		window.addEventListener('blur', clearHeldKeys);
		document.addEventListener('visibilitychange', clearHeldKeys);

		return () => {
			window.removeEventListener('keydown', handleKeyboard, true);
			window.removeEventListener('keyup', handleKeyboard, true);
			window.removeEventListener('blur', clearHeldKeys);
			document.removeEventListener('visibilitychange', clearHeldKeys);
		};
	}, [onAction, ownerId, store]);

	return (
		<InputContext.Provider value={{ system, onAction, registerCanvasRoot }}>
			{children}
		</InputContext.Provider>
	);
}

export function useInputContext(): InputContextValue {
	const ctx = useContext(InputContext);
	if (!ctx) {
		throw new Error('useInputContext must be used within an InputProvider');
	}
	return ctx;
}

export const GestureProvider = InputProvider;
export const useGestureContext = useInputContext;
