/**
 * DaemonService contract (RUSH-3193 P1).
 *
 * Historically the daemon's background services were bare `setInterval` closures
 * inside one long `runDaemon()` (daemon.ts), sharing one event loop with no
 * per-service error boundary or deadline. A throw escaping a tick's local
 * try/catch hits the process-wide handler and kills every service; a tick that
 * hangs on an unbounded await latches its in-flight guard `true` forever and
 * silently freezes that service for the daemon's life.
 *
 * This module defines the service shape a `ServiceSupervisor` (supervisor.ts)
 * drives instead: one contract per service, owned timer, per-tick deadline,
 * and a health record the supervisor can report without the service having to
 * know it is being supervised.
 */
export function isPeriodicService(service) {
    const candidate = service;
    return typeof candidate.tick === 'function' && typeof candidate.intervalMs === 'number' && typeof candidate.deadlineMs === 'number';
}
function blankHealth() {
    return { state: 'idle', lastRunMs: 0, consecutiveFailures: 0 };
}
/**
 * Convenience base for a lifecycle-only (non-periodic) daemon service.
 *
 * Concrete subclasses implement `onStart` and `onStop`; the base owns the
 * `ServiceHealth` record and the `restart()` default (`stop` + `start`).
 * Unlike `BasePeriodicService` there is no tick — the supervisor just calls
 * `start()` once at boot, keeps the service marked `running`, and calls
 * `stop()` at shutdown.
 *
 * `lastRunMs` is set to the time `start()` completed, so `supervisor.health()`
 * carries a meaningful "last known healthy" timestamp even with no periodic ticks.
 */
export class BaseDaemonService {
    ctx = null;
    healthRecord = blankHealth();
    async start(ctx) {
        this.ctx = ctx;
        await this.onStart(ctx);
        this.healthRecord = { ...this.healthRecord, state: 'running', lastRunMs: Date.now() };
    }
    async stop() {
        await this.onStop();
        this.healthRecord = { ...this.healthRecord, state: 'stopped' };
    }
    async restart() {
        const ctx = this.ctx;
        if (!ctx)
            throw new Error(`service '${this.id}' cannot restart before it has started once`);
        await this.stop();
        await this.start(ctx);
    }
    health() {
        return { ...this.healthRecord };
    }
}
/**
 * Convenience base for a periodic service: owns the `ServiceHealth` record so
 * concrete services only implement the three lifecycle hooks. `restart()`
 * defaults to `stop()` then `start()` against the last context passed to
 * `start()`, which is what every current-generation service needs — a
 * concrete service overrides it only if it must reuse state across a restart.
 */
export class BasePeriodicService {
    ctx = null;
    healthRecord = blankHealth();
    async start(ctx) {
        this.ctx = ctx;
        await this.onStart(ctx);
        this.healthRecord = { ...this.healthRecord, state: 'running' };
    }
    async stop() {
        await this.onStop();
        this.healthRecord = { ...this.healthRecord, state: 'stopped' };
    }
    async restart() {
        const ctx = this.ctx;
        if (!ctx)
            throw new Error(`service '${this.id}' cannot restart before it has started once`);
        await this.stop();
        await this.start(ctx);
    }
    async tick(ctx, signal) {
        try {
            await this.onTick(ctx, signal);
            this.healthRecord = { ...this.healthRecord, lastRunMs: Date.now(), consecutiveFailures: 0, lastError: undefined };
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            this.healthRecord = { ...this.healthRecord, lastError: message, consecutiveFailures: this.healthRecord.consecutiveFailures + 1 };
            throw err;
        }
    }
    health() {
        return { ...this.healthRecord };
    }
}
