// Late-response polling. The Go backend's pollForResponse window is
// 60 s; if Claude writes the response file after that (because the
// tmux pane was busy mid-turn when the message arrived), the SSE
// stream has already closed with an `error` event. The Janitor
// (services.LocalPaliadinService.runJanitor) then patches the
// paliadin_turns row when the file lands.
//
// This module is the FE half of that loop: after the bubble shows an
// error, the caller registers the turn here. We poll
// `/api/paliadin/turns/{id}` every 3 s for up to 10 minutes; once the
// row has a non-empty response, we hand it back so the caller can
// swap the bubble content in place.

export interface LateTurn {
  turn_id: string;
  response: string | null;
  error_code: string | null;
  finished_at: string | null;
  duration_ms: number | null;
  used_tools: string[];
  rows_seen: number[];
  chip_count: number;
  classifier_tag: string | null;
}

export interface LatePollOptions {
  turnId: string;
  intervalMs?: number;     // default 3000
  maxDurationMs?: number;  // default 600000 (10 min)
  onLateResponse: (turn: LateTurn) => void;
  onGiveUp?: () => void;
}

export interface LatePollHandle {
  cancel: () => void;
}

export function pollForLateResponse(opts: LatePollOptions): LatePollHandle {
  const interval = opts.intervalMs ?? 3000;
  const maxDuration = opts.maxDurationMs ?? 10 * 60 * 1000;
  const startedAt = Date.now();

  let cancelled = false;
  let timer: number | undefined;

  const tick = async () => {
    if (cancelled) return;
    if (Date.now() - startedAt > maxDuration) {
      opts.onGiveUp?.();
      return;
    }
    try {
      const r = await fetch(`/api/paliadin/turns/${opts.turnId}`, {
        credentials: "same-origin",
      });
      if (r.ok) {
        const turn = (await r.json()) as LateTurn;
        if (turn.response && turn.response.length > 0) {
          opts.onLateResponse(turn);
          return;
        }
      }
      // 404: row gone (very unlikely) — give up.
      if (r.status === 404) {
        opts.onGiveUp?.();
        return;
      }
    } catch {
      // Transient network error; retry on next tick.
    }
    timer = window.setTimeout(tick, interval);
  };

  // First poll deliberately runs after one interval so we don't race
  // the 60 s timeout on the very first tick.
  timer = window.setTimeout(tick, interval);

  return {
    cancel: () => {
      cancelled = true;
      if (timer != null) window.clearTimeout(timer);
    },
  };
}
