import { describe, it, expect, beforeEach } 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 { addNodeToLocalGraphAtom } from '../core/graph-mutations';
import { viewportRectAtom } from '../core/viewport-store';
import {
	lockedNodeIdAtom,
	lockedNodePageIndexAtom,
	lockedNodePageCountAtom,
	lockNodeAtom,
	unlockNodeAtom,
	nextLockedPageAtom,
	prevLockedPageAtom,
	goToLockedPageAtom,
	hasLockedNodeAtom,
} from '../core/locked-node-store';

function freshStore() {
	const store = createStore();
	store.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
	store.set(currentGraphIdAtom, 'test-graph');
	store.set(viewportRectAtom, { x: 0, y: 0, width: 800, height: 600, top: 0, left: 0, right: 800, bottom: 600, toJSON: () => ({}) } as DOMRect);
	return store;
}

function makeDbNode(id: string): DBGraphNode {
	return {
		id, graph_id: 'test-graph', label: `Node ${id}`, node_type: 'default',
		configuration: null, ui_properties: { x: 0, y: 0, width: 200, height: 100, size: 15, zIndex: 0 },
		data: null, created_at: '2024-01-01', updated_at: '2024-01-01',
	};
}

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

	beforeEach(() => {
		store = freshStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
	});

	describe('initial state', () => {
		it('no node locked', () => {
			expect(store.get(lockedNodeIdAtom)).toBeNull();
			expect(store.get(hasLockedNodeAtom)).toBe(false);
		});
	});

	describe('lockNodeAtom', () => {
		it('locks a node', () => {
			store.set(lockNodeAtom, { nodeId: 'n1' });
			expect(store.get(lockedNodeIdAtom)).toBe('n1');
			expect(store.get(hasLockedNodeAtom)).toBe(true);
		});

		it('resets page index on lock', () => {
			store.set(lockedNodePageIndexAtom, 3);
			store.set(lockNodeAtom, { nodeId: 'n1' });
			expect(store.get(lockedNodePageIndexAtom)).toBe(0);
		});
	});

	describe('unlockNodeAtom', () => {
		it('unlocks the node', () => {
			store.set(lockNodeAtom, { nodeId: 'n1' });
			store.set(unlockNodeAtom);
			expect(store.get(lockedNodeIdAtom)).toBeNull();
			expect(store.get(hasLockedNodeAtom)).toBe(false);
		});
	});

	describe('page navigation', () => {
		it('nextLockedPageAtom wraps around', () => {
			store.set(lockedNodePageCountAtom, 3);
			store.set(nextLockedPageAtom); // 0 → 1
			expect(store.get(lockedNodePageIndexAtom)).toBe(1);
			store.set(nextLockedPageAtom); // 1 → 2
			store.set(nextLockedPageAtom); // 2 → 0
			expect(store.get(lockedNodePageIndexAtom)).toBe(0);
		});

		it('prevLockedPageAtom wraps around', () => {
			store.set(lockedNodePageCountAtom, 3);
			store.set(prevLockedPageAtom); // 0 → 2
			expect(store.get(lockedNodePageIndexAtom)).toBe(2);
		});

		it('goToLockedPageAtom sets specific page', () => {
			store.set(lockedNodePageCountAtom, 5);
			store.set(goToLockedPageAtom, 3);
			expect(store.get(lockedNodePageIndexAtom)).toBe(3);
		});

		it('goToLockedPageAtom ignores out-of-bounds', () => {
			store.set(lockedNodePageCountAtom, 3);
			store.set(goToLockedPageAtom, 10);
			expect(store.get(lockedNodePageIndexAtom)).toBe(0);
		});
	});
});
