/**
 * Action Executor
 *
 * Executes actions by ID with the provided context and helpers.
 * This is the core execution logic, separate from React hooks.
 */

import type { createStore } from 'jotai';
import { getAction } from './action-registry';
import type { ActionContext, ActionHelpers, ActionContextOptions, CanvasEventType } from './settings-types';
import { BuiltInActionId } from './settings-types';
import {
	selectedNodeIdsAtom,
	selectSingleNodeAtom,
	addNodesToSelectionAtom,
	clearSelectionAtom,
	selectEdgeAtom,
	clearEdgeSelectionAtom,
} from './selection-store';
import { zoomAtom, panAtom, resetViewportAtom, viewportRectAtom, fitToBoundsAtom, centerOnNodeAtom } from './viewport-store';
import { lockedNodeIdAtom, lockNodeAtom, unlockNodeAtom } from './locked-node-store';
import { canUndoAtom, canRedoAtom, undoAtom, redoAtom } from './history-store';
import { FitToBoundsMode } from '../utils/layout';
import { createDebug } from '../utils/debug';

const debug = createDebug('actions');

// =============================================================================
// Execution Result
// =============================================================================

export interface ActionExecutionResult {
	success: boolean;
	actionId: string;
	error?: Error;
}

// =============================================================================
// Execute Action
// =============================================================================

/**
 * Execute an action by its ID.
 *
 * @param actionId - The ID of the action to execute
 * @param context - The context of the triggering event
 * @param helpers - Helper functions for manipulating canvas state
 * @returns Promise resolving to the execution result
 */
export async function executeAction(
	actionId: string,
	context: ActionContext,
	helpers: ActionHelpers
): Promise<ActionExecutionResult> {
	// None action is a no-op
	if (actionId === BuiltInActionId.None) {
		return { success: true, actionId };
	}

	const action = getAction(actionId);

	if (!action) {
		debug.warn('Action not found: %s', actionId);
		return {
			success: false,
			actionId,
			error: new Error(`Action not found: ${actionId}`),
		};
	}

	// Check if action requires a node but none was provided
	if (action.requiresNode && !context.nodeId) {
		debug.warn('Action %s requires a node context', actionId);
		return {
			success: false,
			actionId,
			error: new Error(`Action ${actionId} requires a node context`),
		};
	}

	try {
		const result = action.handler(context, helpers);

		// Handle both sync and async handlers
		if (result instanceof Promise) {
			await result;
		}

		return { success: true, actionId };
	} catch (error) {
		debug.error('Error executing action %s: %O', actionId, error);
		return {
			success: false,
			actionId,
			error: error instanceof Error ? error : new Error(String(error)),
		};
	}
}

// =============================================================================
// Context Builder Helpers
// =============================================================================

/**
 * Create an ActionContext from a mouse event.
 */
export function createActionContext(
	eventType: CanvasEventType,
	screenEvent: { clientX: number; clientY: number },
	worldPosition: { x: number; y: number },
	options?: ActionContextOptions
): ActionContext {
	return {
		eventType,
		nodeId: options?.nodeId,
		nodeData: options?.nodeData,
		edgeId: options?.edgeId,
		edgeData: options?.edgeData,
		worldPosition,
		screenPosition: {
			x: screenEvent.clientX,
			y: screenEvent.clientY,
		},
		modifiers: {
			shift: false,
			ctrl: false,
			alt: false,
			meta: false,
		},
	};
}

/**
 * Create an ActionContext from a React mouse event.
 */
export function createActionContextFromReactEvent(
	eventType: CanvasEventType,
	event: React.MouseEvent,
	worldPosition: { x: number; y: number },
	options?: ActionContextOptions
): ActionContext {
	return {
		eventType,
		nodeId: options?.nodeId,
		nodeData: options?.nodeData,
		edgeId: options?.edgeId,
		edgeData: options?.edgeData,
		worldPosition,
		screenPosition: {
			x: event.clientX,
			y: event.clientY,
		},
		modifiers: {
			shift: event.shiftKey,
			ctrl: event.ctrlKey,
			alt: event.altKey,
			meta: event.metaKey,
		},
	};
}

