/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { spawn } from 'node:child_process';
import { renderPacketAsPrompt } from './workflow-delegation.js';
import { buildInvocation } from './workflow-executor.js';
// ─── Concurrency control ────────────────────────────────────────────────────
const DEFAULT_MAX_CONCURRENT = 2;
let activeSpawns = 0;
let maxConcurrent = DEFAULT_MAX_CONCURRENT;
const spawnQueue = [];
export function setMaxConcurrentSpawns(max) {
    maxConcurrent = Math.max(1, max);
}
export function getSpawnPoolStatus() {
    return { active: activeSpawns, queued: spawnQueue.length, max: maxConcurrent, circuitOpen: isCircuitOpen() };
}
export function acquireSpawnSlot() {
    if (isCircuitOpen()) {
        return Promise.reject(new Error(`Circuit breaker open — ${consecutiveFailures} consecutive failures. Cooldown until ${new Date(circuitOpenedAt + CIRCUIT_COOLDOWN_MS).toISOString()}`));
    }
    if (activeSpawns < maxConcurrent) {
        activeSpawns++;
        return Promise.resolve();
    }
    return new Promise((resolve) => {
        spawnQueue.push(() => {
            activeSpawns++;
            resolve();
        });
    });
}
export function releaseSpawnSlot() {
    activeSpawns--;
    const next = spawnQueue.shift();
    if (next)
        next();
}
export function resetSpawnPool() {
    activeSpawns = 0;
    maxConcurrent = DEFAULT_MAX_CONCURRENT;
    spawnQueue.length = 0;
    consecutiveFailures = 0;
    circuitOpenedAt = 0;
}
// ─── Circuit breaker ───────────────────────────────────────────────────────
const CIRCUIT_FAILURE_THRESHOLD = 3;
const CIRCUIT_COOLDOWN_MS = 30_000;
let consecutiveFailures = 0;
let circuitOpenedAt = 0;
function isCircuitOpen() {
    if (consecutiveFailures < CIRCUIT_FAILURE_THRESHOLD)
        return false;
    if (Date.now() - circuitOpenedAt >= CIRCUIT_COOLDOWN_MS) {
        consecutiveFailures = 0;
        circuitOpenedAt = 0;
        return false;
    }
    return true;
}
export function recordSpawnSuccess() {
    consecutiveFailures = 0;
}
export function recordSpawnFailure() {
    consecutiveFailures++;
    if (consecutiveFailures >= CIRCUIT_FAILURE_THRESHOLD && circuitOpenedAt === 0) {
        circuitOpenedAt = Date.now();
    }
}
export async function spawnDelegated(runtime, packet, cwd, gates, timeoutMs) {
    await acquireSpawnSlot();
    try {
        const result = await spawnProcess(runtime, packet, cwd, gates, timeoutMs);
        if (result.exitCode === 0) {
            recordSpawnSuccess();
        }
        else {
            recordSpawnFailure();
        }
        return result;
    }
    catch (err) {
        recordSpawnFailure();
        throw err;
    }
    finally {
        releaseSpawnSlot();
    }
}
function spawnProcess(runtime, packet, cwd, gates, timeoutMs) {
    const prompt = renderPacketAsPrompt(packet);
    const invocation = buildInvocation(runtime, packet.phase, packet.taskId, cwd, { gates });
    const args = replacePromptInArgs(invocation.args, prompt);
    return new Promise((resolve) => {
        const startMs = Date.now();
        const stdoutChunks = [];
        let child;
        try {
            child = spawn(invocation.command, args, { cwd, stdio: ['inherit', 'pipe', 'pipe'] });
        }
        catch {
            resolve({
                exitCode: 127,
                stdout: runtimeNotFoundMessage(runtime),
                elapsedMs: Date.now() - startMs,
            });
            return;
        }
        let settled = false;
        child.on('error', (err) => {
            if (settled)
                return;
            settled = true;
            const msg = err.code === 'ENOENT' ? runtimeNotFoundMessage(runtime) : `Spawn error: ${err.message}`;
            resolve({ exitCode: 127, stdout: msg, elapsedMs: Date.now() - startMs });
        });
        const killTimer = setTimeout(() => {
            if (settled)
                return;
            settled = true;
            child.kill('SIGTERM');
            resolve({ exitCode: 124, stdout: Buffer.concat(stdoutChunks).toString(), elapsedMs: Date.now() - startMs });
        }, timeoutMs);
        child.stdout?.on('data', (chunk) => {
            stdoutChunks.push(chunk);
        });
        child.stderr?.on('data', (chunk) => {
            process.stderr.write(chunk);
        });
        child.on('close', (code) => {
            if (settled)
                return;
            settled = true;
            clearTimeout(killTimer);
            resolve({ exitCode: code ?? 1, stdout: Buffer.concat(stdoutChunks).toString(), elapsedMs: Date.now() - startMs });
        });
    });
}
const RUNTIME_INSTALL_HINTS = {
    claude: 'npm install -g @anthropic-ai/claude-code',
    cursor: 'Install Cursor from https://cursor.com and enable the CLI',
    codex: 'npm install -g @openai/codex',
    agentforce: 'Use Agentforce in Salesforce IDE or Developer Console',
    windsurf: 'Install Windsurf from https://windsurf.com',
};
function runtimeNotFoundMessage(runtime) {
    return [
        `Runtime "${runtime}" is not installed or not in PATH.`,
        '',
        `To install: ${RUNTIME_INSTALL_HINTS[runtime]}`,
        '',
        'After installing, verify with:',
        `  which ${runtime}`,
    ].join('\n');
}
function replacePromptInArgs(args, newPrompt) {
    const result = [...args];
    const printIdx = result.indexOf('--print');
    if (printIdx !== -1 && printIdx + 1 < result.length) {
        result[printIdx + 1] = newPrompt;
    }
    else {
        const lastIdx = result.length - 1;
        if (lastIdx >= 0)
            result[lastIdx] = newPrompt;
    }
    return result;
}
//# sourceMappingURL=spawn-pool.js.map