/**
 * SET 21 — chart helpers: tok annotation parse + burn series from fixture events.
 */
import { describe, expect, it } from 'vitest';
import {
  buildTokenBurnSeries,
  isMarkerEvent,
  normalizeStage,
  parseTokAnnotation,
  stageBuckets,
} from '../web/src/runChartData';

describe('SET 21 run charts', () => {
  it('parseTokAnnotation reads (N→ / M← tok) forms', () => {
    expect(parseTokAnnotation('llm call (2,341→ / 1,876← tok)')).toEqual({
      inTok: 2341,
      outTok: 1876,
    });
    expect(parseTokAnnotation('F1 · draw done (100→ / 50← tok)')).toEqual({
      inTok: 100,
      outTok: 50,
    });
    expect(parseTokAnnotation('substrate: fixed — (0 tok)')).toBeNull();
    expect(parseTokAnnotation('still working')).toBeNull();
  });

  it('buildTokenBurnSeries accumulates from fixture metrics events', () => {
    const start = '2026-07-26T12:00:00.000Z';
    const events = [
      { ts: '2026-07-26T12:00:10.000Z', message: 'encode done (1,000→ / 200← tok)' },
      { ts: '2026-07-26T12:00:30.000Z', message: 'build (500→ / 100← tok)' },
      { ts: '2026-07-26T12:01:00.000Z', message: 'crash quarantine: dup across 2 features — ladders frozen' },
      { ts: '2026-07-26T12:01:20.000Z', message: 'repair (800→ / 400← tok)' },
      { ts: '2026-07-26T12:02:00.000Z', message: 'provider degraded: 12 tok/s vs 80 baseline — check host (GPU fallback?)' },
    ];
    const series = buildTokenBurnSeries(events, start);
    expect(series[0]).toEqual({ tMs: 0, cumulative: 0 });
    const lastTok = series.filter(p => !p.marker).at(-1)!;
    expect(lastTok.cumulative).toBe(1000 + 200 + 500 + 100 + 800 + 400);
    expect(lastTok.tMs).toBe(80_000);
    const markers = series.filter(p => p.marker);
    expect(markers).toHaveLength(2);
    expect(markers[0].tMs).toBe(60_000);
    expect(isMarkerEvent(markers[1].message!)).toBe(true);
  });

  it('stageBuckets maps ops onto encode/ir/build/repair', () => {
    const b = stageBuckets({
      encode: { calls: 2, tokensIn: 100, tokensOut: 10 },
      probe: { calls: 1, tokensIn: 50, tokensOut: 5 },
      ir: { calls: 3, tokensIn: 200, tokensOut: 40 },
      build: { calls: 4, tokensIn: 300, tokensOut: 80 },
      repair: { calls: 5, tokensIn: 400, tokensOut: 90 },
      permute: { calls: 2, tokensIn: 100, tokensOut: 20 },
    });
    expect(b.encode.tokensIn).toBe(150);
    expect(b.ir.calls).toBe(3);
    expect(b.build.tokensOut).toBe(80);
    expect(b.repair.tokensIn).toBe(500);
    expect(b.repair.calls).toBe(7);
    expect(normalizeStage('fixStub')).toBe('encode');
  });
});
