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 } from '../core/graph-mutations-edges';
import { selectedNodeIdsAtom } from '../core/selection-store';
import {
	clipboardAtom,
	hasClipboardContentAtom,
	clipboardNodeCountAtom,
	copyToClipboardAtom,
	cutToClipboardAtom,
	pasteFromClipboardAtom,
	duplicateSelectionAtom,
	clearClipboardAtom,
} from '../core/clipboard-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',
	};
}

describe('clipboard-store', () => {
	let store: ReturnType<typeof createStore>;

	beforeEach(() => {
		store = createStore();
		store.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
		store.set(currentGraphIdAtom, 'test-graph');
	});

	it('starts with empty clipboard', () => {
		expect(store.get(hasClipboardContentAtom)).toBe(false);
		expect(store.get(clipboardNodeCountAtom)).toBe(0);
	});

	it('copy populates clipboard with selected nodes', () => {
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1', 10, 20));
		store.set(addNodeToLocalGraphAtom, makeDbNode('n2', 30, 40));
		store.set(selectedNodeIdsAtom, new Set(['n1', 'n2']));

		store.set(copyToClipboardAtom);

		expect(store.get(hasClipboardContentAtom)).toBe(true);
		expect(store.get(clipboardNodeCountAtom)).toBe(2);
	});

	it('copy includes edges between selected nodes', () => {
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		store.set(addNodeToLocalGraphAtom, makeDbNode('n2'));
		store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));
		store.set(selectedNodeIdsAtom, new Set(['n1', 'n2']));

		store.set(copyToClipboardAtom);

		const clipboard = store.get(clipboardAtom)!;
		expect(clipboard.edges).toHaveLength(1);
	});

	it('copy with no selection is a no-op', () => {
		store.set(copyToClipboardAtom);
		expect(store.get(hasClipboardContentAtom)).toBe(false);
	});

	it('cut removes originals from graph', () => {
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		store.set(selectedNodeIdsAtom, new Set(['n1']));

		store.set(cutToClipboardAtom);

		expect(store.get(hasClipboardContentAtom)).toBe(true);
		expect(store.get(graphAtom).order).toBe(0);
	});

	it('paste creates new nodes with offset', () => {
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1', 100, 100));
		store.set(selectedNodeIdsAtom, new Set(['n1']));
		store.set(copyToClipboardAtom);

		const newIds = store.set(pasteFromClipboardAtom);
		expect(newIds).toHaveLength(1);
		expect(store.get(graphAtom).order).toBe(2);
	});

	it('paste remaps edge IDs between pasted nodes', () => {
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		store.set(addNodeToLocalGraphAtom, makeDbNode('n2'));
		store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'n1', 'n2'));
		store.set(selectedNodeIdsAtom, new Set(['n1', 'n2']));
		store.set(copyToClipboardAtom);

		store.set(pasteFromClipboardAtom);

		const graph = store.get(graphAtom);
		expect(graph.order).toBe(4);
		expect(graph.size).toBe(2);
	});

	it('paste from empty clipboard returns empty array', () => {
		const result = store.set(pasteFromClipboardAtom);
		expect(result).toEqual([]);
	});

	it('clearClipboard empties clipboard', () => {
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		store.set(selectedNodeIdsAtom, new Set(['n1']));
		store.set(copyToClipboardAtom);
		expect(store.get(hasClipboardContentAtom)).toBe(true);

		store.set(clearClipboardAtom);
		expect(store.get(hasClipboardContentAtom)).toBe(false);
	});

	it('duplicate copies + pastes in one operation', () => {
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		store.set(selectedNodeIdsAtom, new Set(['n1']));

		store.set(duplicateSelectionAtom);

		expect(store.get(graphAtom).order).toBe(2);
	});
});
