///|
/// Categories ordered by standard high-poker strength.
pub(all) enum Category {
  HighCard
  OnePair
  TwoPair
  ThreeOfAKind
  Straight
  Flush
  FullHouse
  FourOfAKind
  StraightFlush
} derive(Eq, Debug)

///|
/// The selected five cards and the ranks used to break ties, highest priority first.
/// Suits never break a tie. A royal flush is an ace-high StraightFlush.
pub struct Evaluation {
  category : Category
  tie_break : Array[Int]
  best_five : Array[Card]
  priv strength : Int
} derive(Eq, Debug)

///|
/// Compare evaluations: 1 if left wins, -1 if right wins, 0 for a tie.
pub fn compare(left : Evaluation, right : Evaluation) -> Int {
  if left.strength > right.strength {
    1
  } else if left.strength < right.strength {
    -1
  } else {
    0
  }
}

///|
/// Evaluate exactly five distinct cards.
pub fn evaluate_five(cards : Array[Card]) -> Result[Evaluation, String] {
  if cards.length() != 5 {
    return Err("Expected exactly five cards")
  }
  if validate_unique(cards) is Some(e) {
    return Err(e)
  }
  Ok(rank_five(cards))
}

///|
/// Evaluate five, six or seven distinct cards by exhaustive five-card subsets.
/// Equal-strength subsets keep the first combination in input order.
pub fn evaluate(cards : Array[Card]) -> Result[Evaluation, String] {
  if cards.length() < 5 || cards.length() > 7 {
    return Err("Expected five to seven cards")
  }
  if validate_unique(cards) is Some(e) {
    return Err(e)
  }
  Ok(best_unchecked(cards))
}

///|
fn best_unchecked(cards : Array[Card]) -> Evaluation {
  let mut best = rank_five([cards[0], cards[1], cards[2], cards[3], cards[4]])
  let n = cards.length()
  for a in 0..<(n - 4) {
    for b in (a + 1)..<(n - 3) {
      for c in (b + 1)..<(n - 2) {
        for d in (c + 1)..<(n - 1) {
          for e in (d + 1).. best.strength {
              best = candidate
            }
          }
        }
      }
    }
  }
  best
}

///|
fn rank_five(cards : Array[Card]) -> Evaluation {
  let counts = Array::make(15, 0)
  let mut flush = true
  for card in cards {
    counts[card.id / 4 + 2] += 1
    if card.id % 4 != cards[0].id % 4 {
      flush = false
    }
  }
  let singles = []
  let pairs = []
  let mut trips = 0
  let mut quads = 0
  let ranks = []
  for i in 0..<13 {
    let r = 14 - i
    if counts[r] > 0 {
      ranks.push(r)
    }
    match counts[r] {
      4 => quads = r
      3 => trips = r
      2 => pairs.push(r)
      1 => singles.push(r)
      _ => ()
    }
  }
  let mut straight = 0
  if ranks.length() == 5 {
    if ranks[0] - ranks[4] == 4 {
      straight = ranks[0]
    } else if ranks == [14, 5, 4, 3, 2] {
      straight = 5
    }
  }
  let (category, number, tie_break) = if flush && straight > 0 {
    (StraightFlush, 8, [straight])
  } else if quads > 0 {
    (FourOfAKind, 7, [quads, singles[0]])
  } else if trips > 0 && pairs.length() == 1 {
    (FullHouse, 6, [trips, pairs[0]])
  } else if flush {
    (Flush, 5, ranks)
  } else if straight > 0 {
    (Straight, 4, [straight])
  } else if trips > 0 {
    (ThreeOfAKind, 3, [trips, singles[0], singles[1]])
  } else if pairs.length() == 2 {
    (TwoPair, 2, [pairs[0], pairs[1], singles[0]])
  } else if pairs.length() == 1 {
    (OnePair, 1, [pairs[0], singles[0], singles[1], singles[2]])
  } else {
    (HighCard, 0, ranks)
  }
  let mut strength = number
  for i in 0..<5 {
    strength = strength * 15 +
      (if i < tie_break.length() { tie_break[i] } else { 0 })
  }
  { category, tie_break, best_five: cards.copy(), strength, }
}