///|
/// Bounded game history with simple threefold/perpetual-check adjudication.
/// Long-chase and tournament-specific exceptions are outside this policy.
pub struct Game {
  positions : Array[Board]
} derive(Debug)

///|
pub fn Game::new(board : Board) -> Game {
  { positions: [board], }
}

///|
pub fn Game::board(self : Game) -> Board {
  self.positions[self.positions.length() - 1]
}

///|
/// Replay legal moves, including analysis lines beyond an earlier repetition.
pub fn Game::play(self : Game, coordinate : String) -> Unit raise ChessError {
  if self.positions.length() >= 4097 {
    raise Invalid("game history limit 4096 plies")
  }
  let next = self.board().play(coordinate)
  self.positions.push(next)
}

///|
pub fn Game::status(self : Game) -> String {
  let current = self.board()
  if current.legal_moves().is_empty() {
    return if current.red {
      "red-loses-no-moves"
    } else {
      "black-loses-no-moves"
    }
  }
  let matches : Array[Int] = []
  for i, board in self.positions {
    if board == current {
      matches.push(i)
    }
  }
  if matches.length() < 3 {
    return "ongoing"
  }
  let start = matches[matches.length() - 3]
  let mut red_checks = true
  let mut black_checks = true
  for i in (start + 1).. Bool = () => false,
  on_iteration? : (SearchResult) -> Unit = _ => (),
) -> SearchResult raise ChessError {
  if depth < 1 || depth > 64 || node_limit < 1 || node_limit > 1000000000 {
    raise Invalid("search depth 1..64 and nodes 1..1000000000 required")
  }
  let status = self.status()
  if status != "ongoing" {
    let score = if status == "draw-repetition" {
      0
    } else if status.has_prefix(
        if self.board().red {
          "red-"
        } else {
          "black-"
        },
      ) {
      -1000000
    } else {
      1000000
    }
    return { best: None, score, depth: 0, nodes: 0, stopped: false, }
  }
  self
  .board()
  .search(
    depth,
    node_limit~,
    should_stop~,
    on_iteration~,
    history=self.positions[:self.positions.length() - 1].map(b => b.fen()),
  )
}