/**
 * Events — protocol.md §3.
 *
 * An event is a FACT. Events are ordered, append-only, and fully determine state:
 * `state = fold(events)`. That one property gives reconnection, restart recovery and post-game
 * replay together.
 *
 * DESIGN RULE (overview.md, post-game replay): events must render STANDALONE. Carry the from/to,
 * not just an id the renderer has to resolve against live state — otherwise a replay viewer has to
 * reconstruct the whole board to draw one frame.
 */

import type { CarType, OfficeTier } from './content.ts';
import type { LocalOpsOption } from './intents.ts';
import type { CardId, GridCoord, PlayerIndex, RollingStock, TrayId } from './state.ts';

export type GameEvent =
  // -- clock
  | { type: 'stageBegan'; day: number; stage: number }
  | { type: 'phaseBegan'; phase: string }
  | { type: 'actorChanged'; player: PlayerIndex | null }
  // -- local operations
  | { type: 'localOpsOptionChosen'; player: PlayerIndex; option: LocalOpsOption }
  | { type: 'trayMoved'; trayId: TrayId; from: GridCoord; to: GridCoord; movesRemaining: number; facing?: 'n' | 's' | 'e' | 'w' }
  | { type: 'carsCoupled'; trayId: TrayId; at: GridCoord; stock: RollingStock[] }
  | { type: 'carsDropped'; trayId: TrayId; at: GridCoord; stock: RollingStock[] }
  | { type: 'consistSorted'; trayId: TrayId; at: GridCoord; before: RollingStock[]; after: RollingStock[] }
  | { type: 'cardDrawn'; player: PlayerIndex; source: 'homeOffice' | 'department'; slot?: number; cardId: CardId }
  /** `variant` is the chosen orientation (Gap 11); it must be replayable, so it rides the event. */
  | { type: 'cardPlayed'; player: PlayerIndex; cardId: CardId; placement?: GridCoord; variant?: number }
  | { type: 'trackLaid'; player: PlayerIndex; geometry: string; hand: string; at: GridCoord; variant: number; remaining: number }
  | { type: 'mainlineModified'; player: PlayerIndex; cardId: CardId; node: number; key: string; became?: string }
  | { type: 'redFlagsSet'; player: PlayerIndex; cardId: CardId; trayId: TrayId; node: number }
  | {
      type: 'trainsDestroyed';
      player: PlayerIndex;
      trains: { label: string; consist: RollingStock[] }[];
      reason: string;
      where: string;
    }
  | { type: 'flyingSwitch'; player: PlayerIndex; cardId: CardId; trayId: TrayId; to: GridCoord; stock: RollingStock[] }
  | { type: 'officeUpgraded'; player: PlayerIndex; from: OfficeTier; to: OfficeTier }
  | { type: 'cardDiscarded'; player: PlayerIndex; cardId: CardId; toSlot: number }
  | { type: 'deckReshuffled' }
  | { type: 'departmentRefilled'; slot: number; cardId: CardId }
  // -- freight agent
  | { type: 'stockToOutbound'; player: PlayerIndex; at: GridCoord; stock: RollingStock }
  | { type: 'inboundCleared'; player: PlayerIndex; at: GridCoord; stock: RollingStock }
  | { type: 'facilityUnjammed'; player: PlayerIndex; at: GridCoord; from: string; stock: RollingStock }
  // -- trains
  /**
   * §7 — a Timetabled Train card played from hand is scheduled by a 1D12 roll. `rngState` carries
   * the advanced RNG so that folding events reproduces the draw exactly.
   */
  | { type: 'trainScheduled'; player: PlayerIndex; trainNumber: number; roll: number; slot: number; rngState: number }
  /**
   * Train lifecycle. These were originally squeezed into `phaseBegan` with a free-text label,
   * which made the replay say "New Train" and nothing else. A train being made up, departing,
   * arriving or finishing its run are four distinct facts and deserve four event types.
   */
  | { type: 'enhancementPlaced'; player: PlayerIndex; key: string; at: GridCoord }
  | { type: 'extraQueued'; player: PlayerIndex; trainNumber: number }
  | { type: 'secondSectionOrdered'; player: PlayerIndex; trainNumber: number }
  | { type: 'trainMadeUp'; trainNumber: number; isExtra: boolean; at: string; direction: string }
  | { type: 'trainHeld'; trainNumber: number; reason: string }
  | { type: 'trainHighballed'; trainNumber: number; from: string; to: string }
  | { type: 'trainArrived'; trainNumber: number; consist: RollingStock[]; office: string }
  | { type: 'trainDiverted'; trainNumber: number; to: string; reason: string }
  | { type: 'trainCompleted'; trainNumber: number; consist: RollingStock[] }
  | { type: 'carPlacedOnTrain'; player: PlayerIndex; trayId: TrayId; stock: RollingStock }
  | { type: 'carPassed'; player: PlayerIndex; trayId: TrayId }
  | { type: 'dispatchBonusUsed'; key: string; bonus: number; trainNumber: number; againstTrain: number }
  | { type: 'clearanceRequested'; trainId: TrayId; occupiedBy: TrayId }
  | { type: 'clearanceGiven'; trainId: TrayId; allow: boolean }
  // -- load / unload
  | { type: 'passengersBoarded'; player: PlayerIndex; at: GridCoord }
  | { type: 'passengersDetrained'; player: PlayerIndex; at: GridCoord }
  | { type: 'loadStarted'; player: PlayerIndex; at: GridCoord; carType: CarType }
  | { type: 'loadAdvanced'; player: PlayerIndex; at: GridCoord; fromBox: number; toBox: number }
  | { type: 'unloadCompleted'; player: PlayerIndex; at: GridCoord; carType: CarType }
  | { type: 'loadCompleted'; player: PlayerIndex; at: GridCoord; carType: CarType }
  | { type: 'unloadBegan'; player: PlayerIndex; at: GridCoord; carType: CarType }
  // -- consequences
  | { type: 'revenueChanged'; player: PlayerIndex; delta: number; total: number; reason: string }
  | { type: 'phaseEnded'; player: PlayerIndex; phase: string };

export type EventType = GameEvent['type'];
