///|
/// Build a checked immutable one-to-one map. Both source keys and values must
/// be non-negative because each becomes an MPHF key in one direction.
pub fn StaticIntBiMap::from_entries(
  entries : Array[IntEntry],
) -> Result[StaticIntBiMap, MphfError] {
  if entries.length() == 0 {
    return Err(EmptyInput)
  }
  let reverse_entries : Array[IntEntry] = []
  let values : Array[Int] = []
  for entry in entries {
    if entry.value < 0 {
      return Err(NegativeKey(entry.value))
    }
    reverse_entries.push({ key: entry.value, value: entry.key })
    values.push(entry.value)
  }
  values.sort()
  for index in 1.. value
    Err(error) => return Err(error)
  }
  let reverse = match StaticIntMap::from_entries(reverse_entries) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({ forward, reverse })
}

///|
/// Look up the unique value associated with one source key.
pub fn StaticIntBiMap::get_by_key(self : StaticIntBiMap, key : Int) -> Int? {
  self.forward.get(key)
}

///|
/// Look up the unique source key associated with one value.
pub fn StaticIntBiMap::get_by_value(self : StaticIntBiMap, value : Int) -> Int? {
  self.reverse.get(value)
}

///|
/// Test whether a source key exists.
pub fn StaticIntBiMap::contains_key(self : StaticIntBiMap, key : Int) -> Bool {
  self.forward.contains_key(key)
}

///|
/// Test whether a mapped value exists.
pub fn StaticIntBiMap::contains_value(
  self : StaticIntBiMap,
  value : Int,
) -> Bool {
  self.reverse.contains_key(value)
}

///|
/// Return the number of one-to-one pairs.
pub fn StaticIntBiMap::len(self : StaticIntBiMap) -> Int {
  self.forward.len()
}

///|
/// Return source pairs in deterministic forward-slot order.
pub fn StaticIntBiMap::entries_by_key(self : StaticIntBiMap) -> Array[IntEntry] {
  self.forward.entries_by_slot()
}

///|
/// Verify both maps are individually valid and mutually inverse. This catches
/// a cross-payload substitution that ordinary nested map decoding cannot see.
pub fn StaticIntBiMap::validate(
  self : StaticIntBiMap,
) -> Result[Unit, MphfError] {
  if self.forward.len() != self.reverse.len() {
    return Err(InvalidMetadata)
  }
  match self.forward.validate() {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  match self.reverse.validate() {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  for entry in self.forward.entries_by_slot() {
    if self.reverse.get(entry.value) != Some(entry.key) {
      return Err(InvalidMetadata)
    }
  }
  for entry in self.reverse.entries_by_slot() {
    if self.forward.get(entry.value) != Some(entry.key) {
      return Err(InvalidMetadata)
    }
  }
  Ok(())
}

///|
/// Return forward construction diagnostics. Reverse diagnostics can differ
/// because values produce a different hypergraph, so callers can inspect the
/// reverse map explicitly through its validated serialized form.
pub fn StaticIntBiMap::stats(self : StaticIntBiMap) -> MphfStats {
  self.forward.stats()
}

///|
/// Encode a bidirectional map as two independently checked static map blobs.
///
/// Format: `[12, forward_word_count, forward_words..., reverse_words...]`.
pub fn StaticIntBiMap::encode_words(self : StaticIntBiMap) -> Array[Int] {
  let forward_words = self.forward.encode_words()
  let reverse_words = self.reverse.encode_words()
  let words : Array[Int] = [12, forward_words.length()]
  words.append(forward_words)
  words.append(reverse_words)
  words
}

///|
/// Decode a bidirectional map and verify that the two nested payloads describe
/// inverse mappings, not merely two individually valid static maps.
pub fn decode_static_int_bimap_words(
  words : Array[Int],
) -> Result[StaticIntBiMap, MphfError] {
  if words.length() == 0 {
    return Err(MissingHeader)
  }
  if words[0] != 12 {
    return Err(UnsupportedVersion(words[0]))
  }
  if words.length() < 3 || words[1] <= 0 {
    return Err(InvalidMetadata)
  }
  let forward_length = words[1]
  if forward_length > words.length() - 3 {
    return Err(InvalidMetadata)
  }
  let reverse_start = 2 + forward_length
  if reverse_start >= words.length() {
    return Err(InvalidPayloadLength(reverse_start + 1, words.length()))
  }
  let forward_words = bimap_word_slice(words, 2, reverse_start)
  let reverse_words = bimap_word_slice(words, reverse_start, words.length())
  let forward = match decode_static_int_map_words(forward_words) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let reverse = match decode_static_int_map_words(reverse_words) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let bimap = { forward, reverse }
  match bimap.validate() {
    Ok(_) => Ok(bimap)
    Err(error) => Err(error)
  }
}

///|
/// Copy one nested map payload while validating the outer framing separately.
fn bimap_word_slice(words : Array[Int], start : Int, stop : Int) -> Array[Int] {
  let result : Array[Int] = []
  for index in start..