// engine/spec-dependency-graph/graph-builder.ts — SPEC-746
// Builds a SpecGraph (nodes + edges + diagnostics) from a list of specs.
import { parseSupersedes, parseDependsOn } from './supersedes-parser.js';
// ---------------------------------------------------------------------------
// Internal cycle detection (DFS on supersedes subgraph)
// ---------------------------------------------------------------------------
function detectCyclesInSupersedes(supersededByMap) {
    const diagnostics = [];
    const visited = new Set();
    const inStack = new Set();
    const stack = [];
    function dfs(node) {
        if (inStack.has(node)) {
            const cycleStart = stack.indexOf(node);
            if (cycleStart !== -1) {
                diagnostics.push({
                    code: 'SUPERSEDES_CYCLE',
                    cycle: [...stack.slice(cycleStart), node],
                });
            }
            return;
        }
        if (visited.has(node)) {
            return;
        }
        visited.add(node);
        inStack.add(node);
        stack.push(node);
        // Walk supersedes edges: for each spec that supersedes `node`, follow the chain
        for (const [superseder, superseded] of supersededByMap) {
            if (superseded === node) {
                dfs(superseder);
            }
        }
        stack.pop();
        inStack.delete(node);
    }
    for (const node of supersededByMap.keys()) {
        dfs(node);
    }
    return diagnostics;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Build a full SpecGraph from an array of specs.
 * Ingests `supersedes`, `depends_on`, and legacy `dependencies` fields.
 */
export function buildSpecGraph(specs) {
    const nodes = [];
    const edges = [];
    const specIds = new Set(specs.map((s) => s.id));
    // Map: superseded spec → superseder spec (for supersededBy annotation)
    const supersededByMap = new Map();
    for (const spec of specs) {
        nodes.push({
            id: spec.id,
            status: spec.status,
        });
        // dependency edges (legacy `dependencies` field)
        for (const dep of spec.dependencies) {
            if (specIds.has(dep)) {
                edges.push({ from: spec.id, to: dep, kind: 'dependency' });
            }
        }
        // depends_on edges (alias, merged with dependencies inside parseDependsOn)
        const dependsOn = parseDependsOn(spec);
        const alreadyAdded = new Set(spec.dependencies);
        for (const dep of dependsOn) {
            if (specIds.has(dep) && !alreadyAdded.has(dep)) {
                edges.push({ from: spec.id, to: dep, kind: 'depends_on' });
                alreadyAdded.add(dep);
            }
        }
        // supersedes edges
        for (const sup of parseSupersedes(spec)) {
            edges.push({ from: spec.id, to: sup, kind: 'supersedes' });
            supersededByMap.set(sup, spec.id);
        }
    }
    // Annotate supersededBy on nodes
    for (const node of nodes) {
        const superseder = supersededByMap.get(node.id);
        if (superseder) {
            node.supersededBy = superseder;
        }
    }
    // Detect cycles in supersedes subgraph
    const diagnostics = detectCyclesInSupersedes(supersededByMap);
    return { nodes, edges, diagnostics };
}
//# sourceMappingURL=graph-builder.js.map