/**
 * circuit-breaker.ts — Failure fingerprinting and circuit breaker for auto-mode.
 *
 * Tracks failure fingerprints (unitId + error hash) and trips when the same
 * fingerprint occurs N times (configurable, default 3). Complements detect-stuck.ts
 * which operates at the dispatch layer; this operates at the execution/verification layer.
 *
 * All state is plain Maps — no DB, no async. Fire-and-forget: recording never throws.
 */
// ─── Failure Fingerprinting ──────────────────────────────────────────────────
/**
 * Simple string hash (djb2). Produces a stable numeric hash from a string.
 * Not cryptographic — just needs to be deterministic and fast.
 */
function djb2(str) {
    let hash = 5381;
    for (let i = 0; i < str.length; i++) {
        hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
    }
    return hash >>> 0; // force unsigned 32-bit
}
/**
 * Produce a stable fingerprint from a unit ID and error message.
 * Uses the first 200 chars of the error to avoid fingerprint divergence
 * on messages that differ only in trailing details (timestamps, paths, etc.).
 */
export function hashFailure(unitId, errorMsg) {
    const truncated = errorMsg.slice(0, 200);
    return `${unitId}:${djb2(truncated).toString(36)}`;
}
export class CircuitBreaker {
    threshold;
    counts = new Map();
    trippedFingerprint = null;
    constructor(config = {}) {
        this.threshold = config.threshold ?? 3;
    }
    /**
     * Record a failure fingerprint. If the count reaches the threshold,
     * the breaker trips for that fingerprint.
     *
     * Fire-and-forget: never throws.
     */
    record(fingerprint) {
        try {
            const count = (this.counts.get(fingerprint) ?? 0) + 1;
            this.counts.set(fingerprint, count);
            if (count >= this.threshold) {
                this.trippedFingerprint = fingerprint;
            }
        }
        catch {
            // Fire-and-forget — swallow unexpected errors
        }
    }
    /**
     * Check whether a specific fingerprint has tripped the breaker.
     */
    isTripped(fingerprint) {
        return (this.counts.get(fingerprint) ?? 0) >= this.threshold;
    }
    /**
     * Get info about the most recently tripped fingerprint, or null if none.
     */
    getTripInfo() {
        if (!this.trippedFingerprint)
            return null;
        return {
            fingerprint: this.trippedFingerprint,
            count: this.counts.get(this.trippedFingerprint) ?? 0,
        };
    }
    /**
     * Clear all recorded failures and trip state.
     */
    reset() {
        this.counts.clear();
        this.trippedFingerprint = null;
    }
    /**
     * Diagnostic snapshot for toJSON / logging.
     */
    toJSON() {
        return {
            threshold: this.threshold,
            trippedFingerprint: this.trippedFingerprint,
            counts: Object.fromEntries(this.counts),
        };
    }
}
