/**
 * GestureProvider Utilities
 *
 * Pure functions extracted from GestureProvider.tsx for testability.
 * No React dependencies — all functions operate on plain objects or DOM elements.
 *
 * @since 1.8.0
 */

import type { Store } from 'jotai/vanilla/store';
import type { GestureSubject, Vec2 } from './types';
import {
	draggingNodeIdAtom,
	edgeCreationAtom,
	fingerCountAtom,
	focusedNodeIdAtom,
	inputModeAtom,
	isStylusActiveAtom,
	keyboardInteractionModeAtom,
	screenToWorldAtom,
	selectedNodeIdsAtom,
	uiNodesAtom,
	worldToScreenAtom,
} from '../core';
import { isFilterActiveAtom } from '../core/search-store';
import { selectionPathAtom } from '../core/selection-path-store';
import { commandLineVisibleAtom } from '../commands/store';

// =============================================================================
// Editable Target Detection
// =============================================================================

/**
 * Check whether a DOM target is an editable element (input, textarea,
 * contenteditable, or marked with data-no-canvas-keyboard).
 */
export function isEditableTarget(target: EventTarget | null): boolean {
	if (!(target instanceof HTMLElement)) {
		return false;
	}

	if (target.isContentEditable) {
		return true;
	}

	const editable = target.closest('input, textarea, [contenteditable="true"], [data-no-canvas-keyboard="true"]');
	return editable !== null;
}

// =============================================================================
// Held Keys State Management
// =============================================================================

export interface HeldKeysState {
	byKey: Readonly<Record<string, boolean>>;
	byCode: Readonly<Record<string, boolean>>;
}

/**
 * Immutably set a single key's held state. Returns the same object
 * reference if nothing changed (for React bailout).
 */
export function setHeldKeyValue(
	current: Readonly<Record<string, boolean>>,
	key: string,
	isHeld: boolean,
): Readonly<Record<string, boolean>> {
	const previous = current[key] ?? false;
	if (previous === isHeld) {
		return current;
	}

	const next = { ...current };
	if (isHeld) {
		next[key] = true;
	} else {
		delete next[key];
	}
	return next;
}

/**
 * Apply a keyboard event to the held-keys state.
 * Returns same reference if nothing changed.
 */
export function applyHeldKeyDelta(
	event: KeyboardEvent,
	current: HeldKeysState,
): HeldKeysState {
	const isHeld = event.type === 'keydown';
	const nextByKey = setHeldKeyValue(current.byKey, event.key, isHeld);
	const nextByCode = setHeldKeyValue(current.byCode, event.code, isHeld);

	if (nextByKey === current.byKey && nextByCode === current.byCode) {
		return current;
	}

	return {
		byKey: nextByKey,
		byCode: nextByCode,
	};
}

// =============================================================================
// Subject Resolution
// =============================================================================

/**
 * Determine the current gesture subject from store state.
 */
export function getCurrentSubject(store: Store): GestureSubject {
	const draggingNodeId = store.get(draggingNodeIdAtom);
	if (draggingNodeId) {
		return { kind: 'node', nodeId: draggingNodeId };
	}

	const edgeCreation = store.get(edgeCreationAtom);
	if (edgeCreation.isCreating && edgeCreation.sourceNodeId) {
		return { kind: 'node', nodeId: edgeCreation.sourceNodeId };
	}

	const focusedNodeId = store.get(focusedNodeIdAtom);
	if (focusedNodeId) {
		return { kind: 'node', nodeId: focusedNodeId };
	}

	return { kind: 'background' };
}

/**
 * Get screen and world positions for a gesture subject.
 */
export function getSubjectPosition(
	store: Store,
	root: HTMLDivElement | null,
	subject: GestureSubject,
): { screenPosition?: Vec2; worldPosition?: Vec2 } {
	const worldToScreen = store.get(worldToScreenAtom);

	if (subject.kind === 'node') {
		const node = store.get(uiNodesAtom).find((entry: { id: string }) => entry.id === subject.nodeId);
		if (!node) {
			return {};
		}

		const worldPosition = {
			x: node.position.x + (node.width ?? 200) / 2,
			y: node.position.y + (node.height ?? 100) / 2,
		};

		return {
			worldPosition,
			screenPosition: worldToScreen(worldPosition.x, worldPosition.y),
		};
	}

	if (subject.kind === 'background' && root) {
		const rect = root.getBoundingClientRect();
		const screenPosition = {
			x: rect.left + rect.width / 2,
			y: rect.top + rect.height / 2,
		};

		return {
			screenPosition,
			worldPosition: store.get(screenToWorldAtom)(screenPosition.x, screenPosition.y),
		};
	}

	return {};
}

// =============================================================================
// Guard Context
// =============================================================================

/**
 * Build the guard context object used for gesture resolution.
 */
export function buildGuardContext(
	store: Store,
	system: { heldKeys: HeldKeysState } | HeldKeysState,
) {
	const heldKeys = 'heldKeys' in system ? system.heldKeys : system;
	const edgeCreation = store.get(edgeCreationAtom);
	return {
		isStylusActive: store.get(isStylusActiveAtom),
		fingerCount: store.get(fingerCountAtom),
		isDragging: store.get(draggingNodeIdAtom) !== null,
		isResizing: false,
		isSplitting: false,
		inputMode: store.get(inputModeAtom),
		keyboardInteractionMode: store.get(keyboardInteractionModeAtom),
		selectedNodeIds: store.get(selectedNodeIdsAtom),
		focusedNodeId: store.get(focusedNodeIdAtom),
		isSearchActive: store.get(isFilterActiveAtom),
		commandLineVisible: store.get(commandLineVisibleAtom),
		heldKeys,
		custom: {
			isSelecting: store.get(selectionPathAtom) !== null,
			isCreatingEdge: edgeCreation.isCreating,
		},
	};
}
