/**
 * SET 56a — pure sweep for stale "running" run records.
 * Source of live truth is the offscreen in-memory job (liveId), never storage.
 * The SW mirrors this logic against chrome.storage; tests bind here.
 */

export const SWEEP_STALE_MSG =
  'interrupted — swept as stale (no live job, checkpoint aged out)';

export const SWEEP_AGE_MS = 120_000;

export type SweepEvent = {
  ts: string;
  phase: string;
  featureId: string | null;
  message: string;
};

export type SweepableRecord = {
  id: string;
  status?: string | null;
  running?: boolean | null;
  finalized?: boolean | null;
  checkpointAt?: string | number | null;
  ts?: string | number | null;
  sweptAt?: string | null;
  events?: SweepEvent[] | null;
};

function toMs(v: string | number | null | undefined): number {
  if (v == null || v === '') return 0;
  if (typeof v === 'number') return Number.isFinite(v) ? v : 0;
  const n = Date.parse(v);
  return Number.isFinite(n) ? n : 0;
}

/** ZOMBIE classifier — see CURSOR_BACKLOG SET 56a.2. */
export function isZombieRunning(
  rec: SweepableRecord,
  liveId: string | null,
  nowMs: number,
): boolean {
  const statusUnset = rec.status == null;
  const looksRunning =
    rec.status === 'running'
    || rec.running === true
    || (rec.finalized !== true && statusUnset);
  if (!looksRunning) return false;
  if (liveId != null && rec.id === liveId) return false;
  const ageAnchor = Math.max(toMs(rec.checkpointAt ?? 0), toMs(rec.ts));
  return nowMs - ageAnchor > SWEEP_AGE_MS;
}

/** Apply interrupted + sweep event in place (does not reorder prior events). */
export function applySweepToRecord(
  rec: SweepableRecord,
  nowIso: string,
): SweepableRecord {
  const events = Array.isArray(rec.events) ? rec.events.slice() : [];
  if (!events.some(e => e && e.message === SWEEP_STALE_MSG)) {
    events.push({
      ts: nowIso,
      phase: 'done',
      featureId: null,
      message: SWEEP_STALE_MSG,
    });
  }
  return {
    ...rec,
    status: 'interrupted',
    running: false,
    finalized: true,
    sweptAt: nowIso,
    events,
  };
}

/**
 * Sweep a list of full run records. Idempotent: second call with the same
 * inputs yields changed=false and identical records.
 */
export function sweepStaleRunningRecords(
  records: SweepableRecord[],
  liveId: string | null,
  nowMs: number = Date.now(),
): { records: SweepableRecord[]; changed: boolean } {
  const nowIso = new Date(nowMs).toISOString();
  let changed = false;
  const next = records.map(rec => {
    if (!isZombieRunning(rec, liveId, nowMs)) return rec;
    changed = true;
    return applySweepToRecord(rec, nowIso);
  });
  return { records: next, changed };
}
