///|
/// Test whether every supplied key belongs to this exact static set. An empty
/// query succeeds, which makes this useful for validating optional filters.
pub fn StaticSet::contains_all(self : StaticSet, keys : Array[Int]) -> Bool {
  for key in keys {
    if !self.contains(key) {
      return false
    }
  }
  true
}

///|
/// Return all input keys that are present, preserving query order and repeated
/// probes. This is deliberately different from set algebra.
pub fn StaticSet::filter_present(
  self : StaticSet,
  keys : Array[Int],
) -> Array[Int] {
  let present : Array[Int] = []
  for key in keys {
    if self.contains(key) {
      present.push(key)
    }
  }
  present
}

///|
/// Return all input keys absent from this set, preserving query order.
pub fn StaticSet::filter_missing(
  self : StaticSet,
  keys : Array[Int],
) -> Array[Int] {
  let missing : Array[Int] = []
  for key in keys {
    if !self.contains(key) {
      missing.push(key)
    }
  }
  missing
}

///|
/// Count exact hit and miss outcomes for a batch of set probes.
pub fn StaticSet::query_summary(
  self : StaticSet,
  keys : Array[Int],
) -> LookupSummary {
  let mut hits = 0
  for key in keys {
    if self.contains(key) {
      hits += 1
    }
  }
  {
    query_count: keys.length(),
    hit_count: hits,
    miss_count: keys.length() - hits,
  }
}

///|
/// Probe many map keys at once. Result positions correspond exactly to query
/// positions; misses are represented by `None`.
pub fn StaticIntMap::get_many(
  self : StaticIntMap,
  keys : Array[Int],
) -> Array[Int?] {
  let values : Array[Int?] = []
  for key in keys {
    values.push(self.get(key))
  }
  values
}

///|
/// Count exact hit and miss outcomes for a batch of map probes.
pub fn StaticIntMap::query_summary(
  self : StaticIntMap,
  keys : Array[Int],
) -> LookupSummary {
  let mut hits = 0
  for key in keys {
    if self.contains_key(key) {
      hits += 1
    }
  }
  {
    query_count: keys.length(),
    hit_count: hits,
    miss_count: keys.length() - hits,
  }
}

///|
/// Probe many multimap keys at once. Each returned sub-array is independent,
/// so callers can safely mutate their own result without touching the index.
pub fn StaticIntMultiMap::get_all_many(
  self : StaticIntMultiMap,
  keys : Array[Int],
) -> Array[Array[Int]] {
  let values : Array[Array[Int]] = []
  for key in keys {
    values.push(self.get_all(key))
  }
  values
}

///|
/// Validate the internal dimensions and vertex ranges of a decoded MPHF. This
/// does not establish source membership because an MPHF intentionally stores
/// no source-key manifest.
pub fn Mphf::validate(self : Mphf) -> Result[Unit, MphfError] {
  if self.key_count <= 0 ||
    self.vertex_count < 3 ||
    self.seed < 0 ||
    self.attempts <= 0 ||
    self.values.length() != self.vertex_count {
    return Err(InvalidMetadata)
  }
  for value in self.values {
    if value < 0 || value >= self.key_count {
      return Err(InvalidVertexValue(value))
    }
  }
  Ok(())
}

///|
/// Verify every retained source key routes to its recorded set slot.
pub fn StaticSet::validate(self : StaticSet) -> Result[Unit, MphfError] {
  match self.mphf.validate() {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  validate_slot_keys(self.mphf, self.keys_by_slot)
}

///|
/// Verify every retained source key routes to its recorded map slot and that
/// the key/value arrays have matching cardinality.
pub fn StaticIntMap::validate(self : StaticIntMap) -> Result[Unit, MphfError] {
  if self.values_by_slot.length() != self.keys_by_slot.length() {
    return Err(
      InvalidPayloadLength(
        self.keys_by_slot.length(),
        self.values_by_slot.length(),
      ),
    )
  }
  match self.mphf.validate() {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  validate_slot_keys(self.mphf, self.keys_by_slot)
}

///|
/// Verify a multimap's MPHF slots and its contiguous value ranges.
pub fn StaticIntMultiMap::validate(
  self : StaticIntMultiMap,
) -> Result[Unit, MphfError] {
  match self.mphf.validate() {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  match validate_slot_keys(self.mphf, self.keys_by_slot) {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  validate_offsets(
    self.offsets_by_slot,
    self.values.length(),
    self.keys_by_slot.length(),
  )
}

///|
/// Compute a reproducible source-manifest fingerprint for a set. The result is
/// intended for accidental-artifact mismatch detection, not cryptographic use.
pub fn StaticSet::fingerprint(self : StaticSet) -> StaticIndexFingerprint {
  let keys = self.keys_by_slot.copy()
  keys.sort()
  {
    key_count: keys.length(),
    key_fingerprint: fingerprint_keys(keys),
    value_fingerprint: 0,
  }
}

///|
/// Compute a reproducible source-manifest fingerprint for a map. Entries are
/// normalized by key, so its value is independent of MPHF slot placement.
pub fn StaticIntMap::fingerprint(self : StaticIntMap) -> StaticIndexFingerprint {
  let entries = self.entries_by_slot()
  entries.sort_by((left, right) => left.key.compare(right.key))
  let keys : Array[Int] = []
  let values : Array[Int] = []
  for entry in entries {
    keys.push(entry.key)
    values.push(entry.value)
  }
  {
    key_count: keys.length(),
    key_fingerprint: fingerprint_keys(keys),
    value_fingerprint: fingerprint_values(values),
  }
}

///|
/// Fold sorted source keys with an index-sensitive deterministic mixer.
fn fingerprint_keys(keys : Array[Int]) -> Int {
  let mut state = 0x2911
  for index in 0.. Int {
  let mut state = 0x2d3f
  for index in 0..