// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function getEstHours(spec) {
    return Math.max(spec.estimation.devHours || 1, 0.5);
}
// ---------------------------------------------------------------------------
// Simulate parallel execution
// ---------------------------------------------------------------------------
/**
 * Assign specs to agents in a greedy fashion (shortest job first heuristic).
 * Returns an execution timeline with Gantt chart.
 */
export function simulateParallelExecution(specs, numAgents) {
    if (specs.length === 0) {
        return { agentAssignments: [], totalHours: 0, criticalPath: [], ganttChart: '' };
    }
    const clampedAgents = Math.max(1, Math.min(numAgents, 20));
    // Sort by estimated hours descending (longest first — better load balancing)
    const sorted = [...specs].sort((a, b) => getEstHours(b) - getEstHours(a));
    const agentEnd = new Array(clampedAgents).fill(0);
    const assignments = [];
    for (const spec of sorted) {
        // Assign to agent with earliest end time
        let minIdx = 0;
        for (let i = 1; i < clampedAgents; i++) {
            if ((agentEnd[i] ?? 0) < (agentEnd[minIdx] ?? 0)) {
                minIdx = i;
            }
        }
        const startHour = agentEnd[minIdx] ?? 0;
        const endHour = startHour + getEstHours(spec);
        agentEnd[minIdx] = endHour;
        assignments.push({ agentId: minIdx + 1, specId: spec.id, startHour, endHour });
    }
    const totalHours = Math.max(...agentEnd);
    // Critical path: specs assigned to the agent with longest total time
    const maxAgent = agentEnd.indexOf(totalHours) + 1;
    const criticalPath = assignments
        .filter((a) => a.agentId === maxAgent)
        .sort((a, b) => a.startHour - b.startHour)
        .map((a) => a.specId);
    const ganttChart = buildGanttChart(assignments, clampedAgents, totalHours);
    return { agentAssignments: assignments, totalHours, criticalPath, ganttChart };
}
// ---------------------------------------------------------------------------
// ASCII Gantt chart
// ---------------------------------------------------------------------------
function buildGanttChart(assignments, numAgents, totalHours) {
    const cols = Math.min(Math.ceil(totalHours), 40);
    const scale = cols / Math.max(totalHours, 1);
    const lines = ['Gantt Chart (each char ≈ ' + (totalHours / cols).toFixed(1) + 'h)'];
    lines.push('Agent | ' + Array.from({ length: cols }, (_, i) => String((i + 1) % 10)).join(''));
    lines.push('------+' + '-'.repeat(cols));
    for (let agent = 1; agent <= numAgents; agent++) {
        const agentTasks = assignments.filter((a) => a.agentId === agent);
        const row = new Array(cols).fill(' ');
        for (const task of agentTasks) {
            const start = Math.floor(task.startHour * scale);
            const end = Math.min(Math.ceil(task.endHour * scale), cols);
            for (let c = start; c < end; c++) {
                row[c] = '█';
            }
        }
        lines.push(`  A${String(agent).padStart(3, ' ')} | ${row.join('')}`);
    }
    return lines.join('\n');
}
// ---------------------------------------------------------------------------
// Critical path analyzer
// ---------------------------------------------------------------------------
/** Find the longest chain of specs by estimated hours. */
export function analyzeCriticalPath(specs) {
    if (specs.length === 0) {
        return { criticalPath: [], totalHours: 0, longestChainLength: 0 };
    }
    // Without explicit dependency graph, we find the max-sum subset
    // by treating each spec as independent and picking the top contributors
    const sorted = [...specs].sort((a, b) => getEstHours(b) - getEstHours(a));
    const totalHours = specs.reduce((acc, s) => acc + getEstHours(s), 0);
    const criticalPath = sorted.slice(0, Math.min(5, sorted.length)).map((s) => s.id);
    return {
        criticalPath,
        totalHours,
        longestChainLength: criticalPath.length,
    };
}
// ---------------------------------------------------------------------------
// Parallel efficiency score
// ---------------------------------------------------------------------------
/** Calculate parallel utilization efficiency. */
export function computeParallelEfficiency(specs, numAgents) {
    const timeline = simulateParallelExecution(specs, numAgents);
    const totalSpecHours = specs.reduce((acc, s) => acc + getEstHours(s), 0);
    const totalAgentHours = timeline.totalHours * numAgents;
    const score = totalAgentHours > 0 ? totalSpecHours / totalAgentHours : 0;
    let interpretation;
    if (score >= 0.85) {
        interpretation = 'Excellent — agents are well utilized.';
    }
    else if (score >= 0.65) {
        interpretation = 'Good — minor idle time exists.';
    }
    else if (score >= 0.4) {
        interpretation = 'Fair — significant idle time. Consider fewer agents or rebalancing tasks.';
    }
    else {
        interpretation = 'Poor — too many agents for the workload. Reduce agent count.';
    }
    return {
        score,
        totalSpecHours,
        totalAgentHours,
        numAgents,
        interpretation,
    };
}
// ---------------------------------------------------------------------------
// Optimize scheduling
// ---------------------------------------------------------------------------
/** Suggest an optimal spec ordering to minimize idle time. */
export function optimizeSpecScheduling(specs, numAgents) {
    if (specs.length === 0) {
        return { order: [], expectedIdleHours: 0, suggestion: 'No specs to schedule.' };
    }
    const clampedAgents = Math.max(1, Math.min(numAgents, 20));
    // Sort longest first to minimize idle time (LPT algorithm)
    const ordered = [...specs].sort((a, b) => getEstHours(b) - getEstHours(a));
    // Calculate idle time with this ordering
    const agentEnd = new Array(clampedAgents).fill(0);
    for (const spec of ordered) {
        let minIdx = 0;
        for (let i = 1; i < clampedAgents; i++) {
            if ((agentEnd[i] ?? 0) < (agentEnd[minIdx] ?? 0)) {
                minIdx = i;
            }
        }
        agentEnd[minIdx] = (agentEnd[minIdx] ?? 0) + getEstHours(spec);
    }
    const totalHours = Math.max(...agentEnd);
    const totalWork = agentEnd.reduce((acc, v) => acc + v, 0);
    const maxWork = totalHours * clampedAgents;
    const expectedIdleHours = maxWork - totalWork;
    const suggestion = expectedIdleHours < 1
        ? `Optimal: use ${String(clampedAgents)} agents. Expected idle time is minimal (${expectedIdleHours.toFixed(1)}h).`
        : `Use ${String(clampedAgents)} agents. Expected idle time: ${expectedIdleHours.toFixed(1)}h. ` +
            `Consider processing ${ordered[0]?.id ?? 'large specs'} first.`;
    return {
        order: ordered.map((s) => s.id),
        expectedIdleHours,
        suggestion,
    };
}
//# sourceMappingURL=spec-scheduler.js.map