import { formatDuration } from "./duration.js";
/**
 * Periodically supervise Chrome:
 *  - Idle-close: if Chrome has been idle past idleCloseMs and nothing else
 *    is using or about to use it, close it (keeping the workspace lock).
 *    The next chat / scheduled task triggers a lazy in-place relaunch.
 *  - Aged-restart: if Chrome uptime exceeds maxRuntimeMs, do a full
 *    restart while idle — long-running renderers accumulate heap /
 *    decoded images / DOM that aren't released until tabs close, so this
 *    bounds memory growth over multi-day sessions.
 *
 * Both checks bail when the browser lock is busy or a scheduled task is
 * within its configured lead time, and both re-check inside the lock to
 * avoid acting on data that changed while queued.
 */
export class ChromeWatchdog {
    options;
    deps;
    agedTimer = null;
    idleTimer = null;
    constructor(options, deps) {
        this.options = options;
        this.deps = deps;
    }
    start() {
        if (this.agedTimer || this.idleTimer)
            return;
        this.agedTimer = setInterval(() => {
            this.tickAgedRestart().catch((err) => {
                const msg = err instanceof Error ? err.message : String(err);
                console.error(`  Chrome runtime watchdog failed: ${msg}`);
            });
        }, this.options.checkIntervalMs);
        this.agedTimer.unref?.();
        if (this.options.idleCloseMs != null) {
            const interval = this.options.idleCheckIntervalMs ?? this.options.checkIntervalMs;
            this.idleTimer = setInterval(() => {
                this.tickIdleClose().catch((err) => {
                    const msg = err instanceof Error ? err.message : String(err);
                    console.error(`  Chrome idle-close watchdog failed: ${msg}`);
                });
            }, interval);
            this.idleTimer.unref?.();
        }
    }
    stop() {
        if (this.agedTimer) {
            clearInterval(this.agedTimer);
            this.agedTimer = null;
        }
        if (this.idleTimer) {
            clearInterval(this.idleTimer);
            this.idleTimer = null;
        }
    }
    /**
     * Aged-restart iteration. Exposed for testing / manual triggering;
     * normally driven by the interval timer.
     */
    async tickAgedRestart() {
        const { maxRuntimeMs } = this.options;
        const uptimeMs = this.deps.getUptimeMs();
        if (uptimeMs == null || uptimeMs < maxRuntimeMs)
            return;
        if (!this.deps.lock.isIdle()) {
            console.log(`  Chrome runtime ${formatDuration(uptimeMs)} exceeds ${formatDuration(maxRuntimeMs)}; restart deferred because browser is busy.`);
            return;
        }
        if (this.hasScheduledTaskSoon(this.options.minLeadMs)) {
            console.log(`  Chrome runtime ${formatDuration(uptimeMs)} exceeds ${formatDuration(maxRuntimeMs)}; restart deferred because a scheduled task is close.`);
            return;
        }
        const handle = this.deps.lock.tryAcquire();
        if (!handle)
            return;
        await handle.promise;
        try {
            // Re-check inside the lock — uptime / scheduled-soon may have changed
            // while we queued, and a restart-on-stale data is the kind of bug
            // that only shows up once a quarter.
            const currentUptimeMs = this.deps.getUptimeMs();
            if (currentUptimeMs == null || currentUptimeMs < maxRuntimeMs)
                return;
            if (this.hasScheduledTaskSoon(this.options.minLeadMs))
                return;
            await this.deps.restartBrowser(`Chrome runtime ${formatDuration(currentUptimeMs)} exceeded ${formatDuration(maxRuntimeMs)}`);
        }
        finally {
            handle.release();
        }
    }
    /**
     * Idle-close iteration. Only runs when idleCloseMs is configured.
     * Exposed for testing / manual triggering.
     */
    async tickIdleClose() {
        const idleCloseMs = this.options.idleCloseMs;
        if (idleCloseMs == null)
            return;
        const idleMs = this.deps.getIdleMs();
        if (idleMs == null || idleMs < idleCloseMs)
            return;
        if (!this.deps.lock.isIdle())
            return;
        const leadMs = this.options.idleCloseMinLeadMs ?? 0;
        if (this.hasScheduledTaskSoon(leadMs)) {
            console.log(`  Chrome idle for ${formatDuration(idleMs)}; close deferred because a scheduled task is close.`);
            return;
        }
        const handle = this.deps.lock.tryAcquire();
        if (!handle)
            return;
        await handle.promise;
        try {
            const currentIdleMs = this.deps.getIdleMs();
            if (currentIdleMs == null || currentIdleMs < idleCloseMs)
                return;
            if (this.hasScheduledTaskSoon(leadMs))
                return;
            await this.deps.closeBrowser(`Chrome idle for ${formatDuration(currentIdleMs)}`);
        }
        finally {
            handle.release();
        }
    }
    hasScheduledTaskSoon(leadMs) {
        if (leadMs <= 0)
            return false;
        const nextRun = this.deps.getNextRun();
        if (!nextRun)
            return false;
        const untilNextRunMs = nextRun.getTime() - Date.now();
        return untilNextRunMs >= 0 && untilNextRunMs < leadMs;
    }
}
//# sourceMappingURL=chrome-watchdog.js.map