// A liveness watchdog for the live event stream. The gateway pushes a `heartbeat` event on a
// fixed cadence (GATEWAY_PUSH_HEARTBEAT_INTERVAL, default 10 s), so a healthy stream delivers at
// least one event well inside the timeout even when the game itself is idle. A network that dies
// silently — e.g. airplane mode cutting the radio — leaves the streaming fetch neither erroring
// nor delivering; without this watchdog the machine would stay "online" until the next active
// call fails (often only on a foreground resync). The watchdog resets on every delivered event
// and fires onDead once the stream has been silent for the whole timeout, so the app can report
// the drop and reconnect.

/** StreamWatchdog is the control surface returned by {@link createStreamWatchdog}. */
export interface StreamWatchdog {
  /** reset (re)starts the countdown; call on stream open and on every delivered event. It also
   *  clears a prior pause, so a reopen after a suspend resumes watching. */
  reset(): void;
  /** pause stops the countdown while the app is backgrounded (timers are throttled while hidden,
   *  and a suspended stream legitimately delivers nothing — neither should trip a false drop). */
  pause(): void;
  /** resume restarts the countdown after a pause (on returning to the foreground). */
  resume(): void;
  /** clear stops the countdown without re-arming; call when the stream is closed or has errored. */
  clear(): void;
}

/**
 * createStreamWatchdog builds a watchdog that calls onDead once no reset has happened for
 * timeoutMs. timeoutMs must exceed the server heartbeat interval plus proxy/jitter slack, or a
 * healthy but idle stream would trip it.
 */
export function createStreamWatchdog(opts: { timeoutMs: number; onDead: () => void }): StreamWatchdog {
  let timer: ReturnType<typeof setTimeout> | null = null;
  let paused = false;

  function stop(): void {
    if (timer !== null) {
      clearTimeout(timer);
      timer = null;
    }
  }
  function arm(): void {
    stop();
    timer = setTimeout(opts.onDead, opts.timeoutMs);
  }

  return {
    reset(): void {
      paused = false;
      arm();
    },
    pause(): void {
      paused = true;
      stop();
    },
    resume(): void {
      if (paused) {
        paused = false;
        arm();
      }
    },
    clear(): void {
      stop();
    },
  };
}
