///|
/// Winner indices are zero-based and can contain multiple players in a tie.
pub struct Showdown {
  winners : Array[Int]
  evaluations : Array[Evaluation]
} derive(Debug)

///|
pub extend Showdown with @moonbitlang/core/debug.Debug::{to_repr}

///|
/// Determine winners with exactly five board cards and two cards per player.
/// This compares hands only; it does not model bets, folded players or side pots.
pub fn showdown(
  hands : Array[Array[Card]],
  board : Array[Card],
) -> Result[Showdown, String] {
  if hands.length() < 2 || hands.length() > 23 {
    return Err("Expected 2 to 23 players")
  }
  if board.length() != 5 {
    return Err("Showdown requires five board cards")
  }
  let all = board.copy()
  for hand in hands {
    if hand.length() != 2 {
      return Err("Each player needs two hole cards")
    }
    for card in hand {
      all.push(card)
    }
  }
  if validate_unique(all) is Some(e) {
    return Err(e)
  }
  let evaluations = []
  let winners = []
  let mut best = -1
  for i in 0.. best {
      winners.clear()
      winners.push(i)
      best = evaluation.strength
    } else if evaluation.strength == best {
      winners.push(i)
    }
  }
  Ok({ winners, evaluations, })
}

///|
/// Exact counts over equally likely remaining boards for two known hands.
pub struct Equity {
  wins : Int
  losses : Int
  ties : Int
  total : Int
} derive(Eq, Debug)

///|
pub extend Equity with Eq::{equal, not_equal}

///|
pub extend Equity with @moonbitlang/core/debug.Debug::{to_repr}

///|
/// Fraction won outright (ties excluded).
pub fn Equity::win_probability(self : Equity) -> Double {
  self.wins.to_double() / self.total.to_double()
}

///|
/// Expected pot fraction with ties split equally between the two players.
pub fn Equity::pot_equity(self : Equity) -> Double {
  (self.wins.to_double() + self.ties.to_double() / 2.0) / self.total.to_double()
}

///|
/// Enumerate remaining boards on flop, turn or river (3 to 5 known board cards).
/// Preflop and opponent ranges are deliberately outside v0.1's scope.
pub fn exact_equity(
  hero : Array[Card],
  villain : Array[Card],
  board : Array[Card],
) -> Result[Equity, String] {
  if hero.length() != 2 || villain.length() != 2 {
    return Err("Each player needs two hole cards")
  }
  if board.length() < 3 || board.length() > 5 {
    return Err("Exact equity requires 3 to 5 board cards")
  }
  let known = board.copy()
  for card in hero {
    known.push(card)
  }
  for card in villain {
    known.push(card)
  }
  if validate_unique(known) is Some(e) {
    return Err(e)
  }
  let deck = []
  for id in 0..<52 {
    let card = Card::{ id, }
    if !known.contains(card) {
      deck.push(card)
    }
  }
  let completed = board.copy()
  let counts = [0, 0, 0]
  enumerate_boards(hero, villain, completed, deck, 0, counts)
  Ok({
    wins: counts[0],
    losses: counts[1],
    ties: counts[2],
    total: counts[0] + counts[1] + counts[2],
  })
}

///|
fn enumerate_boards(
  hero : Array[Card],
  villain : Array[Card],
  board : Array[Card],
  deck : Array[Card],
  start : Int,
  counts : Array[Int],
) -> Unit {
  if board.length() == 5 {
    let h = board.copy()
    let v = board.copy()
    for card in hero {
      h.push(card)
    }
    for card in villain {
      v.push(card)
    }
    let result = compare(best_unchecked(h), best_unchecked(v))
    let index = if result > 0 { 0 } else if result < 0 { 1 } else { 2 }
    counts[index] += 1
    return
  }
  let needed = 5 - board.length()
  for i in start..<(deck.length() - needed + 1) {
    board.push(deck[i])
    enumerate_boards(hero, villain, board, deck, i + 1, counts)
    ignore(board.pop())
  }
}