// engine/workers/worker-crash-recovery.ts — Auto-restart on crash with backoff (SPEC-137)
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const DEFAULT_MAX_RESTARTS = 5;
const BASE_BACKOFF_MS = 1_000;
const MAX_BACKOFF_MS = 300_000; // 5 minutes
// ---------------------------------------------------------------------------
// WorkerCrashRecovery
// ---------------------------------------------------------------------------
/**
 * Listens for 'worker-dead' events from WorkerHeartbeat instances and
 * auto-restarts the dead worker via WorkerEngine with exponential backoff.
 * Stops restarting after maxRestarts consecutive crashes.
 */
export class WorkerCrashRecovery {
    engine;
    maxRestarts;
    metricsMap = new Map();
    timers = new Map();
    constructor(engine, options) {
        this.engine = engine;
        this.maxRestarts = options?.maxRestarts ?? DEFAULT_MAX_RESTARTS;
    }
    /**
     * Register a worker and its heartbeat monitor.
     * The recovery manager will listen for 'worker-dead' events and react.
     */
    registerWorker(workerName, heartbeat) {
        if (!this.metricsMap.has(workerName)) {
            this.metricsMap.set(workerName, {
                workerName,
                restartCount: 0,
                lastCrashAt: null,
                lastRestartAt: null,
                status: 'healthy',
            });
        }
        heartbeat.on('worker-dead', (deadWorkerName) => {
            void this.handleCrash(deadWorkerName);
        });
    }
    /** Returns current crash metrics for a given worker. */
    getMetrics(workerName) {
        return (this.metricsMap.get(workerName) ?? {
            workerName,
            restartCount: 0,
            lastCrashAt: null,
            lastRestartAt: null,
            status: 'healthy',
        });
    }
    /** Reset crash counter for a worker (e.g. after manual intervention). */
    resetMetrics(workerName) {
        this.metricsMap.set(workerName, {
            workerName,
            restartCount: 0,
            lastCrashAt: null,
            lastRestartAt: null,
            status: 'healthy',
        });
        const pendingTimer = this.timers.get(workerName);
        if (pendingTimer !== undefined) {
            clearTimeout(pendingTimer);
            this.timers.delete(workerName);
        }
    }
    // ---------------------------------------------------------------------------
    // Private
    // ---------------------------------------------------------------------------
    async handleCrash(workerName) {
        const current = this.metricsMap.get(workerName);
        if (!current) {
            return;
        }
        if (current.restartCount >= this.maxRestarts) {
            this.metricsMap.set(workerName, {
                ...current,
                lastCrashAt: new Date().toISOString(),
                status: 'dead',
            });
            return;
        }
        const backoffMs = this.computeBackoff(current.restartCount);
        this.metricsMap.set(workerName, {
            ...current,
            lastCrashAt: new Date().toISOString(),
            status: 'recovering',
        });
        await new Promise((resolve) => {
            const timer = setTimeout(() => {
                this.timers.delete(workerName);
                resolve();
            }, backoffMs);
            this.timers.set(workerName, timer);
        });
        // Re-check in case resetMetrics was called during backoff
        const afterBackoff = this.metricsMap.get(workerName);
        if (!afterBackoff || afterBackoff.restartCount >= this.maxRestarts) {
            return;
        }
        this.metricsMap.set(workerName, {
            ...afterBackoff,
            restartCount: afterBackoff.restartCount + 1,
            lastRestartAt: new Date().toISOString(),
            status: 'healthy',
        });
        this.engine.enqueue(workerName);
    }
    computeBackoff(restartCount) {
        const backoff = BASE_BACKOFF_MS * Math.pow(2, restartCount);
        return Math.min(backoff, MAX_BACKOFF_MS);
    }
}
//# sourceMappingURL=worker-crash-recovery.js.map