/**
 * Create an ActionContext from a touch event (for long-press).
 */
export function createActionContextFromTouchEvent(
	eventType: CanvasEventType,
	touch: { clientX: number; clientY: number },
	worldPosition: { x: number; y: number },
	options?: ActionContextOptions
): ActionContext {
	return {
		eventType,
		nodeId: options?.nodeId,
		nodeData: options?.nodeData,
		edgeId: options?.edgeId,
		edgeData: options?.edgeData,
		worldPosition,
		screenPosition: {
			x: touch.clientX,
			y: touch.clientY,
		},
		modifiers: {
			shift: false,
			ctrl: false,
			alt: false,
			meta: false,
		},
	};
}

// =============================================================================
// Headless Action Helpers Builder
// =============================================================================

export interface BuildActionHelpersOptions {
	onDeleteNode?: (nodeId: string) => Promise<void>;
	onOpenContextMenu?: (position: { x: number; y: number }, nodeId?: string) => void;
	onCreateNode?: (position: { x: number; y: number }) => Promise<void>;
	onApplyForceLayout?: () => Promise<void>;
}

/**
 * Build ActionHelpers from a raw Jotai store.
 * No React required — works in tests, CLI tools, and non-React integrations.
 */
export function buildActionHelpers(
	store: ReturnType<typeof createStore>,
	options: BuildActionHelpersOptions = {},
): ActionHelpers {
	return {
		selectNode: (nodeId: string) => store.set(selectSingleNodeAtom, nodeId),
		addToSelection: (nodeId: string) => store.set(addNodesToSelectionAtom, [nodeId]),
		clearSelection: () => store.set(clearSelectionAtom),
		getSelectedNodeIds: () => Array.from(store.get(selectedNodeIdsAtom)),

		fitToBounds: (mode: 'graph' | 'selection', padding?: number) => {
			const fitMode = mode === 'graph' ? FitToBoundsMode.Graph : FitToBoundsMode.Selection;
			store.set(fitToBoundsAtom, { mode: fitMode, padding });
		},
		centerOnNode: (nodeId: string) => store.set(centerOnNodeAtom, nodeId),
		resetViewport: () => store.set(resetViewportAtom),

		lockNode: (nodeId: string) => store.set(lockNodeAtom, { nodeId }),
		unlockNode: (_nodeId: string) => store.set(unlockNodeAtom),
		toggleLock: (nodeId: string) => {
			const currentLockedId = store.get(lockedNodeIdAtom);
			if (currentLockedId === nodeId) {
				store.set(unlockNodeAtom);
			} else {
				store.set(lockNodeAtom, { nodeId });
			}
		},
		deleteNode: async (nodeId: string) => {
			if (options.onDeleteNode) {
				await options.onDeleteNode(nodeId);
			} else {
				debug.warn('deleteNode called but onDeleteNode callback not provided');
			}
		},
		isNodeLocked: (nodeId: string) => store.get(lockedNodeIdAtom) === nodeId,

		applyForceLayout: async () => {
			if (options.onApplyForceLayout) {
				await options.onApplyForceLayout();
			} else {
				debug.warn('applyForceLayout called but onApplyForceLayout callback not provided');
			}
		},

		undo: () => store.set(undoAtom),
		redo: () => store.set(redoAtom),
		canUndo: () => store.get(canUndoAtom),
		canRedo: () => store.get(canRedoAtom),

		selectEdge: (edgeId: string) => store.set(selectEdgeAtom, edgeId),
		clearEdgeSelection: () => store.set(clearEdgeSelectionAtom),

		openContextMenu: options.onOpenContextMenu,
		createNode: options.onCreateNode,
	};
}
