// engine/migration/plan-generator.ts — Migration plan generation (SPEC-028a)
// Builds ordered migration specs and detects circular dependencies
// --- Topological sort + cycle detection ---
function topologicalSort(components) {
    const idMap = new Map(components.map((c) => [c.id, c]));
    const visited = new Set();
    const inStack = new Set();
    const order = [];
    const cycles = [];
    function dfs(id, path) {
        if (inStack.has(id)) {
            const cycleStart = path.indexOf(id);
            const cycle = path.slice(cycleStart);
            cycle.push(id);
            /* v8 ignore start */
            const first = cycle[0] ?? id;
            const secondToLast = cycle[cycle.length - 2] ?? id;
            /* v8 ignore stop */
            cycles.push({
                cycle,
                breakStrategy: `Extract interface/contract between ${first} and ${secondToLast} to break the cycle during migration`,
            });
            return;
        }
        if (visited.has(id)) {
            return;
        }
        visited.add(id);
        inStack.add(id);
        path.push(id);
        const comp = idMap.get(id);
        if (comp) {
            for (const dep of comp.dependsOn) {
                dfs(dep, [...path]);
            }
        }
        inStack.delete(id);
        order.push(id);
    }
    for (const comp of components) {
        if (!visited.has(comp.id)) {
            dfs(comp.id, []);
        }
    }
    return { order, cycles };
}
// --- Phase building ---
function buildPhases(components, sortedOrder) {
    const idMap = new Map(components.map((c) => [c.id, c]));
    // Phase 1: no dependencies (utils, models, helpers)
    const phase1 = sortedOrder
        .map((id) => idMap.get(id))
        .filter((c) => c !== undefined)
        .filter((c) => c.dependsOn.length === 0)
        .map((c) => c.id);
    // Phase 2: business logic (depends only on phase 1)
    /* v8 ignore start */
    const phase2 = sortedOrder
        .map((id) => idMap.get(id))
        .filter((c) => c !== undefined &&
        c.dependsOn.length > 0 &&
        c.dependsOn.every((dep) => phase1.includes(dep)) &&
        (c.type === 'service' || c.type === 'module'))
        .map((c) => c.id);
    /* v8 ignore stop */
    // Phase 3: entry points and remaining
    const phase3 = sortedOrder.filter((id) => !phase1.includes(id) && !phase2.includes(id));
    const phases = [];
    if (phase1.length > 0) {
        phases.push({
            order: 1,
            name: 'Foundation — utils, models, helpers',
            description: 'Components with no dependencies — safest to migrate first',
            components: phase1,
        });
    }
    if (phase2.length > 0) {
        phases.push({
            order: 2,
            name: 'Business Logic — services, modules',
            description: 'Core business logic components that depend on foundation layer',
            components: phase2,
        });
    }
    if (phase3.length > 0) {
        phases.push({
            order: 3,
            name: 'Entry Points — handlers, API, CLI',
            description: 'External-facing components migrated last',
            components: phase3,
        });
    }
    return phases;
}
// --- Risk factor calculation ---
function calculateRiskFactor(components, hasTests) {
    let factor = 1.0;
    if (!hasTests) {
        factor += 0.5;
    }
    const hotCount = components.filter((c) => c.temperature === 'hot').length;
    factor += hotCount * 0.15;
    const warmCount = components.filter((c) => c.temperature === 'warm').length;
    factor += warmCount * 0.05;
    return Math.min(factor, 2.5);
}
function estimateHours(comp) {
    const base = Math.ceil(comp.linesOfCode / 50);
    const complexityMult = comp.temperature === 'hot' ? 3 : comp.temperature === 'warm' ? 2 : 1;
    return Math.max(1, base * complexityMult);
}
function mapComponentRisk(comp) {
    if (comp.temperature === 'hot') {
        return 'high';
    }
    if (comp.temperature === 'warm') {
        return 'medium';
    }
    return 'low';
}
// --- Public API ---
export function generateMigrationPlan(params) {
    const { sourceStack, targetStack, components, hasTests } = params;
    const { order, cycles } = topologicalSort(components);
    const phases = buildPhases(components, order);
    const idMap = new Map(components.map((c) => [c.id, c]));
    const specs = order
        .map((id) => idMap.get(id))
        .filter((c) => c !== undefined)
        .map((c, idx) => ({
        specId: `migration-${c.id}`,
        title: `Migrate: ${c.name}`,
        sourceComponent: c.path,
        targetStack,
        applicableEquivalences: [],
        parityChecklist: [
            `All public functions/methods of ${c.name} behave identically`,
            `Same error types and messages`,
            `Same side effects (DB writes, events, external calls)`,
        ],
        estimatedHours: estimateHours(c),
        risk: mapComponentRisk(c),
        order: idx + 1,
    }));
    const totalEstimatedHours = specs.reduce((sum, s) => sum + s.estimatedHours, 0);
    const riskFactor = calculateRiskFactor(components, hasTests);
    return {
        sourceStack,
        targetStack,
        phases,
        specs,
        circularDependencies: cycles,
        totalEstimatedHours,
        riskFactor,
        adjustedHours: Math.ceil(totalEstimatedHours * riskFactor),
        generatedAt: new Date().toISOString(),
    };
}
//# sourceMappingURL=plan-generator.js.map