import { filterBlocked } from './dependency-filter.js';
import { hasFileConflict } from './conflict-filter.js';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
export const MAX_WAVES = 10;
export const MAX_PARALLEL_PER_WAVE = 5;
export const ORCHESTRATION_TRIGGER_THRESHOLD = 2;
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * SPEC-686 Scenarios 12-15: Build a wave-based orchestration plan for all approved specs.
 *
 * Returns null when fewer than ORCHESTRATION_TRIGGER_THRESHOLD approved specs exist.
 * Algorithm:
 *   1. Filter approved specs only.
 *   2. Build dependency DAG; topologically sort (Kahn's algorithm).
 *   3. For each topological "level": split into wave-groups by file-conflict
 *      (no two specs in same wave touch the same file).
 *   4. Cap at MAX_WAVES and MAX_PARALLEL_PER_WAVE.
 *   5. Increment planVersion from previous session.json value.
 */
export async function resolveOrchestrationPlan(projectPath, allSpecs) {
    const approved = allSpecs.filter((s) => s.status === 'approved');
    if (approved.length < ORCHESTRATION_TRIGGER_THRESHOLD) {
        return null;
    }
    const unblocked = filterBlocked(approved, allSpecs);
    const { inDegree, dependents } = buildAdjacency(unblocked);
    const levels = buildTopologicalLevels(unblocked, inDegree, dependents);
    const waves = [];
    const resolvedIds = new Set();
    for (const level of levels) {
        if (waves.length >= MAX_WAVES) {
            break;
        }
        const waveGroups = groupByConflict(level);
        for (const group of waveGroups) {
            if (waves.length >= MAX_WAVES) {
                break;
            }
            const wave = group.map((spec) => buildWaveEntry(spec, group, resolvedIds));
            waves.push(wave);
            for (const spec of group) {
                resolvedIds.add(spec.id);
            }
        }
    }
    const summary = buildSummary(approved.length, waves);
    const planVersion = await readPlanVersion(projectPath);
    return {
        planVersion: planVersion + 1,
        generatedAt: new Date().toISOString(),
        waves,
        summary,
    };
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
function buildAdjacency(unblocked) {
    const unblockedIds = new Set(unblocked.map((s) => s.id));
    const inDegree = new Map();
    const dependents = new Map();
    for (const spec of unblocked) {
        inDegree.set(spec.id, 0);
        dependents.set(spec.id, []);
    }
    for (const spec of unblocked) {
        const deps = Array.isArray(spec.dependencies) ? spec.dependencies : [];
        for (const depId of deps) {
            if (unblockedIds.has(depId)) {
                inDegree.set(spec.id, (inDegree.get(spec.id) ?? 0) + 1);
                const existing = dependents.get(depId) ?? [];
                existing.push(spec.id);
                dependents.set(depId, existing);
            }
        }
    }
    return { inDegree, dependents };
}
function buildTopologicalLevels(unblocked, inDegree, dependents) {
    const specById = new Map();
    for (const s of unblocked) {
        specById.set(s.id, s);
    }
    const levels = [];
    let queue = unblocked.filter((s) => (inDegree.get(s.id) ?? 0) === 0);
    while (queue.length > 0) {
        levels.push([...queue]);
        const nextQueue = [];
        for (const spec of queue) {
            for (const dependentId of dependents.get(spec.id) ?? []) {
                const newDegree = (inDegree.get(dependentId) ?? 1) - 1;
                inDegree.set(dependentId, newDegree);
                if (newDegree === 0) {
                    const s = specById.get(dependentId);
                    if (s) {
                        nextQueue.push(s);
                    }
                }
            }
        }
        queue = nextQueue;
    }
    return levels;
}
function buildWaveEntry(spec, group, resolvedIds) {
    return {
        specId: spec.id,
        title: spec.title,
        model: normalizeModel(spec.model),
        parallelSafe: group.length > 1,
        dependsOn: (Array.isArray(spec.dependencies) ? spec.dependencies : []).filter((d) => resolvedIds.has(d)),
    };
}
function groupByConflict(specs) {
    const groups = [];
    const assigned = new Set();
    for (const spec of specs) {
        if (assigned.has(spec.id)) {
            continue;
        }
        let placed = false;
        for (const group of groups) {
            if (group.length >= MAX_PARALLEL_PER_WAVE) {
                continue;
            }
            if (!group.some((other) => hasFileConflict(spec, other))) {
                group.push(spec);
                assigned.add(spec.id);
                placed = true;
                break;
            }
        }
        if (!placed) {
            groups.push([spec]);
            assigned.add(spec.id);
        }
    }
    return groups;
}
function buildSummary(approvedCount, waves) {
    const waveParts = waves.map((wave, i) => {
        const ids = wave.map((e) => e.specId).join(', ');
        const label = wave.length > 1 ? 'parallel' : 'sequential';
        return `Wave ${String(i + 1)} ${label} [${ids}]`;
    });
    return `${String(approvedCount)} specs approved. Plan: ${waveParts.join(', ')}`;
}
function normalizeModel(model) {
    if (model === 'haiku' || model === 'sonnet' || model === 'opus') {
        return model;
    }
    return 'sonnet';
}
async function readPlanVersion(projectPath) {
    try {
        const raw = await readFile(join(projectPath, 'planu', 'session.json'), 'utf-8');
        const index = JSON.parse(raw);
        return index.orchestrationPlan?.planVersion ?? 0;
    }
    catch {
        return 0;
    }
}
//# sourceMappingURL=orchestration-planner.js.map