/**
 * Clipboard state management
 *
 * Provides copy/cut/paste operations for nodes and edges.
 * Uses local-first approach - pasted nodes exist only in local graph until saved.
 */

import { atom } from 'jotai';
import type {
	NodeId,
	GraphNodeAttributes,
	GraphEdgeAttributes,
	DBGraphNode,
	DBGraphEdge,
} from './types';
import { graphAtom, graphUpdateVersionAtom } from './graph-store';
import { nodePositionUpdateCounterAtom } from './graph-position';
import { addNodeToLocalGraphAtom, optimisticDeleteNodeAtom } from './graph-mutations';
import { addEdgeToLocalGraphAtom } from './graph-mutations-edges';
import { selectedNodeIdsAtom, addNodesToSelectionAtom, clearSelectionAtom } from './selection-store';
import { pushHistoryAtom } from './history-store';
import { createDebug } from '../utils/debug';

const debug = createDebug('clipboard');

// --- Types ---

export interface ClipboardNodeData {
	/** Original node attributes */
	attrs: GraphNodeAttributes;
	/** Original DB data (will get new ID on paste) */
	dbData: DBGraphNode;
}

export interface ClipboardEdgeData {
	/** Original source node ID */
	source: string;
	/** Original target node ID */
	target: string;
	/** Edge attributes */
	attrs: GraphEdgeAttributes;
	/** Original DB data */
	dbData: DBGraphEdge;
}

export interface ClipboardBounds {
	minX: number;
	minY: number;
	maxX: number;
	maxY: number;
}

export interface ClipboardData {
	/** Copied nodes with their data */
	nodes: ClipboardNodeData[];
	/** Copied edges (only edges between copied nodes) */
	edges: ClipboardEdgeData[];
	/** Bounding box of copied selection */
	bounds: ClipboardBounds;
	/** When the copy was made */
	timestamp: number;
}

// --- Constants ---

/** Default offset when pasting (diagonal from original) */
export const PASTE_OFFSET = { x: 50, y: 50 };

// --- Core Atoms ---

/**
 * Clipboard data storage
 */
export const clipboardAtom = atom<ClipboardData | null>(null);

/**
 * Whether clipboard has content
 */
export const hasClipboardContentAtom = atom((get) => get(clipboardAtom) !== null);

/**
 * Number of nodes in clipboard
 */
export const clipboardNodeCountAtom = atom((get) => {
	const clipboard = get(clipboardAtom);
	return clipboard?.nodes.length ?? 0;
});

// --- Helper Functions ---

/**
 * Calculate bounding box of nodes
 */
function calculateBounds(nodes: ClipboardNodeData[]): ClipboardBounds {
	if (nodes.length === 0) {
		return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
	}

	let minX = Infinity;
	let minY = Infinity;
	let maxX = -Infinity;
	let maxY = -Infinity;

	for (const node of nodes) {
		minX = Math.min(minX, node.attrs.x);
		minY = Math.min(minY, node.attrs.y);
		maxX = Math.max(maxX, node.attrs.x + node.attrs.width);
		maxY = Math.max(maxY, node.attrs.y + node.attrs.height);
	}

	return { minX, minY, maxX, maxY };
}

/**
 * Generate a new unique ID for pasted nodes
 */
function generatePasteId(index: number): string {
	return `paste-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 8)}`;
}

// --- Action Atoms ---

/**
 * Copy selected nodes and their internal edges to clipboard
 */
export const copyToClipboardAtom = atom(null, (get, set, nodeIds?: NodeId[]) => {
	const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom));

	if (selectedIds.length === 0) {
		debug('Nothing to copy - no nodes selected');
		return;
	}

	const graph = get(graphAtom);
	const selectedSet = new Set(selectedIds);
	const nodes: ClipboardNodeData[] = [];
	const edges: ClipboardEdgeData[] = [];

	// Collect node data
	for (const nodeId of selectedIds) {
		if (!graph.hasNode(nodeId)) {
			debug('Node %s not found in graph, skipping', nodeId);
			continue;
		}

		const attrs = graph.getNodeAttributes(nodeId) as GraphNodeAttributes;
		nodes.push({
			attrs: { ...attrs },
			dbData: { ...attrs.dbData },
		});
	}

	// Collect edges where BOTH source and target are in selection
	graph.forEachEdge((edgeKey, attrs, source, target) => {
		if (selectedSet.has(source) && selectedSet.has(target)) {
			edges.push({
				source,
				target,
				attrs: { ...attrs } as GraphEdgeAttributes,
				dbData: { ...attrs.dbData },
			});
		}
	});

	const bounds = calculateBounds(nodes);

	const clipboardData: ClipboardData = {
		nodes,
		edges,
		bounds,
		timestamp: Date.now(),
	};

	set(clipboardAtom, clipboardData);
	debug('Copied %d nodes and %d edges to clipboard', nodes.length, edges.length);
});

