import { describe, it, expect } from 'vitest';
import { createStore } from 'jotai';
import {
	selectedNodeIdsAtom,
	selectedEdgeIdAtom,
	selectSingleNodeAtom,
	toggleNodeInSelectionAtom,
	clearSelectionAtom,
	addNodesToSelectionAtom,
	removeNodesFromSelectionAtom,
	selectEdgeAtom,
	clearEdgeSelectionAtom,
	handleNodePointerDownSelectionAtom,
	selectedNodesCountAtom,
	hasSelectionAtom,
} from '../core/selection-store';

describe('selection-store', () => {
	// --- Single Node Selection ---

	describe('selectSingleNodeAtom', () => {
		it('selects a single node', () => {
			const store = createStore();
			store.set(selectSingleNodeAtom, 'n1');
			expect(Array.from(store.get(selectedNodeIdsAtom))).toEqual(['n1']);
		});

		it('replaces existing selection', () => {
			const store = createStore();
			store.set(selectSingleNodeAtom, 'n1');
			store.set(selectSingleNodeAtom, 'n2');
			expect(Array.from(store.get(selectedNodeIdsAtom))).toEqual(['n2']);
		});

		it('clears selection when passed null', () => {
			const store = createStore();
			store.set(selectSingleNodeAtom, 'n1');
			store.set(selectSingleNodeAtom, null);
			expect(store.get(selectedNodeIdsAtom).size).toBe(0);
		});

		it('clears selection when passed undefined', () => {
			const store = createStore();
			store.set(selectSingleNodeAtom, 'n1');
			store.set(selectSingleNodeAtom, undefined);
			expect(store.get(selectedNodeIdsAtom).size).toBe(0);
		});

		it('no-ops when already the only selection', () => {
			const store = createStore();
			store.set(selectSingleNodeAtom, 'n1');
			const ref1 = store.get(selectedNodeIdsAtom);
			store.set(selectSingleNodeAtom, 'n1');
			const ref2 = store.get(selectedNodeIdsAtom);
			// Same reference — no state change
			expect(ref1).toBe(ref2);
		});

		it('clears edge selection', () => {
			const store = createStore();
			store.set(selectedEdgeIdAtom, 'e1');
			store.set(selectSingleNodeAtom, 'n1');
			expect(store.get(selectedEdgeIdAtom)).toBeNull();
		});
	});

	// --- Toggle ---

	describe('toggleNodeInSelectionAtom', () => {
		it('adds node to empty selection', () => {
			const store = createStore();
			store.set(toggleNodeInSelectionAtom, 'n1');
			expect(store.get(selectedNodeIdsAtom).has('n1')).toBe(true);
		});

		it('removes node from selection', () => {
			const store = createStore();
			store.set(selectSingleNodeAtom, 'n1');
			store.set(toggleNodeInSelectionAtom, 'n1');
			expect(store.get(selectedNodeIdsAtom).has('n1')).toBe(false);
		});

		it('adds to existing multi-selection', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1', 'n2']);
			store.set(toggleNodeInSelectionAtom, 'n3');
			expect(store.get(selectedNodeIdsAtom).size).toBe(3);
		});
	});

	// --- Clear ---

	describe('clearSelectionAtom', () => {
		it('clears all selected nodes', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1', 'n2', 'n3']);
			store.set(clearSelectionAtom);
			expect(store.get(selectedNodeIdsAtom).size).toBe(0);
		});
	});

	// --- Multi-select ---

	describe('addNodesToSelectionAtom', () => {
		it('adds multiple nodes', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1', 'n2', 'n3']);
			expect(store.get(selectedNodeIdsAtom).size).toBe(3);
		});

		it('adds to existing selection', () => {
			const store = createStore();
			store.set(selectSingleNodeAtom, 'n1');
			store.set(addNodesToSelectionAtom, ['n2', 'n3']);
			expect(store.get(selectedNodeIdsAtom).size).toBe(3);
		});

		it('handles duplicates gracefully', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1', 'n1', 'n2']);
			expect(store.get(selectedNodeIdsAtom).size).toBe(2);
		});
	});

	describe('removeNodesFromSelectionAtom', () => {
		it('removes specified nodes', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1', 'n2', 'n3']);
			store.set(removeNodesFromSelectionAtom, ['n1', 'n3']);
			expect(Array.from(store.get(selectedNodeIdsAtom))).toEqual(['n2']);
		});

		it('ignores non-existent nodes', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1']);
			store.set(removeNodesFromSelectionAtom, ['n99']);
			expect(store.get(selectedNodeIdsAtom).size).toBe(1);
		});
	});

	// --- Edge Selection ---

	describe('selectEdgeAtom', () => {
		it('selects an edge', () => {
			const store = createStore();
			store.set(selectEdgeAtom, 'e1');
			expect(store.get(selectedEdgeIdAtom)).toBe('e1');
		});

		it('clears node selection when selecting edge', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1', 'n2']);
			store.set(selectEdgeAtom, 'e1');
			expect(store.get(selectedNodeIdsAtom).size).toBe(0);
		});

		it('allows null to deselect', () => {
			const store = createStore();
			store.set(selectEdgeAtom, 'e1');
			store.set(selectEdgeAtom, null);
			expect(store.get(selectedEdgeIdAtom)).toBeNull();
		});

		it('does not clear nodes when deselecting edge with null', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1']);
			store.set(selectEdgeAtom, null);
			expect(store.get(selectedNodeIdsAtom).size).toBe(1);
		});
	});

	describe('clearEdgeSelectionAtom', () => {
		it('clears edge selection', () => {
			const store = createStore();
			store.set(selectEdgeAtom, 'e1');
			store.set(clearEdgeSelectionAtom);
			expect(store.get(selectedEdgeIdAtom)).toBeNull();
		});
	});

	// --- Pointer Down Selection ---

	describe('handleNodePointerDownSelectionAtom', () => {
		it('selects node on click without shift', () => {
			const store = createStore();
			store.set(handleNodePointerDownSelectionAtom, { nodeId: 'n1', isShiftPressed: false });
			expect(Array.from(store.get(selectedNodeIdsAtom))).toEqual(['n1']);
		});

		it('preserves multi-selection when clicking already-selected node', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1', 'n2']);
			store.set(handleNodePointerDownSelectionAtom, { nodeId: 'n1', isShiftPressed: false });
			// Should preserve — allows multi-node drag
			expect(store.get(selectedNodeIdsAtom).size).toBe(2);
		});

		it('shift-click adds to selection', () => {
			const store = createStore();
			store.set(handleNodePointerDownSelectionAtom, { nodeId: 'n1', isShiftPressed: false });
			store.set(handleNodePointerDownSelectionAtom, { nodeId: 'n2', isShiftPressed: true });
			expect(store.get(selectedNodeIdsAtom).size).toBe(2);
		});

		it('shift-click toggles off selected node', () => {
			const store = createStore();
			store.set(addNodesToSelectionAtom, ['n1', 'n2']);
			store.set(handleNodePointerDownSelectionAtom, { nodeId: 'n1', isShiftPressed: true });
			expect(store.get(selectedNodeIdsAtom).has('n1')).toBe(false);
			expect(store.get(selectedNodeIdsAtom).has('n2')).toBe(true);
		});

		it('clears edge selection', () => {
			const store = createStore();
			store.set(selectEdgeAtom, 'e1');
			store.set(handleNodePointerDownSelectionAtom, { nodeId: 'n1', isShiftPressed: false });
			expect(store.get(selectedEdgeIdAtom)).toBeNull();
		});
	});

	// --- Derived Atoms ---

	describe('derived atoms', () => {
		it('selectedNodesCountAtom returns count', () => {
			const store = createStore();
			expect(store.get(selectedNodesCountAtom)).toBe(0);
			store.set(addNodesToSelectionAtom, ['n1', 'n2']);
			expect(store.get(selectedNodesCountAtom)).toBe(2);
		});

		it('hasSelectionAtom returns boolean', () => {
			const store = createStore();
			expect(store.get(hasSelectionAtom)).toBe(false);
			store.set(selectSingleNodeAtom, 'n1');
			expect(store.get(hasSelectionAtom)).toBe(true);
		});
	});
});
