import type { JobEnvelope, JobRegistry, DispatchOpts } from './index.js'

/** Minimal `DurableObjectState` alarm surface used by the runner. */
export type DurableObjectAlarmContext = {
  setAlarm(scheduledTime: number | Date): Promise<void>
  getAlarm(): Promise<number | null>
}

export const REARM_DELAY_MS = 1500

/**
 * Alarm-driven job runner — `nudge()` ensures an alarm is pending; `alarm()` drains
 * one unit per tick and self-reschedules while backlog remains (re-arm in `finally`).
 */
export abstract class JobRunnerDO<E> {
  constructor(
    protected readonly ctx: DurableObjectAlarmContext,
    protected readonly registry: JobRegistry<E>,
    protected readonly env: E,
    protected readonly dispatchOpts?: DispatchOpts<E>,
  ) {}

  /** Idempotent wake — sets `setAlarm(now)` only when none is already pending. */
  async nudge(): Promise<void> {
    const pending = await this.ctx.getAlarm()
    if (pending != null) return
    await this.ctx.setAlarm(Date.now())
  }

  protected abstract pullNext(): Promise<JobEnvelope | null>
  protected abstract hasBacklog(): Promise<boolean>

  async alarm(): Promise<void> {
    try {
      const work = await this.pullNext()
      if (work) {
        await this.registry.dispatch(this.env, work, this.dispatchOpts)
      }
    } finally {
      if (await this.hasBacklog()) {
        await this.ctx.setAlarm(Date.now() + REARM_DELAY_MS)
      }
    }
  }
}
