/**
 * Graph Mutations — Edge Operations
 *
 * Edge CRUD, atomic swap, animation, and label editing atoms.
 * Split from graph-mutations.ts for modularity.
 */

import { atom } from 'jotai';
import type {
	EdgeKey,
	GraphEdgeAttributes,
	DBGraphEdge,
	UIEdgeState,
	UIEdgePropertiesFromDB,
} from './types';
import {
	graphAtom,
	graphUpdateVersionAtom,
} from './graph-store';
import { nodePositionUpdateCounterAtom } from './graph-position';
import { edgeFamilyAtom } from './graph-derived';
import { createDebug } from '../utils/debug';
import { prefersReducedMotionAtom } from './reduced-motion-store';

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

// --- Edge CRUD ---

/**
 * Add an edge directly to local graph
 */
export const addEdgeToLocalGraphAtom = atom(null, (get, set, newEdge: DBGraphEdge) => {
	const graph = get(graphAtom);

	if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) {
		const uiProps = (newEdge.ui_properties || {}) as UIEdgePropertiesFromDB;
		const attributes: GraphEdgeAttributes = {
			type: (typeof uiProps.style === 'string' ? uiProps.style : 'solid') as 'solid' | 'dashed',
			color: typeof uiProps.color === 'string' ? uiProps.color : '#999',
			label: newEdge.edge_type ?? undefined,
			weight: typeof uiProps.weight === 'number' ? uiProps.weight : 1,
			dbData: newEdge,
		};
		if (!graph.hasEdge(newEdge.id)) {
			try {
				debug('Adding edge %s to local graph', newEdge.id);
				graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes);
				set(graphAtom, graph.copy());
				set(graphUpdateVersionAtom, (v) => v + 1);
			} catch (e) {
				debug('Failed to add edge %s: %o', newEdge.id, e);
			}
		}
	}
});

/**
 * Remove an edge from local graph
 */
export const removeEdgeFromLocalGraphAtom = atom(null, (get, set, edgeId: EdgeKey) => {
	const graph = get(graphAtom);
	if (graph.hasEdge(edgeId)) {
		graph.dropEdge(edgeId);
		set(graphAtom, graph.copy());
		set(graphUpdateVersionAtom, (v) => v + 1);
	}
});

/**
 * Atomic swap of temp edge with real edge (prevents render flash)
 */
export const swapEdgeAtomicAtom = atom(null, (get, set, { tempEdgeId, newEdge }: { tempEdgeId: EdgeKey; newEdge: DBGraphEdge }) => {
	const graph = get(graphAtom);

	if (graph.hasEdge(tempEdgeId)) {
		graph.dropEdge(tempEdgeId);
	}

	if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) {
		const uiProps = (newEdge.ui_properties || {}) as UIEdgePropertiesFromDB;
		const attributes: GraphEdgeAttributes = {
			type: (typeof uiProps.style === 'string' ? uiProps.style : 'solid') as 'solid' | 'dashed',
			color: typeof uiProps.color === 'string' ? uiProps.color : '#999',
			label: newEdge.edge_type ?? undefined,
			weight: typeof uiProps.weight === 'number' ? uiProps.weight : 1,
			dbData: newEdge,
		};

		if (!graph.hasEdge(newEdge.id)) {
			try {
				debug('Atomically swapping temp edge %s with real edge %s', tempEdgeId, newEdge.id);
				graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes);
			} catch (e) {
				debug('Failed to add edge %s: %o', newEdge.id, e);
			}
		}
	}

	set(graphAtom, graph.copy());
	set(graphUpdateVersionAtom, (v) => v + 1);
});

// --- Edge Animation State ---

/**
 * Departing edges: snapshots of edges that have been removed from the graph
 * but are still being rendered with an exit animation.
 */
export const departingEdgesAtom = atom<Map<string, UIEdgeState>>(new Map());

/**
 * Edge animation duration in milliseconds
 */
export const EDGE_ANIMATION_DURATION = 300;

/**
 * Remove an edge with a fade-out animation.
 * Snapshots the edge state, removes it from the graph, then cleans up
 * the departing entry after the animation duration.
 */
export const removeEdgeWithAnimationAtom = atom(null, (get, set, edgeKey: EdgeKey) => {
	const edgeState = get(edgeFamilyAtom(edgeKey));
	if (edgeState) {
		const departing = new Map(get(departingEdgesAtom));
		departing.set(edgeKey, edgeState);
		set(departingEdgesAtom, departing);

		set(removeEdgeFromLocalGraphAtom, edgeKey);

		const duration = get(prefersReducedMotionAtom) ? 0 : EDGE_ANIMATION_DURATION;
		setTimeout(() => {
			const current = new Map(get(departingEdgesAtom));
			current.delete(edgeKey);
			set(departingEdgesAtom, current);
		}, duration);
	}
});

// --- Edge Label Editing ---

/**
 * Edge key currently being edited (label inline editing).
 * null when no edge label is being edited.
 */
export const editingEdgeLabelAtom = atom<string | null>(null);

/**
 * Update an edge's label attribute
 */
export const updateEdgeLabelAtom = atom(null, (get, set, { edgeKey, label }: { edgeKey: EdgeKey; label: string }) => {
	const graph = get(graphAtom);
	if (graph.hasEdge(edgeKey)) {
		graph.setEdgeAttribute(edgeKey, 'label', label || undefined);
		set(graphUpdateVersionAtom, (v) => v + 1);
		set(nodePositionUpdateCounterAtom, (c) => c + 1);
	}
});