/**
 * Cut selected nodes (copy + delete from graph)
 * Pushes history for undo support.
 */
export const cutToClipboardAtom = atom(null, (get, set, nodeIds?: NodeId[]) => {
	const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom));
	if (selectedIds.length === 0) return;

	// First copy to clipboard
	set(copyToClipboardAtom, selectedIds);

	// Push history so entire cut is undoable
	set(pushHistoryAtom, 'Cut nodes');

	// Delete each selected node (also removes connected edges)
	for (const nodeId of selectedIds) {
		set(optimisticDeleteNodeAtom, { nodeId });
	}

	set(clearSelectionAtom);
	debug('Cut %d nodes — copied to clipboard and deleted from graph', selectedIds.length);
});

/**
 * Paste clipboard content at an offset from original position
 */
export const pasteFromClipboardAtom = atom(
	null,
	(get, set, offset?: { x: number; y: number }) => {
		const clipboard = get(clipboardAtom);

		if (!clipboard || clipboard.nodes.length === 0) {
			debug('Nothing to paste - clipboard empty');
			return [];
		}

		const pasteOffset = offset ?? PASTE_OFFSET;
		const graph = get(graphAtom);

		// Push history for undo support
		set(pushHistoryAtom, 'Paste nodes');

		// Map old IDs to new IDs
		const idMap = new Map<string, string>();
		const newNodeIds: string[] = [];

		// Create new nodes
		for (let i = 0; i < clipboard.nodes.length; i++) {
			const nodeData = clipboard.nodes[i];
			const newId = generatePasteId(i);
			idMap.set(nodeData.dbData.id, newId);
			newNodeIds.push(newId);

			// Create new DBGraphNode with offset position
			const newDbNode: DBGraphNode = {
				...nodeData.dbData,
				id: newId,
				created_at: new Date().toISOString(),
				updated_at: new Date().toISOString(),
				ui_properties: {
					...(nodeData.dbData.ui_properties as Record<string, unknown> || {}),
					x: nodeData.attrs.x + pasteOffset.x,
					y: nodeData.attrs.y + pasteOffset.y,
				},
			};

			debug('Pasting node %s -> %s at (%d, %d)',
				nodeData.dbData.id, newId,
				nodeData.attrs.x + pasteOffset.x,
				nodeData.attrs.y + pasteOffset.y
			);

			set(addNodeToLocalGraphAtom, newDbNode);
		}

		// Create new edges with remapped IDs
		for (const edgeData of clipboard.edges) {
			const newSourceId = idMap.get(edgeData.source);
			const newTargetId = idMap.get(edgeData.target);

			if (!newSourceId || !newTargetId) {
				debug('Edge %s: source or target not found in id map, skipping', edgeData.dbData.id);
				continue;
			}

			const newEdgeId = generatePasteId(clipboard.edges.indexOf(edgeData) + clipboard.nodes.length);

			const newDbEdge: DBGraphEdge = {
				...edgeData.dbData,
				id: newEdgeId,
				source_node_id: newSourceId,
				target_node_id: newTargetId,
				created_at: new Date().toISOString(),
				updated_at: new Date().toISOString(),
			};

			debug('Pasting edge %s -> %s (from %s to %s)',
				edgeData.dbData.id, newEdgeId, newSourceId, newTargetId
			);

			set(addEdgeToLocalGraphAtom, newDbEdge);
		}

		// Select the newly pasted nodes
		set(clearSelectionAtom);
		set(addNodesToSelectionAtom, newNodeIds);

		debug('Pasted %d nodes and %d edges', newNodeIds.length, clipboard.edges.length);

		return newNodeIds;
	}
);

/**
 * Duplicate selected nodes in place with offset
 * Shorthand for copy + paste
 */
export const duplicateSelectionAtom = atom(null, (get, set) => {
	set(copyToClipboardAtom);
	return set(pasteFromClipboardAtom);
});

/**
 * Clear clipboard content
 */
export const clearClipboardAtom = atom(null, (_get, set) => {
	set(clipboardAtom, null);
	debug('Clipboard cleared');
});
