/**
 * SET 21 — pure chart data helpers (no React/JSX — safe for root tsc + vitest).
 */

export type StageBucket = { calls: number; tokensIn: number; tokensOut: number };

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

export const STAGE_ORDER = ['encode', 'ir', 'build', 'repair'] as const;
export type StageKey = (typeof STAGE_ORDER)[number];

/** Map raw op names onto the four display stages. */
export function normalizeStage(op: string): StageKey | null {
  const o = op.toLowerCase();
  if (o === 'encode' || o === 'probe' || o === 'fixstub') return 'encode';
  if (o === 'ir' || o === 'plan' || o === 'fill') return 'ir';
  if (o === 'build') return 'build';
  if (o === 'repair' || o === 'permute' || o === 'vanilla' || o === 'referee') return 'repair';
  return null;
}

/** Parse `(2,341→ / 1,876← tok)` annotations from pipeline messages. */
export function parseTokAnnotation(message: string): { inTok: number; outTok: number } | null {
  const m = /(\d[\d,]*)\s*→\s*\/\s*(\d[\d,]*)\s*←\s*tok/i.exec(message);
  if (!m) return null;
  const inTok = Number(m[1].replace(/,/g, ''));
  const outTok = Number(m[2].replace(/,/g, ''));
  if (!Number.isFinite(inTok) || !Number.isFinite(outTok)) return null;
  return { inTok, outTok };
}

/** Crash / quarantine / degraded markers for the burn timeline. */
export function isMarkerEvent(message: string): boolean {
  const m = message.toLowerCase();
  return (
    m.includes('crash')
    || m.includes('quarantine')
    || m.includes('provider degraded')
    || m.includes('degraded:')
  );
}

export type BurnPoint = {
  tMs: number;
  cumulative: number;
  marker?: boolean;
  message?: string;
};

/** Build cumulative token series from events with tok annotations. */
export function buildTokenBurnSeries(
  events: ChartEvent[],
  startTs: string,
): BurnPoint[] {
  const t0 = new Date(startTs).getTime();
  if (!Number.isFinite(t0)) return [];
  let cum = 0;
  const out: BurnPoint[] = [{ tMs: 0, cumulative: 0 }];
  for (const e of events) {
    const tMs = Math.max(0, new Date(e.ts).getTime() - t0);
    const tok = parseTokAnnotation(e.message);
    if (tok) {
      cum += tok.inTok + tok.outTok;
      out.push({ tMs, cumulative: cum });
    } else if (isMarkerEvent(e.message)) {
      out.push({ tMs, cumulative: cum, marker: true, message: e.message });
    }
  }
  return out;
}

/** Collapse perStage ops into encode/ir/build/repair buckets. */
export function stageBuckets(
  perStage?: Record<string, StageBucket> | null,
): Record<StageKey, StageBucket> {
  const empty = (): StageBucket => ({ calls: 0, tokensIn: 0, tokensOut: 0 });
  const acc: Record<StageKey, StageBucket> = {
    encode: empty(), ir: empty(), build: empty(), repair: empty(),
  };
  if (!perStage) return acc;
  for (const [op, s] of Object.entries(perStage)) {
    const key = normalizeStage(op);
    if (!key || !s) continue;
    acc[key].calls += s.calls || 0;
    acc[key].tokensIn += s.tokensIn || 0;
    acc[key].tokensOut += s.tokensOut || 0;
  }
  return acc;
}
