/**
 * SET 56d — canonical pipeline stage progression for the Pipeline log module.
 * Browser-clean: no imports beyond types.
 */

export const STAGE_ORDER = [
  'encode',
  'gate',
  'ir',
  'build',
  'auth',
  'rha',
  'done',
] as const;

export type StagePhase = (typeof STAGE_ORDER)[number];

export const STAGE_LABELS: Record<StagePhase, string> = {
  encode: 'Encode',
  gate: 'Spec gate',
  ir: 'IR contract',
  build: 'Build',
  auth: 'Authenticate',
  rha: 'RHA',
  done: 'Done',
};

export type StageStatus = 'done' | 'active' | 'pending' | 'failed';

export type StageProgressRow = {
  phase: string;
  label: string;
  status: StageStatus;
  elapsedMs: number | null;
};

const STAGE_SET = new Set<string>(STAGE_ORDER);

export function isCanonicalStage(phase: string): phase is StagePhase {
  return STAGE_SET.has(phase);
}

/** Unknown phases inherit the previous event's canonical stage; first event → encode. */
export function canonicalizePhase(
  phase: string,
  previousCanonical: StagePhase | null,
): StagePhase {
  if (isCanonicalStage(phase)) return phase;
  return previousCanonical ?? 'encode';
}

/** Format elapsed ms as MmSSs (or Ss under a minute) — same shape as exportRunLog. */
export function formatStageElapsed(ms: number): string {
  const sec = Math.max(0, Math.round(ms / 1000));
  const m = Math.floor(sec / 60);
  const s = sec % 60;
  const pad = String(s).padStart(2, '0');
  return m > 0 ? `${m}m${pad}s` : `${s}s`;
}

/**
 * Always returns all 7 stages in canonical order.
 * - done: any later-ordered stage has events, or the run finished past this stage
 * - active: owns the newest event and running
 * - failed: not running, never reached done, owns the newest event
 * - pending: otherwise
 * elapsedMs: first(next-with-events) − first(this); last started uses newest (or now if running);
 * null when this stage has no events.
 */
export function stageProgress(
  events: { ts: string; phase: string }[],
  running: boolean,
  nowMs: number = Date.now(),
): StageProgressRow[] {
  let prev: StagePhase | null = null;
  const canon = events.map(e => {
    const phase = canonicalizePhase(e.phase, prev);
    prev = phase;
    return { ts: e.ts, phase };
  });

  const firstTs = new Map<StagePhase, number>();
  for (const e of canon) {
    if (!firstTs.has(e.phase)) firstTs.set(e.phase, new Date(e.ts).getTime());
  }

  const reachedDone = firstTs.has('done');
  const newest = canon.length ? canon[canon.length - 1]! : null;
  const newestPhase = newest?.phase ?? null;
  const newestTs = newest ? new Date(newest.ts).getTime() : null;
  const newestIdx = newestPhase ? STAGE_ORDER.indexOf(newestPhase) : -1;

  let lastStartedIdx = -1;
  for (let i = 0; i < STAGE_ORDER.length; i++) {
    if (firstTs.has(STAGE_ORDER[i]!)) lastStartedIdx = i;
  }

  return STAGE_ORDER.map((phase, idx) => {
    const label = STAGE_LABELS[phase];
    const ownsNewest = newestPhase === phase;

    let status: StageStatus;
    if (ownsNewest && running) {
      status = 'active';
    } else if (ownsNewest && !running && !reachedDone) {
      status = 'failed';
    } else if (ownsNewest && !running && reachedDone) {
      status = 'done';
    } else {
      const laterHas = STAGE_ORDER.slice(idx + 1).some(p => firstTs.has(p));
      const finishedPast = !running && newestIdx > idx;
      status = laterHas || finishedPast || (!running && reachedDone && idx <= newestIdx)
        ? 'done'
        : 'pending';
    }

    let elapsedMs: number | null = null;
    const start = firstTs.get(phase);
    if (start != null) {
      if (idx === lastStartedIdx) {
        const end = running ? nowMs : (newestTs ?? start);
        elapsedMs = Math.max(0, end - start);
      } else {
        const nextWith = STAGE_ORDER.slice(idx + 1).find(p => firstTs.has(p));
        if (nextWith) {
          elapsedMs = Math.max(0, firstTs.get(nextWith)! - start);
        }
      }
    }

    return { phase, label, status, elapsedMs };
  });
}
