import { computeSequences } from "../../core/sequences";
import type { Task } from "../../types";
import type { DependencyGraph, GraphEdge, GraphFilters, GraphNode } from "../types/graph";

/**
 * Build a dependency graph from a list of tasks
 */
export function buildDependencyGraph(tasks: Task[]): DependencyGraph {
	// 1. Create node map
	const nodeMap = new Map<string, GraphNode>();
	for (const task of tasks) {
		nodeMap.set(task.id, {
			id: task.id,
			title: task.title,
			status: task.status,
			priority: task.priority,
			labels: task.labels || [],
			dependencies: task.dependencies || [],
			dependents: [],
		});
	}

	// 2. Build dependents (reverse edges)
	for (const task of tasks) {
		for (const depId of task.dependencies || []) {
			const depNode = nodeMap.get(depId);
			if (depNode) {
				depNode.dependents.push(task.id);
			}
		}
	}

	// 3. Build edges (source -> target means target depends on source)
	const edges: GraphEdge[] = [];
	for (const task of tasks) {
		for (const depId of task.dependencies || []) {
			if (nodeMap.has(depId)) {
				edges.push({ source: depId, target: task.id });
			}
		}
	}

	// 4. Compute sequences using existing algorithm
	const { sequences, unsequenced } = computeSequences(tasks);

	// 5. Annotate nodes with sequence index
	for (const seq of sequences) {
		for (const task of seq.tasks) {
			const node = nodeMap.get(task.id);
			if (node) node.sequenceIndex = seq.index;
		}
	}

	// 6. Convert unsequenced tasks to graph nodes
	const unsequencedNodes = unsequenced.map((t) => nodeMap.get(t.id)).filter((n): n is GraphNode => n !== undefined);

	return {
		nodes: Array.from(nodeMap.values()),
		edges,
		sequences,
		unsequenced: unsequencedNodes,
	};
}

/**
 * Filter the graph by various criteria
 */
export function filterGraph(graph: DependencyGraph, filters: GraphFilters): DependencyGraph {
	const filteredNodes = graph.nodes.filter((node) => {
		// Filter out completed tasks if showCompleted is false
		if (!filters.showCompleted && isCompleted(node.status)) {
			return false;
		}
		// Filter by status
		if (filters.status?.length && !filters.status.includes(node.status)) {
			return false;
		}
		// Filter by priority
		if (filters.priority?.length && node.priority && !filters.priority.includes(node.priority)) {
			return false;
		}
		// Filter by labels
		if (filters.labels?.length && !filters.labels.some((l) => node.labels.includes(l))) {
			return false;
		}
		return true;
	});

	const nodeIds = new Set(filteredNodes.map((n) => n.id));
	const filteredEdges = graph.edges.filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));
	const filteredUnsequenced = graph.unsequenced.filter((n) => nodeIds.has(n.id));

	return {
		...graph,
		nodes: filteredNodes,
		edges: filteredEdges,
		unsequenced: filteredUnsequenced,
	};
}

/**
 * Check if a status indicates completion
 */
function isCompleted(status: string): boolean {
	const lower = status.toLowerCase();
	return lower.includes("done") || lower.includes("complete");
}

/**
 * Get the status class for a node (for styling)
 */
function getStatusClass(status: string): string {
	const lower = status.toLowerCase();
	if (lower.includes("done") || lower.includes("complete")) return "done";
	if (lower.includes("progress")) return "inProgress";
	return "todo";
}

/**
 * Escape text for Mermaid labels
 */
function escapeLabel(text: string): string {
	return (
		text
			.replace(/"/g, "'")
			.replace(/\n/g, " ")
			.replace(/[[\](){}]/g, "") // Remove brackets that could break mermaid
			.substring(0, 35) + (text.length > 35 ? "..." : "")
	);
}

/**
 * Generate a Mermaid flowchart from the dependency graph
 */
export function generateMermaidFlowchart(graph: DependencyGraph): string {
	const lines: string[] = ["flowchart TB"];

	// Define node styles
	lines.push("  %% Node Styles");
	lines.push("  classDef done fill:#22c55e,stroke:#16a34a,color:#fff");
	lines.push("  classDef inProgress fill:#3b82f6,stroke:#2563eb,color:#fff");
	lines.push("  classDef todo fill:#6b7280,stroke:#4b5563,color:#fff");
	lines.push("  classDef highPriority stroke:#ef4444,stroke-width:3px");
	lines.push("");

	// Group by sequence for layout hints
	const bySequence = new Map<number | undefined, GraphNode[]>();

	// First add sequenced nodes
	for (const node of graph.nodes) {
		if (node.sequenceIndex !== undefined) {
			if (!bySequence.has(node.sequenceIndex)) bySequence.set(node.sequenceIndex, []);
			bySequence.get(node.sequenceIndex)?.push(node);
		}
	}

	// Add unsequenced nodes separately
	if (graph.unsequenced.length > 0) {
		bySequence.set(undefined, graph.unsequenced);
	}

	// Render nodes with subgraphs for sequences
	lines.push("  %% Nodes by Sequence");

	// Sort sequences by index
	const sortedSeqKeys = Array.from(bySequence.keys()).sort((a, b) => {
		if (a === undefined) return 1;
		if (b === undefined) return -1;
		return a - b;
	});

	for (const seqIndex of sortedSeqKeys) {
		const nodes = bySequence.get(seqIndex);
		if (!nodes || nodes.length === 0) continue;

		if (seqIndex !== undefined) {
			lines.push(`  subgraph Seq${seqIndex}["Sequence ${seqIndex}"]`);
		} else {
			lines.push('  subgraph Unsequenced["Unsequenced"]');
		}

		for (const node of nodes) {
			const label = escapeLabel(node.title);
			const shortId = node.id.replace("task-", "");
			lines.push(`    ${node.id}["#${shortId}: ${label}"]`);
		}

		lines.push("  end");
		lines.push("");
	}

	// Add edges
	if (graph.edges.length > 0) {
		lines.push("  %% Dependencies");
		for (const edge of graph.edges) {
			lines.push(`  ${edge.source} --> ${edge.target}`);
		}
		lines.push("");
	}

	// Apply status classes
	lines.push("  %% Apply Status Classes");
	for (const node of graph.nodes) {
		const statusClass = getStatusClass(node.status);
		lines.push(`  class ${node.id} ${statusClass}`);
	}

	// Apply high priority styling
	const highPriorityNodes = graph.nodes.filter((n) => n.priority === "high");
	if (highPriorityNodes.length > 0) {
		lines.push("");
		lines.push("  %% High Priority Styling");
		for (const node of highPriorityNodes) {
			lines.push(`  class ${node.id} highPriority`);
		}
	}

	return lines.join("\n");
}

/**
 * Extract task ID from a Mermaid node element ID
 * Mermaid generates IDs like "flowchart-task-001-123"
 */
export function extractTaskIdFromNodeId(nodeId: string): string | null {
	const match = nodeId.match(/(task-\d+)/);
	return match?.[1] ?? null;
}
