import { describe, it, expect } from 'vitest';
import Graph from 'graphology';
import { applyDelta, invertDelta, createSnapshot } from '../core/history-actions';
import type { HistoryDelta } from '../core/history-types';
import type { GraphNodeAttributes, GraphEdgeAttributes } from '../core/types';

function makeGraph() {
	const g = new Graph<GraphNodeAttributes, GraphEdgeAttributes>({ multi: true, type: 'directed' });
	g.addNode('n1', { x: 0, y: 0, width: 100, height: 50, size: 1, locked: false, type: 'default', dbData: {} } as any);
	g.addNode('n2', { x: 200, y: 100, width: 100, height: 50, size: 1, locked: false, type: 'default', dbData: {} } as any);
	g.addEdgeWithKey('e1', 'n1', 'n2', { dbData: {} } as any);
	return g;
}

describe('history-actions', () => {
	describe('applyDelta', () => {
		it('move-node updates x/y', () => {
			const g = makeGraph();
			const changed = applyDelta(g, { type: 'move-node', nodeId: 'n1', from: { x: 0, y: 0 }, to: { x: 50, y: 50 } });
			expect(changed).toBe(false); // non-structural
			expect(g.getNodeAttribute('n1', 'x')).toBe(50);
			expect(g.getNodeAttribute('n1', 'y')).toBe(50);
		});

		it('resize-node updates width/height', () => {
			const g = makeGraph();
			applyDelta(g, { type: 'resize-node', nodeId: 'n1', from: { width: 100, height: 50 }, to: { width: 200, height: 100 } });
			expect(g.getNodeAttribute('n1', 'width')).toBe(200);
		});

		it('add-node adds to graph', () => {
			const g = makeGraph();
			const changed = applyDelta(g, { type: 'add-node', nodeId: 'n3', attributes: { x: 0, y: 0, width: 50, height: 50, dbData: {} } as any });
			expect(changed).toBe(true);
			expect(g.hasNode('n3')).toBe(true);
		});

		it('remove-node drops from graph', () => {
			const g = makeGraph();
			const changed = applyDelta(g, { type: 'remove-node', nodeId: 'n1', attributes: {} as any, connectedEdges: [] });
			expect(changed).toBe(true);
			expect(g.hasNode('n1')).toBe(false);
		});

		it('add-edge adds edge', () => {
			const g = makeGraph();
			g.addNode('n3', { dbData: {} } as any);
			const changed = applyDelta(g, { type: 'add-edge', edgeId: 'e2', source: 'n1', target: 'n3', attributes: { dbData: {} } as any });
			expect(changed).toBe(true);
			expect(g.hasEdge('e2')).toBe(true);
		});

		it('remove-edge drops edge', () => {
			const g = makeGraph();
			const changed = applyDelta(g, { type: 'remove-edge', edgeId: 'e1', source: 'n1', target: 'n2', attributes: {} as any });
			expect(changed).toBe(true);
			expect(g.hasEdge('e1')).toBe(false);
		});

		it('batch applies multiple deltas', () => {
			const g = makeGraph();
			const changed = applyDelta(g, {
				type: 'batch',
				deltas: [
					{ type: 'move-node', nodeId: 'n1', from: { x: 0, y: 0 }, to: { x: 10, y: 10 } },
					{ type: 'remove-edge', edgeId: 'e1', source: 'n1', target: 'n2', attributes: {} as any },
				],
			});
			expect(changed).toBe(true); // edge removal is structural
			expect(g.getNodeAttribute('n1', 'x')).toBe(10);
		});

		it('skips missing nodes gracefully', () => {
			const g = makeGraph();
			const changed = applyDelta(g, { type: 'move-node', nodeId: 'nonexistent', from: { x: 0, y: 0 }, to: { x: 5, y: 5 } });
			expect(changed).toBe(false);
		});
	});

	describe('invertDelta', () => {
		it('inverts move-node by swapping from/to', () => {
			const delta: HistoryDelta = { type: 'move-node', nodeId: 'n1', from: { x: 0, y: 0 }, to: { x: 50, y: 50 } };
			const inv = invertDelta(delta);
			expect(inv.type).toBe('move-node');
			if (inv.type === 'move-node') {
				expect(inv.from).toEqual({ x: 50, y: 50 });
				expect(inv.to).toEqual({ x: 0, y: 0 });
			}
		});

		it('inverts add-node to remove-node', () => {
			const delta: HistoryDelta = { type: 'add-node', nodeId: 'n1', attributes: {} as any };
			const inv = invertDelta(delta);
			expect(inv.type).toBe('remove-node');
		});

		it('inverts remove-node to add-node (or batch with edges)', () => {
			const delta: HistoryDelta = { type: 'remove-node', nodeId: 'n1', attributes: {} as any, connectedEdges: [] };
			const inv = invertDelta(delta);
			expect(inv.type).toBe('add-node');
		});

		it('inverts batch in reverse order', () => {
			const delta: HistoryDelta = {
				type: 'batch',
				deltas: [
					{ type: 'move-node', nodeId: 'n1', from: { x: 0, y: 0 }, to: { x: 10, y: 10 } },
					{ type: 'move-node', nodeId: 'n2', from: { x: 0, y: 0 }, to: { x: 20, y: 20 } },
				],
			};
			const inv = invertDelta(delta);
			expect(inv.type).toBe('batch');
			if (inv.type === 'batch') {
				expect(inv.deltas.length).toBe(2);
				if (inv.deltas[0].type === 'move-node') {
					expect(inv.deltas[0].nodeId).toBe('n2'); // reversed
				}
			}
		});
	});

	describe('createSnapshot', () => {
		it('captures all nodes and edges', () => {
			const g = makeGraph();
			const snap = createSnapshot(g, 'test');
			expect(snap.nodes.length).toBe(2);
			expect(snap.edges.length).toBe(1);
			expect(snap.label).toBe('test');
		});

		it('returns empty for empty graph', () => {
			const g = new Graph<GraphNodeAttributes, GraphEdgeAttributes>({ multi: true, type: 'directed' });
			const snap = createSnapshot(g);
			expect(snap.nodes.length).toBe(0);
			expect(snap.edges.length).toBe(0);
		});
	});
});
