"use strict";
/**
 * Directed Acyclic Graph (DAG) utilities
 * Used to analyze and order workflow steps based on dependencies
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DAG = void 0;
class DAG {
    nodes;
    constructor() {
        this.nodes = new Map();
    }
    /**
     * Add a step to the DAG
     */
    addStep(step) {
        if (this.nodes.has(step.id)) {
            throw new Error(`Duplicate step ID: ${step.id}`);
        }
        const node = {
            id: step.id,
            step,
            dependencies: step.depends_on || [],
            dependents: [],
        };
        this.nodes.set(step.id, node);
    }
    /**
     * Build the complete DAG from steps
     */
    build(steps) {
        // First pass: add all nodes
        for (const step of steps) {
            this.addStep(step);
        }
        // Second pass: build dependent relationships and validate dependencies
        for (const node of this.nodes.values()) {
            for (const depId of node.dependencies) {
                const depNode = this.nodes.get(depId);
                if (!depNode) {
                    throw new Error(`Step "${node.id}" depends on unknown step "${depId}"`);
                }
                depNode.dependents.push(node.id);
            }
        }
        // Validate no circular dependencies
        this.detectCycles();
    }
    /**
     * Detect circular dependencies using DFS
     */
    detectCycles() {
        const visited = new Set();
        const recursionStack = new Set();
        const visit = (nodeId, path) => {
            if (recursionStack.has(nodeId)) {
                const cycle = [...path, nodeId].join(' -> ');
                throw new Error(`Circular dependency detected: ${cycle}`);
            }
            if (visited.has(nodeId)) {
                return;
            }
            visited.add(nodeId);
            recursionStack.add(nodeId);
            const node = this.nodes.get(nodeId);
            if (node) {
                for (const depId of node.dependencies) {
                    visit(depId, [...path, nodeId]);
                }
            }
            recursionStack.delete(nodeId);
        };
        for (const nodeId of this.nodes.keys()) {
            if (!visited.has(nodeId)) {
                visit(nodeId, []);
            }
        }
    }
    /**
     * Get topologically sorted steps (dependencies first)
     */
    topologicalSort() {
        const sorted = [];
        const visited = new Set();
        const temp = new Set();
        const visit = (nodeId) => {
            if (visited.has(nodeId)) {
                return;
            }
            if (temp.has(nodeId)) {
                throw new Error(`Circular dependency detected at node: ${nodeId}`);
            }
            temp.add(nodeId);
            const node = this.nodes.get(nodeId);
            if (node) {
                // Visit dependencies first
                for (const depId of node.dependencies) {
                    visit(depId);
                }
                temp.delete(nodeId);
                visited.add(nodeId);
                sorted.push(node.step);
            }
        };
        for (const nodeId of this.nodes.keys()) {
            if (!visited.has(nodeId)) {
                visit(nodeId);
            }
        }
        return sorted;
    }
    /**
     * Get steps that have no dependencies (entry points)
     */
    getEntryPoints() {
        const entryPoints = [];
        for (const node of this.nodes.values()) {
            if (node.dependencies.length === 0) {
                entryPoints.push(node.step);
            }
        }
        return entryPoints;
    }
    /**
     * Get steps that have no dependents (exit points)
     */
    getExitPoints() {
        const exitPoints = [];
        for (const node of this.nodes.values()) {
            if (node.dependents.length === 0) {
                exitPoints.push(node.step);
            }
        }
        return exitPoints;
    }
    /**
     * Get all steps that depend on a given step
     */
    getDependents(stepId) {
        const node = this.nodes.get(stepId);
        if (!node) {
            throw new Error(`Step not found: ${stepId}`);
        }
        return node.dependents
            .map(id => this.nodes.get(id)?.step)
            .filter((step) => step !== undefined);
    }
    /**
     * Get all steps that a given step depends on
     */
    getDependencies(stepId) {
        const node = this.nodes.get(stepId);
        if (!node) {
            throw new Error(`Step not found: ${stepId}`);
        }
        return node.dependencies
            .map(id => this.nodes.get(id)?.step)
            .filter((step) => step !== undefined);
    }
    /**
     * Get execution levels (steps that can run in parallel)
     */
    getExecutionLevels() {
        const levels = [];
        const visited = new Set();
        const nodeMap = new Map(this.nodes);
        while (visited.size < this.nodes.size) {
            const currentLevel = [];
            const currentLevelIds = [];
            // Find all nodes whose dependencies have been visited
            for (const [nodeId, node] of nodeMap.entries()) {
                if (visited.has(nodeId)) {
                    continue;
                }
                const allDepsVisited = node.dependencies.every(depId => visited.has(depId));
                if (allDepsVisited) {
                    currentLevel.push(node.step);
                    currentLevelIds.push(nodeId);
                }
            }
            if (currentLevel.length === 0 && visited.size < this.nodes.size) {
                throw new Error('Unable to resolve execution order - possible circular dependency');
            }
            // Mark all nodes in this level as visited AFTER finding all of them
            for (const nodeId of currentLevelIds) {
                visited.add(nodeId);
            }
            if (currentLevel.length > 0) {
                levels.push(currentLevel);
            }
        }
        return levels;
    }
    /**
     * Get the node for a step
     */
    getNode(stepId) {
        return this.nodes.get(stepId);
    }
    /**
     * Get all nodes
     */
    getAllNodes() {
        return Array.from(this.nodes.values());
    }
}
exports.DAG = DAG;
//# sourceMappingURL=dag.js.map