import { describe, it, expect, vi } 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 { edgeFamilyAtom } from '../core/graph-derived';
import { addNodeToLocalGraphAtom, departingEdgesAtom, removeEdgeWithAnimationAtom } from '../core/graph-mutations';
import { addEdgeToLocalGraphAtom } from '../core/graph-mutations-edges';

// --- Helpers ---

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',
	};
}

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',
	};
}

// --- Tests ---

describe('edge-animations', () => {
	describe('departingEdgesAtom', () => {
		it('starts empty', () => {
			const store = freshStore();
			const departing = store.get(departingEdgesAtom);
			expect(departing.size).toBe(0);
		});
	});

	describe('removeEdgeWithAnimationAtom', () => {
		it('snapshots edge state before removal', () => {
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 0, 0));
			store.set(addNodeToLocalGraphAtom, makeDbNode('b', 200, 200));
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'a', 'b'));

			// Verify edge exists
			const edgeBefore = store.get(edgeFamilyAtom('e1'));
			expect(edgeBefore).not.toBeNull();

			// Remove with animation
			store.set(removeEdgeWithAnimationAtom, 'e1');

			// Edge should be gone from graph
			const edgeAfter = store.get(edgeFamilyAtom('e1'));
			expect(edgeAfter).toBeNull();

			// But snapshot should be in departing edges
			const departing = store.get(departingEdgesAtom);
			expect(departing.size).toBe(1);
			expect(departing.has('e1')).toBe(true);
			expect(departing.get('e1')!.sourceId).toBe('a');
			expect(departing.get('e1')!.targetId).toBe('b');
		});

		it('cleans up departing edge after timeout', async () => {
			vi.useFakeTimers();
			const store = freshStore();
			store.set(addNodeToLocalGraphAtom, makeDbNode('a', 0, 0));
			store.set(addNodeToLocalGraphAtom, makeDbNode('b', 200, 200));
			store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'a', 'b'));

			store.set(removeEdgeWithAnimationAtom, 'e1');
			expect(store.get(departingEdgesAtom).size).toBe(1);

			// Advance time past the animation duration
			vi.advanceTimersByTime(350);

			expect(store.get(departingEdgesAtom).size).toBe(0);
			vi.useRealTimers();
		});

		it('does nothing for non-existent edge', () => {
			const store = freshStore();
			store.set(removeEdgeWithAnimationAtom, 'nonexistent');
			expect(store.get(departingEdgesAtom).size).toBe(0);
		});
	});
});
