/**
 * Graph Mutations — Advanced Operations
 *
 * Split/merge node atoms and drop-target state.
 * Split from graph-mutations.ts for modularity.
 */

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

const debug = createDebug('graph:mutations:advanced');

// --- Drop Target ---

/**
 * ID of the node currently being hovered over as a drop target during drag.
 * Used by drag-to-nest: when a dragged node is released over a target,
 * the dragged node becomes a child of the target.
 */
export const dropTargetNodeIdAtom = atom<string | null>(null);

// --- Split Node ---

/**
 * Split a node into two copies: original stays at position1, clone at position2.
 * All incident edges are duplicated so both copies have the same connections.
 */
export const splitNodeAtom = atom(null, (get, set, {
	nodeId, position1, position2,
}: { nodeId: string; position1: NodePosition; position2: NodePosition }) => {
	const graph = get(graphAtom);
	if (!graph.hasNode(nodeId)) return;
	const attrs = graph.getNodeAttributes(nodeId) as GraphNodeAttributes;
	const graphId = get(currentGraphIdAtom) || attrs.dbData.graph_id;

	// Snapshot for undo (must be before any mutations)
	set(pushHistoryAtom, 'Split node');

	// Move original to position1 (before addNodeToLocalGraphAtom which calls graph.copy())
	graph.setNodeAttribute(nodeId, 'x', position1.x);
	graph.setNodeAttribute(nodeId, 'y', position1.y);

	// Collect edges before addNodeToLocalGraphAtom replaces graph ref
	const edges: Array<{ source: string; target: string; attrs: GraphEdgeAttributes }> = [];
	graph.forEachEdge(nodeId, (_key, eAttrs, source, target) => {
		edges.push({ source, target, attrs: eAttrs as GraphEdgeAttributes });
	});

	// Create clone
	const cloneId = `split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
	const cloneDbNode: DBGraphNode = {
		...attrs.dbData,
		id: cloneId,
		graph_id: graphId,
		ui_properties: {
			...(attrs.dbData.ui_properties as Record<string, unknown> || {}),
			x: position2.x,
			y: position2.y,
		},
		created_at: new Date().toISOString(),
		updated_at: new Date().toISOString(),
	};
	set(addNodeToLocalGraphAtom, cloneDbNode);

	// Duplicate all edges to clone
	for (const edge of edges) {
		const newSource = edge.source === nodeId ? cloneId : edge.source;
		const newTarget = edge.target === nodeId ? cloneId : edge.target;
		set(addEdgeToLocalGraphAtom, {
			...edge.attrs.dbData,
			id: `split-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
			source_node_id: newSource,
			target_node_id: newTarget,
		});
	}

	set(graphUpdateVersionAtom, (v) => v + 1);
	set(nodePositionUpdateCounterAtom, (c) => c + 1);
	debug('Split node %s → clone %s', nodeId, cloneId);
});

// --- Merge Nodes ---

/**
 * Merge multiple nodes into one. The first node in the array survives.
 * Edges from doomed nodes are re-routed to the survivor.
 * Edges between merged nodes are discarded (would be self-loops).
 */
export const mergeNodesAtom = atom(null, (get, set, { nodeIds }: { nodeIds: string[] }) => {
	if (nodeIds.length < 2) return;
	const graph = get(graphAtom);
	const [survivorId, ...doomed] = nodeIds;
	if (!graph.hasNode(survivorId)) return;

	// Snapshot for undo (must be before any mutations)
	set(pushHistoryAtom, `Merge ${nodeIds.length} nodes`);

	const doomedSet = new Set(doomed);

	for (const doomedId of doomed) {
		if (!graph.hasNode(doomedId)) continue;

		// Collect edges before deletion (dropNode removes them)
		const edges: Array<{ source: string; target: string; attrs: GraphEdgeAttributes }> = [];
		graph.forEachEdge(doomedId, (_key, eAttrs, source, target) => {
			edges.push({ source, target, attrs: eAttrs as GraphEdgeAttributes });
		});

		for (const edge of edges) {
			const newSource = doomedSet.has(edge.source) ? survivorId : edge.source;
			const newTarget = doomedSet.has(edge.target) ? survivorId : edge.target;
			// Skip self-loops (edges between merged nodes)
			if (newSource === newTarget) continue;

			set(addEdgeToLocalGraphAtom, {
				...edge.attrs.dbData,
				id: `merge-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
				source_node_id: newSource,
				target_node_id: newTarget,
			});
		}

		// Delete doomed node (auto-removes its original edges)
		set(optimisticDeleteNodeAtom, { nodeId: doomedId });
	}

	set(graphUpdateVersionAtom, (v) => v + 1);
	debug('Merged nodes %o → survivor %s', nodeIds, survivorId);
});
