import { describe, it, expect } 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 { updateNodePositionAtom } from '../core/graph-position';
import { addNodeToLocalGraphAtom, startNodeDragAtom, endNodeDragAtom } from '../core/graph-mutations';
import { setNodeParentAtom, getNodeDescendants } from '../core/group-store';

// --- Helpers ---

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

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

function addNode(store: ReturnType<typeof createStore>, id: string, x = 0, y = 0) {
	store.set(addNodeToLocalGraphAtom, makeDbNode(id, x, y));
}

// --- Tests ---

describe('nested-group-drag', () => {
	describe('getNodeDescendants', () => {
		it('returns empty array for a node with no children', () => {
			const store = freshStore();
			addNode(store, 'lonely', 100, 100);

			const graph = store.get(graphAtom);
			const descendants = getNodeDescendants(graph, 'lonely');
			expect(descendants).toEqual([]);
		});

		it('returns direct children', () => {
			const store = freshStore();
			addNode(store, 'group');
			addNode(store, 'child1');
			addNode(store, 'child2');
			store.set(setNodeParentAtom, { nodeId: 'child1', parentId: 'group' });
			store.set(setNodeParentAtom, { nodeId: 'child2', parentId: 'group' });

			const graph = store.get(graphAtom);
			const descendants = getNodeDescendants(graph, 'group');
			expect(descendants).toContain('child1');
			expect(descendants).toContain('child2');
			expect(descendants).toHaveLength(2);
		});

		it('returns nested descendants recursively', () => {
			const store = freshStore();
			addNode(store, 'outer');
			addNode(store, 'inner');
			addNode(store, 'leaf');
			store.set(setNodeParentAtom, { nodeId: 'inner', parentId: 'outer' });
			store.set(setNodeParentAtom, { nodeId: 'leaf', parentId: 'inner' });

			const graph = store.get(graphAtom);
			const descendants = getNodeDescendants(graph, 'outer');
			expect(descendants).toContain('inner');
			expect(descendants).toContain('leaf');
			expect(descendants).toHaveLength(2);
		});

		it('does not include the group node itself', () => {
			const store = freshStore();
			addNode(store, 'group');
			addNode(store, 'child');
			store.set(setNodeParentAtom, { nodeId: 'child', parentId: 'group' });

			const graph = store.get(graphAtom);
			const descendants = getNodeDescendants(graph, 'group');
			expect(descendants).not.toContain('group');
		});
	});

	describe('drag with auto-resize', () => {
		it('auto-resizes parent group when child drag ends', () => {
			const store = freshStore();
			addNode(store, 'group', 0, 0);
			addNode(store, 'child', 50, 50);
			store.set(setNodeParentAtom, { nodeId: 'child', parentId: 'group' });

			// Drag child to a new position
			store.set(startNodeDragAtom, { nodeId: 'child' });
			store.set(updateNodePositionAtom, { nodeId: 'child', position: { x: 400, y: 400 } });
			store.set(endNodeDragAtom);

			const graph = store.get(graphAtom);
			const groupAttrs = graph.getNodeAttributes('group') as GraphNodeAttributes;
			// Group should have resized to contain child at (400, 400)
			expect(groupAttrs.x).toBe(380); // 400 - 20
			expect(groupAttrs.y).toBe(350); // 400 - 20 - 30
		});
	});
});
