import { describe, it, expect } 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 { nodeKeysAtom, edgeKeysAtom, highestZIndexAtom, nodeFamilyAtom, edgeFamilyAtom } from '../core/graph-derived';
import { addNodeToLocalGraphAtom } from '../core/graph-mutations';
import { addEdgeToLocalGraphAtom } 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, zIndex = 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 },
		data: null,
		created_at: '2024-01-01T00:00:00Z',
		updated_at: '2024-01-01T00:00:00Z',
	};
}

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: 'default',
		filter_condition: null,
		ui_properties: null,
		data: null,
		created_at: '2024-01-01T00:00:00Z',
		updated_at: '2024-01-01T00:00:00Z',
	};
}

describe('graph-derived', () => {
	describe('nodeKeysAtom', () => {
		it('returns empty array for empty graph', () => {
			const store = freshStore();
			expect(store.get(nodeKeysAtom).length).toBe(0);
		});

		it('returns all node ids', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a'));
			store.set(addNodeToLocalGraphAtom, makeDbNode('b'));
			const keys = store.get(nodeKeysAtom);
			expect(keys.length).toBe(2);
			expect(keys).toContain('a');
			expect(keys).toContain('b');
		});
	});

	describe('edgeKeysAtom', () => {
		it('returns empty array for empty graph', () => {
			const store = freshStore();
			expect(store.get(edgeKeysAtom).length).toBe(0);
		});

		it('returns all edge ids', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a'));
			store.set(addNodeToLocalGraphAtom, makeDbNode('b'));
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'a', 'b'));
			expect(store.get(edgeKeysAtom).length).toBe(1);
		});
	});

	describe('highestZIndexAtom', () => {
		it('returns 0 for empty graph', () => {
			const store = freshStore();
			expect(store.get(highestZIndexAtom)).toBe(0);
		});

		it('returns highest zIndex', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 0, 0, 5));
			store.set(addNodeToLocalGraphAtom, makeDbNode('b', 0, 0, 10));
			store.set(addNodeToLocalGraphAtom, makeDbNode('c', 0, 0, 3));
			expect(store.get(highestZIndexAtom)).toBe(10);
		});
	});

	describe('nodeFamilyAtom', () => {
		it('returns null for non-existent node', () => {
			const store = freshStore();
			expect(store.get(nodeFamilyAtom('nonexistent'))).toBeNull();
		});

		it('returns node state with position and label', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 100, 200));
			const node = store.get(nodeFamilyAtom('a'));
			expect(node).not.toBeNull();
			expect(node!.x).toBe(100);
			expect(node!.y).toBe(200);
			expect(node!.label).toBe('Node a');
		});
	});

	describe('edgeFamilyAtom', () => {
		it('returns null for non-existent edge', () => {
			const store = freshStore();
			expect(store.get(edgeFamilyAtom('nonexistent'))).toBeNull();
		});

		it('returns edge state with source/target positions', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 0, 0));
			store.set(addNodeToLocalGraphAtom, makeDbNode('b', 300, 400));
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'a', 'b'));
			const edge = store.get(edgeFamilyAtom('e1'));
			expect(edge).not.toBeNull();
			expect(edge!.sourceId).toBe('a');
			expect(edge!.targetId).toBe('b');
			expect(edge!.sourcePosition).toEqual({ x: 0, y: 0 });
			expect(edge!.targetPosition).toEqual({ x: 300, y: 400 });
		});
	});
});
