///| Hash a string deterministically into the non-negative `Int` key domain.

///|
/// This lightweight mixer is for stable identifiers, not for passwords,
/// signatures, attacker-controlled routing, or any cryptographic purpose.
pub fn stable_string_hash(value : String) -> Int {
  let mut state = 1_469_598_103
  for character in value.to_array() {
    state = ((state ^ character.to_int()) * 16_777_619) & positive_mask
  }
  mix_hash(state, 0x4a7c15)
}

///|
/// Compare two distinct key sets and return their rebuild-relevant delta.
pub fn compare_key_sets(
  before : Array[Int],
  after : Array[Int],
) -> Result[KeySetDiff, MphfError] {
  match validate_keys(before) {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  match validate_keys(after) {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  Ok(diff_sorted_keys(sorted_copy(before), sorted_copy(after)))
}

///|
/// Compare two checked static sets without exposing their slot layout.
pub fn StaticSet::diff_to(self : StaticSet, other : StaticSet) -> KeySetDiff {
  diff_sorted_keys(
    sorted_copy(self.keys_by_slot),
    sorted_copy(other.keys_by_slot),
  )
}

///|
/// True when no rebuild work is needed for a source key-set comparison.
pub fn KeySetDiff::is_empty(self : KeySetDiff) -> Bool {
  self.added.length() == 0 && self.removed.length() == 0
}

///|
/// Number of source keys changed by this delta.
pub fn KeySetDiff::changed_count(self : KeySetDiff) -> Int {
  self.added.length() + self.removed.length()
}

///|
/// Copy and sort keys without mutating caller-owned arrays.
fn sorted_copy(keys : Array[Int]) -> Array[Int] {
  let sorted = keys.copy()
  sorted.sort()
  sorted
}

///|
/// Linear merge of two sorted, duplicate-free arrays.
fn diff_sorted_keys(before : Array[Int], after : Array[Int]) -> KeySetDiff {
  let added : Array[Int] = []
  let removed : Array[Int] = []
  let mut retained = 0
  let mut left = 0
  let mut right = 0
  while left < before.length() && right < after.length() {
    if before[left] == after[right] {
      retained += 1
      left += 1
      right += 1
    } else if before[left] < after[right] {
      removed.push(before[left])
      left += 1
    } else {
      added.push(after[right])
      right += 1
    }
  }
  while left < before.length() {
    removed.push(before[left])
    left += 1
  }
  while right < after.length() {
    added.push(after[right])
    right += 1
  }
  { added, removed, retained }
}