import type { DemoEpisode, DemoStep } from '@/lib/demo-kit';

import { hours } from './format';
import { replay } from './game';
import type { TriageState } from './types';

/**
 * A recorded episode becomes a list of board snapshots.
 *
 * Nothing is read off the fixture but the seed and the reply strings. The
 * world is regenerated from the seed and every reply is stepped through the
 * same engine the environment ran, so the board you scrub through is what the
 * engine did with what the model said — not what the capture script wrote
 * down about it. A fixture whose `info` disagrees with its own replies cannot
 * fool this adapter, because the adapter never looks.
 *
 * `reference_hours` is the one metric read, and only for the reveal: it comes
 * from running the shipped analysts, which the browser has no business doing.
 * Its absence leaves the reveal without a reference, never with a zero.
 *
 * Snapshots, not deltas: the scrubber jumps to any step.
 */
export function adapt(episode: DemoEpisode): DemoStep<TriageState>[] {
  const referenceHours = episode.metrics?.['reference_hours'];
  const referenceMinutes = typeof referenceHours === 'number' && Number.isFinite(referenceHours)
    ? Math.round(referenceHours * 60)
    : null;

  const snapshots = replay(
    episode.seed,
    episode.turns.map((turn) => turn.reply),
    { referenceMinutes },
  );

  let previous: TriageState | null = null;

  return episode.turns.map((turn, index) => {
    const state = snapshots[index] ?? snapshots[snapshots.length - 1];
    if (!state) {
      // Cannot happen: replay returns one snapshot per reply. Typed defensively
      // so a broken port degrades to a readable step rather than a crash.
      throw new Error(`replay produced no snapshot for turn ${index + 1}`);
    }
    const { announce, caption } = describe(state, previous, index);
    previous = state;
    return {
      index,
      state,
      reply: turn.reply,
      reasoning: turn.reasoning,
      call: turn.call,
      announce,
      caption,
    };
  });
}

/**
 * What a screen reader hears when a step lands, and the six words under the
 * chip. Derived from the change between snapshots, so it says what the engine
 * did rather than what the reply claimed.
 */
export function describe(
  state: TriageState,
  previous: TriageState | null,
  index: number,
): { announce: string; caption: string } {
  const turn = `Turn ${index + 1}`;
  const meter = `${hours(state.minutesSpent)} hours spent.`;

  const wasDone = previous !== null && previous.status !== 'playing';
  if (wasDone) {
    return { announce: `${turn}: the episode was already over.`, caption: 'after the end' };
  }

  if (state.lastRejection !== null && state.turnsSpent === (previous?.turnsSpent ?? 0) + 1 && state.lookups.length === (previous?.lookups.length ?? 0) && state.disposition === null) {
    const why = state.lastRejection;
    return {
      announce: `${turn} refused: ${why}. It still cost a turn. ${meter}`,
      caption: `refused: ${shorten(why)}`,
    };
  }

  const d = state.disposition;
  if (d !== null) {
    const what = d.action === 'close' ? `closed, ${d.reason.toLowerCase().replace(/_/g, ' ')}` : `escalated as ${d.typology.toLowerCase().replace(/_/g, ' ')}`;
    const cites = `citing ${d.cites.length} ${d.cites.length === 1 ? 'id' : 'ids'}`;
    const verdict =
      state.outcome === 'solved'
        ? 'Right call.'
        : state.reveal?.label === 'suspicious'
          ? 'Missed: the alert was suspicious.'
          : 'A clean customer was escalated.';
    return {
      announce: `${turn}: ${what}, ${cites}. ${verdict} ${meter}`,
      caption: d.action === 'close' ? 'closed' : 'escalated',
    };
  }

  if (state.status === 'aborted') {
    return {
      announce: `${turn}: no disposition in eight replies. The alert stays in the queue. ${meter}`,
      caption: 'aborted',
    };
  }

  const lookup = state.lookups[state.lookups.length - 1];
  if (lookup && state.lookups.length > (previous?.lookups.length ?? 0)) {
    switch (lookup.what) {
      case 'history':
        return {
          announce: `${turn}: looked up ${lookup.key}, ${lookup.result.transactions.length} transactions. ${meter}`,
          caption: `history ${lookup.key}`,
        };
      case 'counterparty':
        return {
          announce: `${turn}: looked up ${lookup.key}, ${lookup.result.name}, shared with ${lookup.result.other_alerted_accounts} other alerted accounts. ${meter}`,
          caption: `counterparty ${lookup.key}`,
        };
      case 'prior_alerts':
        return {
          announce: `${turn}: looked up prior alerts, ${lookup.result.alerts.length} on file. ${meter}`,
          caption: 'prior alerts',
        };
      case 'documents':
        return {
          announce: `${turn}: looked up documents, ${lookup.result.documents.length} on file. ${meter}`,
          caption: 'documents',
        };
    }
  }

  return { announce: `${turn}: nothing changed. ${meter}`, caption: 'no change' };
}

function shorten(text: string): string {
  return text.length > 40 ? `${text.slice(0, 37)}…` : text;
}
