// SOS+TTS Latenz-Benchmark.
//
// Eine BenchSession pro sendToLyra-Call. Aggregiert Timing-Marker aus
// sosStream + sosTtsQueue und druckt am Ende eine Tabelle ins Dev-Console.
//
// Marker-Reihenfolge im typischen Flow:
//   t0  request-fired       (sendToLyra start)
//   t1  session-post-done   (POST /sos-session resolved → sessionId da)
//   t2  sse-first-chunk     ("Lyra denkt fertig" — erstes Token)
//   t3  sse-done            (full text fertig)
//   t4  tts-fetch-start     (POST an /api/coach/speak-* fired)
//   t5  tts-fetch-headers   (response headers da, body kommt noch)
//   t6  tts-body-done       (kompletter Audio-Body geladen — DAS ist der Bottleneck)
//   t7  tts-file-written    (base64 → File geschrieben)
//   t8  audio-loaded        (Audio.Sound.createAsync resolved)
//   t9  first-audio         (erstes onPlaybackStatusUpdate mit isPlaying)
//
// Bottleneck-Diagnose:
//   - (t6 - t5) groß → Body-Download dominiert. Cartesia's TTFB-Vorteil
//     verpufft hier weil wir auf alles warten statt zu streamen.
//   - (t9 - t8) groß → expo-av lädt langsam (file-codec-detect etc.)

export type BenchMarker =
  | 'session-post-start'
  | 'session-post-done'
  | 'sse-first-chunk'
  | 'sse-done'
  | 'tts-fetch-start'
  | 'tts-fetch-headers'
  | 'tts-body-done'
  | 'tts-file-written'
  | 'audio-loaded'
  | 'first-audio';

export type BenchOnMetric = (marker: BenchMarker, meta?: Record<string, unknown>) => void;

type MarkerEntry = {
  marker: BenchMarker;
  /** ms relativ zu t0 */
  tRel: number;
  meta?: Record<string, unknown>;
};

export class BenchSession {
  readonly t0: number;
  readonly provider: string;
  readonly llm: string;
  readonly label: string;
  private entries: MarkerEntry[] = [];
  private printed = false;

  constructor(opts: { provider: string; llm?: string; label?: string }) {
    this.t0 = Date.now();
    this.provider = opts.provider;
    this.llm = opts.llm ?? 'unknown';
    this.label = opts.label ?? 'sos-turn';
  }

  /** Bound version — kann direkt als onMetric weitergegeben werden. */
  readonly mark: BenchOnMetric = (marker, meta) => {
    if (this.printed) return;
    this.entries.push({ marker, tRel: Date.now() - this.t0, meta });
  };

  /** Druckt eine kompakte Tabelle. Idempotent (nur 1x pro Session). */
  print(extraNote?: string): void {
    if (this.printed) return;
    this.printed = true;

    const get = (m: BenchMarker) => this.entries.find((e) => e.marker === m)?.tRel;
    const fmt = (v: number | undefined) => (v == null ? '—' : `${v}ms`);
    const diff = (a: BenchMarker, b: BenchMarker) => {
      const va = get(a), vb = get(b);
      return va != null && vb != null ? `${vb - va}ms` : '—';
    };

    const stages = {
      tts: this.provider,
      llm: this.llm,
      label: this.label,
      'req→session': fmt(get('session-post-done')),
      'lyra-ttfb': fmt(get('sse-first-chunk')),
      'lyra-done': fmt(get('sse-done')),
      'tts-fired': fmt(get('tts-fetch-start')),
      'tts-ttfb (rel)': diff('tts-fetch-start', 'tts-fetch-headers'),
      'tts-body (rel)': diff('tts-fetch-headers', 'tts-body-done'),
      'tts-file (rel)': diff('tts-body-done', 'tts-file-written'),
      'audio-load (rel)': diff('tts-file-written', 'audio-loaded'),
      'first-audio': fmt(get('first-audio')),
      'TOTAL → speak': fmt(get('first-audio')),
    };

    // Eine kompakte Zeile als console.log (für Logbox-Lesbarkeit) +
    // console.table mit allen Markern (für strukturierte Inspektion).
    // eslint-disable-next-line no-console
    console.log(
      `[bench] LLM=${this.llm} TTS=${this.provider} (${this.label})${extraNote ? ' ' + extraNote : ''}`,
      stages,
    );
    // eslint-disable-next-line no-console
    console.table(this.entries.map((e) => ({ marker: e.marker, tRel: e.tRel })));
  }

  /** Snapshot für UI-Overlays (Debug-Drawer etc.). */
  snapshot(): { provider: string; llm: string; label: string; entries: MarkerEntry[] } {
    return { provider: this.provider, llm: this.llm, label: this.label, entries: [...this.entries] };
  }
}
