// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * SPEC-686 Scenarios 3, 9, 10, 11:
 * Filter out specs whose dependencies have not been completed.
 *
 * - Scenario 3: specs with deps on non-done specs are excluded
 * - Scenario 9: cyclic dependencies → all cycle members excluded
 * - Scenario 10: malformed dependencies (non-array) treated as []
 * - Scenario 11: missing dependencies key treated as []
 *
 * @param specs - candidate specs to filter (typically approved only)
 * @param allSpecs - full spec list (for dependency status lookup)
 * @returns specs that are not blocked by any unmet or cyclic dependency
 */
export function filterBlocked(specs, allSpecs) {
    const statusById = new Map();
    for (const s of allSpecs) {
        statusById.set(s.id, s.status);
    }
    // Detect all specs involved in cycles via DFS
    const cycleMembers = detectCycleMembers(allSpecs);
    return specs.filter((spec) => {
        // Exclude any spec that is part of a cycle (Scenario 9)
        if (cycleMembers.has(spec.id)) {
            return false;
        }
        // Normalize dependencies defensively (Scenarios 10, 11)
        const deps = normalizeDeps(spec.dependencies);
        // Exclude if any dep is not done
        for (const depId of deps) {
            const depStatus = statusById.get(depId);
            if (depStatus !== 'done') {
                return false;
            }
        }
        return true;
    });
}
/**
 * SPEC-686 Scenario 9: Detect all spec IDs that are part of a dependency cycle.
 * Uses DFS iterative coloring (white/gray/black).
 */
export function detectCycleMembers(allSpecs) {
    // Build adjacency list (spec.id → dep IDs)
    const adj = new Map();
    for (const s of allSpecs) {
        adj.set(s.id, normalizeDeps(s.dependencies));
    }
    const WHITE = 0; // not visited
    const GRAY = 1; // in current path
    const BLACK = 2; // fully explored
    const color = new Map();
    const cycleMembers = new Set();
    for (const specId of adj.keys()) {
        color.set(specId, WHITE);
    }
    function dfs(nodeId, path) {
        color.set(nodeId, GRAY);
        path.push(nodeId);
        const neighbors = adj.get(nodeId) ?? [];
        for (const neighborId of neighbors) {
            if (!adj.has(neighborId)) {
                // dependency points to unknown spec — skip
                continue;
            }
            const neighborColor = color.get(neighborId) ?? WHITE;
            if (neighborColor === GRAY) {
                // Found a back-edge → cycle detected
                // Mark all nodes in the cycle path from neighborId onward
                const cycleStart = path.indexOf(neighborId);
                for (let i = cycleStart; i < path.length; i++) {
                    const pathNode = path[i];
                    if (pathNode !== undefined) {
                        cycleMembers.add(pathNode);
                    }
                }
                cycleMembers.add(neighborId);
            }
            else if (neighborColor === WHITE) {
                dfs(neighborId, path);
            }
        }
        path.pop();
        color.set(nodeId, BLACK);
    }
    for (const specId of adj.keys()) {
        if ((color.get(specId) ?? WHITE) === WHITE) {
            dfs(specId, []);
        }
    }
    return cycleMembers;
}
/**
 * Normalize a dependencies field defensively.
 * Handles: undefined, null, string (single dep), or any non-array value.
 */
function normalizeDeps(dependencies) {
    if (!dependencies) {
        return [];
    }
    if (Array.isArray(dependencies)) {
        return dependencies.filter((d) => typeof d === 'string');
    }
    if (typeof dependencies === 'string' && dependencies.trim()) {
        // Scenario 10: string treated as single dependency
        console.warn(`[planu] next-spec-resolver: dependencies is a string "${dependencies}", treating as single-element array`);
        return [dependencies];
    }
    console.warn('[planu] next-spec-resolver: dependencies is not an array, treating as []');
    return [];
}
//# sourceMappingURL=dependency-filter.js.map