/**
 * Circuit breaker + backpressure for MCP tool calls.
 *
 * Circuit breaker: track p99 latency over a rolling window per tool.
 * If p99 exceeds CIRCUIT_TRIP_MS, tool is force-gated (async even for fast calls).
 * Resets automatically after RESET_WINDOW_MS of clean calls.
 *
 * Backpressure: global in-flight counter. If above QUEUE_CAPACITY,
 * return {busy: true, retryAfterMs} instead of queuing more work.
 */
import { nexusEventBus } from '../../../engines/event-bus.js';
// ─── Config ───────────────────────────────────────────────────────────────────
const WINDOW_SIZE = 50; // rolling sample count per tool
const CIRCUIT_TRIP_MS = 20_000; // p99 > 20s → trip circuit
const RESET_WINDOW_MS = 60_000; // stay tripped for 60s before retry
const QUEUE_CAPACITY = 100; // max concurrent in-flight tool calls
// ─── CircuitBreaker ───────────────────────────────────────────────────────────
class ToolCircuit {
    samples = [];
    state = 'closed';
    trippedAt = 0;
    record(durationMs) {
        this.samples.push(durationMs);
        if (this.samples.length > WINDOW_SIZE)
            this.samples.shift();
        this.evaluate();
    }
    /** Returns true if this tool should be force-gated (async even for fast calls). */
    isOpen() {
        if (this.state === 'closed')
            return false;
        if (this.state === 'open') {
            if (Date.now() - this.trippedAt >= RESET_WINDOW_MS) {
                this.state = 'half-open';
                return false;
            }
            return true;
        }
        // half-open: allow one attempt
        return false;
    }
    onSuccess() {
        if (this.state === 'half-open') {
            this.state = 'closed';
            this.samples = [];
        }
    }
    onFailure() {
        if (this.state === 'half-open') {
            this.trip();
        }
    }
    p99() {
        if (this.samples.length === 0)
            return 0;
        const sorted = [...this.samples].sort((a, b) => a - b);
        const idx = Math.floor(sorted.length * 0.99);
        return sorted[Math.min(idx, sorted.length - 1)];
    }
    evaluate() {
        if (this.state !== 'closed')
            return;
        if (this.samples.length >= 10 && this.p99() > CIRCUIT_TRIP_MS) {
            this.trip();
        }
    }
    trip() {
        this.state = 'open';
        this.trippedAt = Date.now();
    }
}
// ─── Singleton manager ────────────────────────────────────────────────────────
class CircuitManager {
    circuits = new Map();
    inFlight = 0;
    getCircuit(tool) {
        if (!this.circuits.has(tool))
            this.circuits.set(tool, new ToolCircuit());
        return this.circuits.get(tool);
    }
    /** Check backpressure BEFORE enqueuing. Returns retryAfterMs or 0 if OK. */
    checkCapacity() {
        return this.inFlight >= QUEUE_CAPACITY ? 5_000 : 0;
    }
    /** True if circuit is open (tool force-gated to async). */
    isForceAsync(tool) {
        return this.getCircuit(tool).isOpen();
    }
    /** Record a completed call. Pass durationMs for p99 tracking. */
    recordComplete(tool, durationMs, success) {
        const c = this.getCircuit(tool);
        c.record(durationMs);
        if (success)
            c.onSuccess();
        else
            c.onFailure();
        this.inFlight = Math.max(0, this.inFlight - 1);
        if (c.state === 'open') {
            nexusEventBus.emit('nexus.circuit-open', { tool, p99Ms: c.p99() });
        }
    }
    /** Call when work starts (increments in-flight). */
    recordStart(tool) {
        this.inFlight++;
        if (this.inFlight >= QUEUE_CAPACITY) {
            nexusEventBus.emit('nexus.circuit-tripped', { tool, reason: 'backpressure', queueDepth: this.inFlight });
        }
    }
    getStats(tool) {
        const c = this.getCircuit(tool);
        return {
            tool,
            state: c.state,
            p99Ms: c.p99(),
            sampleCount: c.samples?.length ?? 0,
            trippedAt: c.state !== 'closed' ? c.trippedAt : undefined,
        };
    }
    getAllStats() {
        return Array.from(this.circuits.keys()).map(t => this.getStats(t));
    }
    getInFlight() {
        return this.inFlight;
    }
}
let _manager = null;
export function getCircuitManager() {
    if (!_manager)
        _manager = new CircuitManager();
    return _manager;
}
export function checkBackpressure() {
    const retryAfterMs = getCircuitManager().checkCapacity();
    return retryAfterMs > 0 ? { ok: false, retryAfterMs } : { ok: true };
}
