/**
 * Tree Layout Hook
 *
 * Arranges nodes in a hierarchical tree (top-down or left-to-right).
 * Roots are nodes with no incoming edges. Uses BFS for depth assignment
 * and distributes children horizontally at each level.
 */

import { useAtomValue } from 'jotai';
import { useRef } from 'react';
import { graphAtom } from '../core/graph-store';
import { uiNodesAtom } from '../core/graph-derived';
import { useAnimatedLayout, type UseAnimatedLayoutOptions } from './useAnimatedLayout';
import type { NodePosition, GraphNodeAttributes } from '../core/types';

export interface UseTreeLayoutOptions extends UseAnimatedLayoutOptions {
	/** Layout direction. Default: 'top-down' */
	direction?: 'top-down' | 'left-right';
	/** Vertical gap between levels. Default: 200 */
	levelGap?: number;
	/** Horizontal gap between siblings. Default: 100 */
	nodeGap?: number;
}

export function useTreeLayout(options: UseTreeLayoutOptions = {}) {
	const {
		direction = 'top-down',
		levelGap = 200,
		nodeGap = 100,
		...animateOptions
	} = options;

	const graph = useAtomValue(graphAtom);
	const nodes = useAtomValue(uiNodesAtom);
	const { animate, isAnimating } = useAnimatedLayout(animateOptions);
	const isRunningRef = useRef(false);

	const applyLayout = async (): Promise<void> => {
		if (isRunningRef.current || isAnimating) return;
		if (nodes.length === 0) return;

		isRunningRef.current = true;

		// Build adjacency: find roots (nodes with no incoming edges)
		const nodeIds = new Set(nodes.map((n) => n.id));
		const children = new Map<string, string[]>();
		const hasIncoming = new Set<string>();

		for (const nodeId of nodeIds) {
			children.set(nodeId, []);
		}

		graph.forEachEdge((_key, _attrs, source, target) => {
			if (nodeIds.has(source) && nodeIds.has(target) && source !== target) {
				children.get(source)?.push(target);
				hasIncoming.add(target);
			}
		});

		// Roots = no incoming edges
		const roots = [...nodeIds].filter((id) => !hasIncoming.has(id));
		if (roots.length === 0) {
			// If no roots (cycle), pick the first node
			roots.push(nodes[0].id);
		}

		// BFS to assign levels
		const levels = new Map<string, number>();
		const queue: string[] = [...roots];
		for (const r of roots) levels.set(r, 0);

		while (queue.length > 0) {
			const current = queue.shift()!;
			const level = levels.get(current)!;
			for (const child of children.get(current) || []) {
				if (!levels.has(child)) {
					levels.set(child, level + 1);
					queue.push(child);
				}
			}
		}

		// Assign any unvisited nodes (disconnected) to level 0
		for (const nodeId of nodeIds) {
			if (!levels.has(nodeId)) levels.set(nodeId, 0);
		}

		// Group nodes by level
		const byLevel = new Map<number, string[]>();
		for (const [nodeId, level] of levels) {
			if (!byLevel.has(level)) byLevel.set(level, []);
			byLevel.get(level)!.push(nodeId);
		}

		// Compute positions
		const targets = new Map<string, NodePosition>();
		const maxLevel = Math.max(...byLevel.keys());

		for (const [level, nodeIdsAtLevel] of byLevel) {
			const count = nodeIdsAtLevel.length;
			// Get max node width at this level for spacing
			let maxNodeSize = 200;
			for (const nid of nodeIdsAtLevel) {
				if (graph.hasNode(nid)) {
					const attrs = graph.getNodeAttributes(nid) as GraphNodeAttributes;
					maxNodeSize = Math.max(maxNodeSize, attrs.width || 200);
				}
			}

			const totalWidth = (count - 1) * (maxNodeSize + nodeGap);
			const startX = -totalWidth / 2;

			for (let i = 0; i < count; i++) {
				const primary = level * levelGap;
				const secondary = startX + i * (maxNodeSize + nodeGap);

				if (direction === 'top-down') {
					targets.set(nodeIdsAtLevel[i], { x: secondary, y: primary });
				} else {
					targets.set(nodeIdsAtLevel[i], { x: primary, y: secondary });
				}
			}
		}

		await animate(targets, direction === 'top-down' ? 'Tree layout' : 'Horizontal layout');
		isRunningRef.current = false;
	};

	return {
		applyLayout,
		isRunning: isRunningRef.current || isAnimating,
	};
}
