import { spawn } from 'node:child_process';
import path from 'node:path';
import { createReleaseGateHermeticEnv } from './release-gate-hermetic-env.js';
import { writeReleaseGateJson } from './release-gate-report.js';
import { guardedProcessKill, guardContextForRoute } from '../safety/mutation-guard.js';
import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
const DISALLOWED_BATCH_RESOURCES = new Set(['zellij-real', 'git-worktree', 'local-llm-real', 'remote-model-real', 'publish', 'global-config', 'timing-sensitive']);
export function isReleaseGateBatchable(gate) {
    if (gate.side_effect !== 'hermetic')
        return false;
    if (!gate.resource.includes('cpu-light') || !gate.resource.includes('fs-read'))
        return false;
    return gate.resource.every((resource) => resource === 'cpu-light' || resource === 'fs-read') && !gate.resource.some((resource) => DISALLOWED_BATCH_RESOURCES.has(resource));
}
export async function runReleaseGateBatch(root, gates, input = {}) {
    const concurrency = Math.max(1, Math.floor(Number(input.concurrency || process.env.SKS_RELEASE_BATCH_CONCURRENCY || 4)));
    const runId = `rgb-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
    const reportRoot = input.reportRoot || path.join(root, '.sneakoscope', 'reports', 'release-gate-batches', runId);
    const nonBatchable = gates.filter((gate) => !isReleaseGateBatchable(gate));
    if (nonBatchable.length) {
        return {
            schema: 'sks.release-gate-batch-result.v1',
            ok: false,
            batch_size: gates.length,
            completed: 0,
            failed: nonBatchable.length,
            results: nonBatchable.map((gate) => ({ id: gate.id, ok: false, exit_code: null, signal: null, timed_out: false, duration_ms: 0 }))
        };
    }
    const queue = [...gates];
    const results = [];
    const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
        while (queue.length) {
            const gate = queue.shift();
            if (!gate)
                continue;
            const result = await runOne(root, runId, reportRoot, gate);
            results.push(result);
            if (input.reportRoot)
                writeChildResult(input.reportRoot, result);
        }
    });
    await Promise.all(workers);
    const failed = results.filter((row) => !row.ok).length;
    return {
        schema: 'sks.release-gate-batch-result.v1',
        ok: failed === 0,
        batch_size: gates.length,
        completed: results.length - failed,
        failed,
        results
    };
}
function runOne(root, runId, reportRoot, gate) {
    const started = Date.now();
    const hermetic = createReleaseGateHermeticEnv({ root, runId, gate, reportRoot });
    return new Promise((resolve) => {
        const child = spawn(gate.command, { cwd: root, shell: true, env: hermetic.env, stdio: ['ignore', 'ignore', 'ignore'], detached: process.platform !== 'win32' });
        let timedOut = false;
        let timeoutCleanup = null;
        const timer = setTimeout(() => {
            timedOut = true;
            timeoutCleanup = cleanupTimedOutGateProcessTree(root, child);
        }, gate.timeout_ms);
        timer.unref?.();
        child.on('close', (code, signal) => {
            void (async () => {
                clearTimeout(timer);
                if (timeoutCleanup)
                    await timeoutCleanup;
                const exitCode = timedOut ? 124 : code;
                resolve({ id: gate.id, ok: exitCode === 0, exit_code: exitCode, signal, timed_out: timedOut, duration_ms: Date.now() - started, report_dir: hermetic.report_dir });
            })();
        });
    });
}
async function cleanupTimedOutGateProcessTree(root, child) {
    await killGateProcessTree(root, child, 'SIGTERM');
    await sleep(1500);
    await killGateProcessTree(root, child, 'SIGKILL');
    await sleep(100);
}
async function killGateProcessTree(root, child, signal) {
    if (!child.pid)
        return;
    const pid = process.platform !== 'win32' ? -child.pid : child.pid;
    const contract = createRequestedScopeContract({
        route: 'release:gate-batch-runner',
        userRequest: 'Terminate only the batched release gate child process tree after its configured timeout.',
        projectRoot: root,
        overrides: { codex_app_process: true }
    });
    try {
        await guardedProcessKill(guardContextForRoute(root, contract, 'release gate batch timeout cleanup'), pid, { signal, confirmed: true });
    }
    catch {
        try {
            child.kill(signal);
        }
        catch { }
    }
}
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
function writeChildResult(reportRoot, result) {
    const dir = result.report_dir || path.join(reportRoot, result.id.replace(/[^A-Za-z0-9_.:-]/g, '_'));
    writeReleaseGateJson(path.join(dir, 'result.json'), {
        schema: 'sks.release-gate-batch-child-result.v1',
        ...result
    });
}
//# sourceMappingURL=release-gate-batch-runner.js.map