import { describe, it, expect } from 'vitest';
import { createStore } from 'jotai';
import Graph from 'graphology';
import type { GraphNodeAttributes, GraphEdgeAttributes, DBGraphNode } from '../core/types';
import { graphAtom, graphOptions, currentGraphIdAtom } from '../core/graph-store';
import { nodePositionAtomFamily, updateNodePositionAtom, cleanupNodePositionAtom, nodePositionUpdateCounterAtom } from '../core/graph-position';
import { addNodeToLocalGraphAtom } from '../core/graph-mutations';

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-01T00:00:00Z',
		updated_at: '2024-01-01T00:00:00Z',
	};
}

describe('graph-position', () => {
	describe('nodePositionAtomFamily', () => {
		it('returns position from graph', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 100, 200));
			const pos = store.get(nodePositionAtomFamily('a'));
			expect(pos).toEqual({ x: 100, y: 200 });
		});

		it('returns same reference for unchanged position (structural equality)', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 100, 200));
			const pos1 = store.get(nodePositionAtomFamily('a'));
			const pos2 = store.get(nodePositionAtomFamily('a'));
			expect(pos1).toBe(pos2);
		});
	});

	describe('updateNodePositionAtom', () => {
		it('updates position in graph', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 0, 0));
			store.set(updateNodePositionAtom, { nodeId: 'a', position: { x: 50, y: 75 } });
			const pos = store.get(nodePositionAtomFamily('a'));
			expect(pos).toEqual({ x: 50, y: 75 });
		});

		it('bumps nodePositionUpdateCounter', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 0, 0));
			const before = store.get(nodePositionUpdateCounterAtom);
			store.set(updateNodePositionAtom, { nodeId: 'a', position: { x: 1, y: 1 } });
			expect(store.get(nodePositionUpdateCounterAtom)).toBe(before + 1);
		});

		it('ignores non-existent node', () => {
			const store = freshStore();
			const before = store.get(nodePositionUpdateCounterAtom);
			store.set(updateNodePositionAtom, { nodeId: 'none', position: { x: 1, y: 1 } });
			expect(store.get(nodePositionUpdateCounterAtom)).toBe(before);
		});
	});

	describe('cleanupNodePositionAtom', () => {
		it('removes position atom for node', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 100, 200));
			// Access to populate cache
			store.get(nodePositionAtomFamily('a'));
			store.set(cleanupNodePositionAtom, 'a');
			// After cleanup, a fresh read should still work if node exists in graph
			// The main purpose is to evict the atomFamily cache entry
			// This test ensures no crash
			expect(true).toBe(true);
		});
	});
});
