import { describe, it, expect, beforeEach } from 'vitest';
import { createStore } from 'jotai';
import Graph from 'graphology';
import type { GraphNodeAttributes, GraphEdgeAttributes, DBGraphNode, DBGraphEdge } from '../core/types';
import { graphAtom, graphOptions, currentGraphIdAtom } from '../core/graph-store';
import { addNodeToLocalGraphAtom } from '../core/graph-mutations';
import { addEdgeToLocalGraphAtom } from '../core/graph-mutations-edges';
import {
	splitNodeAtom,
	mergeNodesAtom,
	dropTargetNodeIdAtom,
} from '../core/graph-mutations-advanced';
import { viewportRectAtom } from '../core/viewport-store';

function freshStore() {
	const store = createStore();
	store.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
	store.set(currentGraphIdAtom, 'test-graph');
	store.set(viewportRectAtom, { x: 0, y: 0, width: 800, height: 600, top: 0, left: 0, right: 800, bottom: 600, toJSON: () => ({}) } as DOMRect);
	return store;
}

function makeDbNode(id: string, x = 0, y = 0): DBGraphNode {
	return {
		id, graph_id: 'test-graph', label: `Node ${id}`, node_type: 'default',
		configuration: null, ui_properties: { x, y, width: 200, height: 100, size: 15, zIndex: 0 },
		data: null, created_at: '2024-01-01', updated_at: '2024-01-01',
	};
}

function makeDbEdge(id: string, source: string, target: string): DBGraphEdge {
	return {
		id, graph_id: 'test-graph', source_node_id: source, target_node_id: target,
		edge_type: null, filter_condition: null, ui_properties: null,
		created_at: '2024-01-01', updated_at: '2024-01-01',
	};
}

describe('graph-mutations-advanced', () => {
	describe('dropTargetNodeIdAtom', () => {
		it('starts as null', () => {
			const store = freshStore();
			expect(store.get(dropTargetNodeIdAtom)).toBeNull();
		});

		it('can be set', () => {
			const store = freshStore();
			store.set(dropTargetNodeIdAtom, 'n1');
			expect(store.get(dropTargetNodeIdAtom)).toBe('n1');
		});
	});

	describe('splitNodeAtom', () => {
		it('creates a clone node at position2', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('n1', 100, 100));

			const graphBefore = store.get(graphAtom);
			const orderBefore = graphBefore.order;

			store.set(splitNodeAtom, {
				nodeId: 'n1',
				position1: { x: 100, y: 100 },
				position2: { x: 300, y: 300 },
			});

			const graphAfter = store.get(graphAtom);
			expect(graphAfter.order).toBe(orderBefore + 1);
		});

		it('duplicates edges to clone', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
			store.set(addNodeToLocalGraphAtom, makeDbNode('n2'));
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));

			store.set(splitNodeAtom, {
				nodeId: 'n1',
				position1: { x: 0, y: 0 },
				position2: { x: 200, y: 200 },
			});

			// Original edge + duplicated edge to clone
			expect(store.get(graphAtom).size).toBe(2);
		});

		it('does nothing for non-existent node', () => {
			const store = freshStore();
			store.set(splitNodeAtom, {
				nodeId: 'nonexistent',
				position1: { x: 0, y: 0 },
				position2: { x: 200, y: 200 },
			});
			expect(store.get(graphAtom).order).toBe(0);
		});
	});

	describe('mergeNodesAtom', () => {
		it('removes doomed nodes, survivor remains', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
			store.set(addNodeToLocalGraphAtom, makeDbNode('n2'));
			store.set(addNodeToLocalGraphAtom, makeDbNode('n3'));

			store.set(mergeNodesAtom, { nodeIds: ['n1', 'n2', 'n3'] });

			const graph = store.get(graphAtom);
			expect(graph.hasNode('n1')).toBe(true);
			expect(graph.hasNode('n2')).toBe(false);
			expect(graph.hasNode('n3')).toBe(false);
		});

		it('re-routes edges to survivor', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
			store.set(addNodeToLocalGraphAtom, makeDbNode('n2'));
			store.set(addNodeToLocalGraphAtom, makeDbNode('n3'));
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n2', 'n3'));

			store.set(mergeNodesAtom, { nodeIds: ['n1', 'n2'] });

			// n2 is doomed, its edge to n3 should be re-routed from n1 to n3
			const graph = store.get(graphAtom);
			expect(graph.order).toBe(2); // n1 + n3
			// Should have re-routed edge (n1→n3)
			expect(graph.size).toBeGreaterThanOrEqual(1);
		});

		it('does nothing with < 2 nodes', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
			store.set(mergeNodesAtom, { nodeIds: ['n1'] });
			expect(store.get(graphAtom).hasNode('n1')).toBe(true);
		});
	});
});
