/**
 * Fleet-level cost circuit breaker.
 * Pauses all new cycles when rolling spend exceeds threshold.
 * @public
 */
const DEFAULT_CONFIG = {
    thresholdUsd: 10.0,
    windowMs: 3_600_000,
    cooldownMs: 300_000,
};
export class FleetCircuitBreaker {
    cfg;
    _state = "closed";
    _openedAt;
    _halfOpenConsumed = false;
    _window = [];
    _now;
    constructor(config, _now = Date.now) {
        this.cfg = { ...DEFAULT_CONFIG, ...config };
        this._now = _now;
    }
    /** Returns true if a new cycle should be ALLOWED. False = circuit open. */
    canProceed() {
        this._evict();
        const now = this._now();
        if (this._state === "open") {
            if (this._openedAt !== undefined && now - this._openedAt >= this.cfg.cooldownMs) {
                this._state = "half-open";
                this._halfOpenConsumed = false;
            }
            else {
                return false;
            }
        }
        if (this._state === "half-open") {
            if (this._halfOpenConsumed) {
                return false;
            }
            this._halfOpenConsumed = true;
            return true;
        }
        // closed: check threshold
        return this._windowSpend() <= this.cfg.thresholdUsd;
    }
    /** Record a cycle's cost. May trip the breaker. */
    recordCost(costUsd) {
        const now = this._now();
        this._window.push({ ts: now, costUsd });
        this._evict();
        if (this._state === "closed") {
            if (this._windowSpend() > this.cfg.thresholdUsd) {
                this._trip(now);
            }
        }
        else if (this._state === "half-open") {
            // Probe completed — evaluate
            const spend = this._windowSpend();
            if (spend > this.cfg.thresholdUsd) {
                this._trip(now);
            }
            else {
                // probe succeeded
                this._state = "closed";
                this._openedAt = undefined;
                this._halfOpenConsumed = false;
            }
        }
        // If open: record but don't change state
    }
    /** Current state. */
    get state() {
        return this._state;
    }
    /** Manually reset (e.g. after operator review). */
    reset() {
        this._state = "closed";
        this._openedAt = undefined;
        this._halfOpenConsumed = false;
        this._window = [];
    }
    /** Total spend in current window. */
    get windowSpend() {
        this._evict();
        return this._windowSpend();
    }
    // ── private helpers ────────────────────────────────────────────────────────
    _evict() {
        const cutoff = this._now() - this.cfg.windowMs;
        let i = 0;
        while (i < this._window.length && this._window[i].ts < cutoff)
            i++;
        if (i > 0)
            this._window.splice(0, i);
    }
    _windowSpend() {
        return this._window.reduce((sum, e) => sum + e.costUsd, 0);
    }
    _trip(now) {
        this._state = "open";
        this._openedAt = now;
        this._halfOpenConsumed = false;
    }
}
//# sourceMappingURL=circuit-breaker.js.map