/**
 * Catch-up recovery as a supervised `PeriodicService` (PHNX-3608).
 *
 * A catch-up pass detects routines whose scheduled fire this device missed (the
 * laptop slept, the daemon was wedged, an OS suspend the process survived) and
 * runs them late. It used to run on a bare `setInterval` booted/stopped inside
 * `runDaemon()` alongside the scheduler, with only a local `catchingUp` overlap
 * flag and NO deadline — a pass that hung on an off-box (host/cloud) dispatch
 * could latch that flag and silently stop recovering missed fires for the
 * daemon's life, the same "abstraction too weak" class the supervisor exists for.
 *
 * Under the supervisor it gets a per-tick deadline, an AbortSignal, and the
 * circuit breaker: a hung pass is abandoned and restarted on backoff instead of
 * latching. The scheduler itself (croner-driven `JobScheduler`) stays outside the
 * supervisor — it is not a tick loop — so this service self-gates on whether the
 * scheduler is currently booted and no-ops cheaply when the scheduler gate is off.
 */
import { BasePeriodicService } from './service.js';
/**
 * How often to re-run catch-up. A startup pass alone misses a fire lost while the
 * daemon stayed up but its event loop was wedged, or one lost across an OS
 * suspend the process survived — five minutes bounds the cost while still
 * recovering from a wedge or an OS suspend the process survived.
 */
export const CATCHUP_TICK_MS = 5 * 60_000;
export class CatchupService extends BasePeriodicService {
    deps;
    id = 'catchup';
    intervalMs = CATCHUP_TICK_MS;
    /**
     * A pass awaits `executeJobDetached` per overdue job and an off-box dispatch
     * can block for a while, so the bound is generous — but finite, which the old
     * un-deadlined loop was not. A pass exceeding it is hung, not slow.
     */
    deadlineMs = 4 * 60_000;
    constructor(deps) {
        super();
        this.deps = deps;
    }
    async onStart() {
        // No owned resources — the supervisor owns the timer.
    }
    async onStop() {
        // No owned resources.
    }
    async onTick(_ctx, signal) {
        if (!this.deps.isSchedulerBooted())
            return;
        await this.deps.runPass(signal);
    }
}
