///|
/// Deterministic pseudo-random generator for reproducible simulations.
///
/// This generator is not cryptographic. It is intentionally small and portable
/// so simulations behave the same across MoonBit targets.
pub(all) struct Rng {
  mut state : UInt64
}

///|
pub fn Rng::new(seed : UInt64) -> Rng {
  let normalized = if seed == 0UL { 0x9E3779B97F4A7C15UL } else { seed }
  { state: normalized }
}

///|
pub fn Rng::state(self : Rng) -> UInt64 {
  self.state
}

///|
pub fn Rng::next_u64(self : Rng) -> UInt64 {
  let mut x = self.state
  x = x ^ (x >> 12)
  x = x ^ (x << 25)
  x = x ^ (x >> 27)
  self.state = x
  x * 2685821657736338717UL
}

///|
pub fn Rng::next_int(self : Rng, bound : Int) -> Int {
  if bound <= 0 {
    0
  } else {
    (self.next_u64() % bound.to_uint64()).to_int()
  }
}

///|
pub fn Rng::next_bool(self : Rng) -> Bool {
  (self.next_u64() & 1UL) == 1UL
}

///|
pub fn Rng::next_range(self : Rng, low : Int, high : Int) -> Int {
  if high <= low {
    low
  } else {
    low + self.next_int(high - low)
  }
}

///|
pub fn[T] Rng::choose(self : Rng, items : Array[T]) -> T? {
  if items.length() == 0 {
    None
  } else {
    Some(items[self.next_int(items.length())])
  }
}

///|
pub fn[T] Rng::shuffle(self : Rng, items : Array[T]) -> Array[T] {
  let shuffled = items.copy()
  let mut i = shuffled.length() - 1
  while i > 0 {
    let j = self.next_int(i + 1)
    let tmp = shuffled[i]
    shuffled[i] = shuffled[j]
    shuffled[j] = tmp
    i -= 1
  }
  shuffled
}

///|
pub(all) struct WeightedChoice {
  label : String
  weight : Int
}

///|
pub fn weighted_choice(label : String, weight : Int) -> WeightedChoice {
  { label, weight: if weight < 0 { 0 } else { weight } }
}

///|
pub fn Rng::choose_weighted(
  self : Rng,
  choices : Array[WeightedChoice],
) -> String? {
  let mut total = 0
  for choice in choices {
    total += choice.weight
  }
  if total <= 0 {
    None
  } else {
    let ticket = self.next_int(total)
    let mut cursor = 0
    for choice in choices {
      cursor += choice.weight
      if ticket < cursor {
        return Some(choice.label)
      }
    }
    None
  }
}