import { describe, it, expect } 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 { setNodeParentAtom, nestNodesOnDropAtom } 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): 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: null,
		filter_condition: null,
		ui_properties: 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('nest-on-drop', () => {
	it('nests a node into a target by setting parentId', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);
		addNode(store, 'B', 300, 300);

		store.set(nestNodesOnDropAtom, { nodeIds: ['A'], targetId: 'B' });

		const graph = store.get(graphAtom);
		expect(graph.getNodeAttribute('A', 'parentId')).toBe('B');
	});

	it('nests multiple nodes into a target', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);
		addNode(store, 'B', 200, 200);
		addNode(store, 'target', 500, 500);

		store.set(nestNodesOnDropAtom, { nodeIds: ['A', 'B'], targetId: 'target' });

		const graph = store.get(graphAtom);
		expect(graph.getNodeAttribute('A', 'parentId')).toBe('target');
		expect(graph.getNodeAttribute('B', 'parentId')).toBe('target');
	});

	it('does not nest a node into itself', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);

		store.set(nestNodesOnDropAtom, { nodeIds: ['A'], targetId: 'A' });

		const graph = store.get(graphAtom);
		expect(graph.getNodeAttribute('A', 'parentId')).toBeUndefined();
	});

	it('prevents circular nesting (via setNodeParentAtom)', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);
		addNode(store, 'B', 200, 200);
		store.set(setNodeParentAtom, { nodeId: 'B', parentId: 'A' });

		// Try to nest A into B (would be circular: A→B→A)
		store.set(nestNodesOnDropAtom, { nodeIds: ['A'], targetId: 'B' });

		const graph = store.get(graphAtom);
		// A should NOT become child of B
		expect(graph.getNodeAttribute('A', 'parentId')).toBeUndefined();
	});

	it('preserves edges when nesting', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);
		addNode(store, 'B', 300, 300);
		addNode(store, 'C', 500, 500);
		store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'A', 'C'));

		store.set(nestNodesOnDropAtom, { nodeIds: ['A'], targetId: 'B' });

		const graph = store.get(graphAtom);
		// Edge still exists with same source/target
		expect(graph.hasEdge('e1')).toBe(true);
		expect(graph.source('e1')).toBe('A');
		expect(graph.target('e1')).toBe('C');
	});

	it('auto-resizes target group after nesting', () => {
		const store = freshStore();
		addNode(store, 'target', 0, 0);
		addNode(store, 'child', 100, 100);

		store.set(nestNodesOnDropAtom, { nodeIds: ['child'], targetId: 'target' });

		const graph = store.get(graphAtom);
		const targetAttrs = graph.getNodeAttributes('target') as GraphNodeAttributes;
		// Group should have resized to contain child at (100, 100)
		expect(targetAttrs.x).toBe(80);  // 100 - 20 padding
		expect(targetAttrs.y).toBe(50);  // 100 - 20 padding - 30 header
	});
});
