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,
	removeEdgeFromLocalGraphAtom,
	editingEdgeLabelAtom,
	updateEdgeLabelAtom,
	departingEdgesAtom,
} from '../core/graph-mutations-edges';

function freshStore() {
	const store = createStore();
	store.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
	store.set(currentGraphIdAtom, 'test-graph');
	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-edges', () => {
	let store: ReturnType<typeof createStore>;

	beforeEach(() => {
		store = freshStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		store.set(addNodeToLocalGraphAtom, makeDbNode('n2'));
	});

	describe('addEdgeToLocalGraphAtom', () => {
		it('adds edge between existing nodes', () => {
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));
			expect(store.get(graphAtom).hasEdge('e1')).toBe(true);
		});

		it('ignores edge with missing source', () => {
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'missing', 'n2'));
			expect(store.get(graphAtom).size).toBe(0);
		});

		it('does not duplicate existing edge', () => {
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));
			expect(store.get(graphAtom).size).toBe(1);
		});
	});

	describe('removeEdgeFromLocalGraphAtom', () => {
		it('removes existing edge', () => {
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));
			store.set(removeEdgeFromLocalGraphAtom, 'e1');
			expect(store.get(graphAtom).hasEdge('e1')).toBe(false);
		});

		it('ignores non-existent edge', () => {
			store.set(removeEdgeFromLocalGraphAtom, 'missing');
			expect(store.get(graphAtom).size).toBe(0);
		});
	});

	describe('editingEdgeLabelAtom', () => {
		it('starts as null', () => {
			expect(store.get(editingEdgeLabelAtom)).toBeNull();
		});

		it('can be set', () => {
			store.set(editingEdgeLabelAtom, 'e1');
			expect(store.get(editingEdgeLabelAtom)).toBe('e1');
		});
	});

	describe('updateEdgeLabelAtom', () => {
		it('updates existing edge label', () => {
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));
			store.set(updateEdgeLabelAtom, { edgeKey: 'e1', label: 'New Label' });
			expect(store.get(graphAtom).getEdgeAttribute('e1', 'label')).toBe('New Label');
		});

		it('clears label when set to empty', () => {
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));
			store.set(updateEdgeLabelAtom, { edgeKey: 'e1', label: 'Something' });
			store.set(updateEdgeLabelAtom, { edgeKey: 'e1', label: '' });
			expect(store.get(graphAtom).getEdgeAttribute('e1', 'label')).toBeUndefined();
		});
	});

	describe('departingEdgesAtom', () => {
		it('starts empty', () => {
			expect(store.get(departingEdgesAtom).size).toBe(0);
		});
	});
});
