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

import { adapt } from './adapter';
import Controls from './controls';
import { initState, replay } from './game';
import meta from './meta';
import { narrative } from './narrative';
import { reward } from './reward';
import { score } from './score';
import Board from './surface';
import type { TriageState } from './types';

export default defineDemo<TriageState>({
  meta,
  narrative,
  reward,
  anatomy: {
    task:
      'Triage one transaction-monitoring alert on a synthetic customer: read the screen, pay analyst-hours for any lookups, then close it with a reason or escalate it with a typology, citing the facts.',
    actions:
      'One JSON object per reply — a lookup (a month’s history, a counterparty, prior alerts, documents) or a disposition (close with a reason, escalate with a typology), citing only ids you were shown. Anything else is refused and still costs a turn.',
    grader:
      'Regenerates the alert from the seed, compares the disposition and the cited ids to what the generator planted, and charges the hours. Deterministic Python: no model, no human, no prose read anywhere.',
    score:
      'Over half for nothing slipping through, a quarter for clearing it within the reference hours, a fifth for citing the facts that decide it. A missed escalation zeroes all three.',
  },
  provenance: {
    envPackage: 'alert_triage',
    tasksetId: 'alert-triage',
    verifiersVersion: '0.3.2.dev12',
    command: 'uv run python envs/probe.py --taskset alert-triage',
    credits: [
      {
        label: 'FinCEN — guidance on preparing a complete and sufficient SAR narrative (the templates are written from public typology descriptions, not from any case)',
        href: 'https://www.fincen.gov/sites/default/files/shared/sarnarrcompletguidfinal_112003.pdf',
      },
      {
        label: 'FATF — methods and trends (typology reports)',
        href: 'https://www.fatf-gafi.org/en/topics/methods-and-trends.html',
      },
      {
        label: 'verifiers — the environment API this mirrors',
        href: 'https://github.com/PrimeIntellect-ai/verifiers',
      },
      {
        label: 'Our synthetic name and word lists, and where they came from',
        href: 'https://git.karti.ai/PIG/PIG-Demo/src/branch/main/envs/alert_triage/names/PROVENANCE.md',
      },
    ],
  },
  adapt,
  Surface: Board,
  interactive: {
    init: initState,
    Controls: Controls,
  },
  /**
   * Re-derive the score from the replies alone.
   *
   * The world is regenerated from the seed and the replies are stepped through
   * the engine; the label, the planted set and the hours all come out of that,
   * never off the fixture. The one thing read from the recording is
   * `reference_hours` — it comes from running the shipped analysts, which the
   * browser has no business doing — and a missing one makes the run
   * *unverifiable* rather than wrong.
   */
  verify: (episode: DemoEpisode): RewardValues | null => {
    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 });
    const final = snapshots[snapshots.length - 1];
    // A run with no replies, or one that stopped before a disposition and
    // before the eighth turn, never reached a terminal state: not scored.
    if (!final || final.reveal === null) return null;

    const d = final.disposition;
    if (final.status === 'playing') return null;
    return score({
      label: final.reveal.label,
      planted: final.reveal.planted,
      disposition: d === null ? null : d.action,
      cites: d === null ? [] : d.cites,
      minutes_spent: final.minutesSpent,
      reference_minutes: referenceMinutes,
      turns_spent: final.turnsSpent,
      rejected: final.rejected,
      truncated: false,
    });
  },
});
