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 } from '../core/graph-store';
import { addNodeToLocalGraphAtom } from '../core/graph-mutations';
import {
	historyStateAtom,
	canUndoAtom,
	canRedoAtom,
	undoCountAtom,
	redoCountAtom,
	pushHistoryAtom,
	undoAtom,
	redoAtom,
	clearHistoryAtom,
	historyLabelsAtom,
} from '../core/history-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',
	};
}

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

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

	it('starts with empty history', () => {
		expect(store.get(canUndoAtom)).toBe(false);
		expect(store.get(canRedoAtom)).toBe(false);
		expect(store.get(undoCountAtom)).toBe(0);
		expect(store.get(redoCountAtom)).toBe(0);
	});

	it('pushHistoryAtom enables undo', () => {
		store.set(pushHistoryAtom, 'Add node');
		expect(store.get(canUndoAtom)).toBe(true);
		expect(store.get(undoCountAtom)).toBe(1);
	});

	it('pushHistoryAtom clears redo stack', () => {
		// Push two snapshots, undo once, then push again
		store.set(pushHistoryAtom, 'Step 1');
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		store.set(pushHistoryAtom, 'Step 2');
		store.set(undoAtom);
		expect(store.get(canRedoAtom)).toBe(true);

		store.set(pushHistoryAtom, 'Step 3');
		expect(store.get(canRedoAtom)).toBe(false);
	});

	it('undoAtom pops from past and pushes to future', () => {
		store.set(pushHistoryAtom, 'Action');
		store.set(undoAtom);

		expect(store.get(undoCountAtom)).toBe(0);
		expect(store.get(redoCountAtom)).toBe(1);
	});

	it('redoAtom pops from future and pushes to past', () => {
		store.set(pushHistoryAtom, 'Action');
		store.set(undoAtom);
		store.set(redoAtom);

		expect(store.get(undoCountAtom)).toBe(1);
		expect(store.get(redoCountAtom)).toBe(0);
	});

	it('clearHistoryAtom resets all history', () => {
		store.set(pushHistoryAtom, 'A');
		store.set(pushHistoryAtom, 'B');
		store.set(clearHistoryAtom);

		expect(store.get(canUndoAtom)).toBe(false);
		expect(store.get(canRedoAtom)).toBe(false);
	});

	it('historyLabelsAtom returns labels', () => {
		store.set(pushHistoryAtom, 'Alpha');
		store.set(pushHistoryAtom, 'Beta');

		const labels = store.get(historyLabelsAtom);
		expect(labels.past).toEqual(['Alpha', 'Beta']);
		expect(labels.future).toEqual([]);
	});

	it('undo on empty history returns false', () => {
		const result = store.set(undoAtom);
		expect(result).toBe(false);
	});

	it('redo on empty future returns false', () => {
		const result = store.set(redoAtom);
		expect(result).toBe(false);
	});

	it('multiple undo/redo operations maintain consistency', () => {
		store.set(pushHistoryAtom, 'Step 1');
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		store.set(pushHistoryAtom, 'Step 2');
		store.set(addNodeToLocalGraphAtom, makeDbNode('n2'));
		store.set(pushHistoryAtom, 'Step 3');

		expect(store.get(undoCountAtom)).toBe(3);

		store.set(undoAtom);
		store.set(undoAtom);
		expect(store.get(undoCountAtom)).toBe(1);
		expect(store.get(redoCountAtom)).toBe(2);

		store.set(redoAtom);
		expect(store.get(undoCountAtom)).toBe(2);
		expect(store.get(redoCountAtom)).toBe(1);
	});
});
