/**
 * The board, as the surface renders it.
 *
 * The pieces of the world — transactions, the KYC block, the alert, the
 * summary — are the port's own types, re-exported from `./generator` so the
 * surface is typed against the shapes CI hashes (digest `world` in
 * `envs/alert_triage/CONFORMANCE.txt`) rather than a restatement of them.
 * Field names are snake_case because they are the canonical JSON's keys.
 *
 * Amounts are integers in minor units, never floats; dates are `YYYY-MM-DD`;
 * months are `YYYY-MM`. Everything is synthetic.
 *
 * Types only, plus the two enums the controls offer as choices.
 */

export type {
  Alert,
  Counterparty,
  Customer,
  Document,
  Label,
  Overlay,
  PriorAlert,
  Screen,
  ScreenCounterparty,
  SummaryRow,
  Template,
  Tier,
  Txn as Transaction,
} from './generator';

import type { Counterparty, Document, Label, Overlay, PriorAlert, Screen, Template, Tier, Txn } from './generator';

/** One accepted lookup and what it returned, in the order they were made. */
export type Lookup =
  | { what: 'history'; key: string; result: { month: string; transactions: Txn[] } }
  | { what: 'counterparty'; key: string; result: Counterparty }
  | { what: 'prior_alerts'; key: null; result: { alerts: PriorAlert[] } }
  | { what: 'documents'; key: null; result: { documents: Document[] } };

export type LookupKind = Lookup['what'];

export type CloseReason =
  | 'CONSISTENT_WITH_PROFILE'
  | 'DOCUMENTED_SOURCE_OF_FUNDS'
  | 'PREVIOUSLY_REVIEWED'
  | 'RULE_ARTEFACT';

export type Typology = 'STRUCTURING' | 'FUNNEL_ACCOUNT' | 'MONEY_MULE' | 'RAPID_MOVEMENT' | 'UNKNOWN';

export const CLOSE_REASONS: readonly CloseReason[] = [
  'CONSISTENT_WITH_PROFILE',
  'DOCUMENTED_SOURCE_OF_FUNDS',
  'PREVIOUSLY_REVIEWED',
  'RULE_ARTEFACT',
];

export const TYPOLOGIES: readonly Typology[] = [
  'STRUCTURING',
  'FUNNEL_ACCOUNT',
  'MONEY_MULE',
  'RAPID_MOVEMENT',
  'UNKNOWN',
];

/** An accepted disposition, in the engine's canonical form. Ends the episode. */
export type Disposition =
  | { action: 'close'; reason: CloseReason; cites: string[]; note: string }
  | { action: 'escalate'; typology: Typology; cites: string[]; narrative: string };

/**
 * What the generator planted, shown only once the episode is over. On the
 * board before then it would be the answer key taped to the exam.
 */
export interface Reveal {
  label: Label;
  typology: string | null;
  /** Alternates; the grader scores the best one. */
  planted: string[][];
  template: Template;
  overlay: Overlay | null;
  tier: Tier | null;
  /**
   * Minutes the cheapest shipped analyst needed on this seed. Null when the
   * board does not know — a recorded run carries it as a metric; a fresh
   * board in Play does only if the port ran the reference policies.
   */
  referenceMinutes: number | null;
}

export type Status = 'playing' | 'closed' | 'escalated' | 'aborted';

/**
 * The board after a step. Snapshots, never deltas: the scrubber jumps to any
 * step, and every field the surface reads is here in full.
 */
export interface TriageState {
  seed: number;
  screen: Screen;
  lookups: Lookup[];
  /** Replies spent, accepted or not. Eight ends the episode. */
  turnsSpent: number;
  rejected: number;
  /** The hour meter, in minutes, so it agrees with the grader to the digit. */
  minutesSpent: number;
  /** Why the most recent reply was refused, or null if it was accepted. */
  lastRejection: string | null;
  disposition: Disposition | null;
  status: Status;
  /** The grader's verdict on the disposition, once there is one. */
  outcome: 'solved' | 'failed' | 'aborted' | null;
  /** Every id the engine has rendered so far — the only ids a disposition may cite. */
  shown: string[];
  /** The replies played so far. The seam replays these to take the next step. */
  replies: (string | null)[];
  reveal: Reveal | null;
}
