///|
/// Create a persistent cursor positioned at the first slot-ordered set key.
pub fn StaticSet::cursor(self : StaticSet) -> StaticSetCursor {
  { keys_by_slot: self.keys_by_slot.copy(), next_slot: 0 }
}

///|
/// Create a persistent cursor at one set slot. This is useful for checkpointed
/// scans after an external consumer has durably processed a prefix.
pub fn StaticSet::cursor_at(
  self : StaticSet,
  slot : Int,
) -> Result[StaticSetCursor, MphfError] {
  if slot < 0 || slot > self.keys_by_slot.length() {
    return Err(InvalidRange(slot, self.keys_by_slot.length()))
  }
  Ok({ keys_by_slot: self.keys_by_slot.copy(), next_slot: slot })
}

///|
/// Consume at most one set key. The original cursor is unchanged; `None`
/// signals exhaustion and returns an equivalent exhausted cursor.
pub fn StaticSetCursor::next(self : StaticSetCursor) -> (Int?, StaticSetCursor) {
  if self.next_slot >= self.keys_by_slot.length() {
    return (None, self)
  }
  let key = self.keys_by_slot[self.next_slot]
  (
    Some(key),
    { keys_by_slot: self.keys_by_slot, next_slot: self.next_slot + 1 },
  )
}

///|
/// Return the number of unread keys in this set cursor.
pub fn StaticSetCursor::remaining(self : StaticSetCursor) -> Int {
  self.keys_by_slot.length() - self.next_slot
}

///|
/// Return whether the cursor has no unread key.
pub fn StaticSetCursor::is_exhausted(self : StaticSetCursor) -> Bool {
  self.remaining() == 0
}

///|
/// Materialize the unread suffix of a set cursor in slot order.
pub fn StaticSetCursor::collect_remaining(self : StaticSetCursor) -> Array[Int] {
  let keys : Array[Int] = []
  for index in self.next_slot.. StaticIntMapCursor {
  {
    keys_by_slot: self.keys_by_slot.copy(),
    values_by_slot: self.values_by_slot.copy(),
    next_slot: 0,
  }
}

///|
/// Create a persistent map cursor at an entry slot.
pub fn StaticIntMap::cursor_at(
  self : StaticIntMap,
  slot : Int,
) -> Result[StaticIntMapCursor, MphfError] {
  if slot < 0 || slot > self.keys_by_slot.length() {
    return Err(InvalidRange(slot, self.keys_by_slot.length()))
  }
  Ok({
    keys_by_slot: self.keys_by_slot.copy(),
    values_by_slot: self.values_by_slot.copy(),
    next_slot: slot,
  })
}

///|
/// Consume at most one map entry and return the successor cursor.
pub fn StaticIntMapCursor::next(
  self : StaticIntMapCursor,
) -> (IntEntry?, StaticIntMapCursor) {
  if self.next_slot >= self.keys_by_slot.length() {
    return (None, self)
  }
  let entry : IntEntry = {
    key: self.keys_by_slot[self.next_slot],
    value: self.values_by_slot[self.next_slot],
  }
  (
    Some(entry),
    {
      keys_by_slot: self.keys_by_slot,
      values_by_slot: self.values_by_slot,
      next_slot: self.next_slot + 1,
    },
  )
}

///|
/// Return the number of unread map entries in this cursor.
pub fn StaticIntMapCursor::remaining(self : StaticIntMapCursor) -> Int {
  self.keys_by_slot.length() - self.next_slot
}

///|
/// Materialize all unread map entries in deterministic slot order.
pub fn StaticIntMapCursor::collect_remaining(
  self : StaticIntMapCursor,
) -> Array[IntEntry] {
  let entries : Array[IntEntry] = []
  for index in self.next_slot.. StaticIntMultiMapCursor {
  {
    keys_by_slot: self.keys_by_slot.copy(),
    offsets_by_slot: self.offsets_by_slot.copy(),
    values: self.values.copy(),
    slot: 0,
    value_index: 0,
  }
}

///|
/// Consume at most one multimap key/value item. A multimap always has at least
/// one value per key when built through the public constructor or decoder.
pub fn StaticIntMultiMapCursor::next(
  self : StaticIntMultiMapCursor,
) -> (IntMultiEntry?, StaticIntMultiMapCursor) {
  let mut slot = self.slot
  let mut value_index = self.value_index
  while slot < self.keys_by_slot.length() {
    let start = self.offsets_by_slot[slot]
    let stop = self.offsets_by_slot[slot + 1]
    if value_index < start {
      value_index = start
    }
    if value_index < stop {
      let entry : IntMultiEntry = {
        key: self.keys_by_slot[slot],
        value: self.values[value_index],
      }
      return (
        Some(entry),
        {
          keys_by_slot: self.keys_by_slot,
          offsets_by_slot: self.offsets_by_slot,
          values: self.values,
          slot,
          value_index: value_index + 1,
        },
      )
    }
    slot += 1
    value_index = 0
  }
  (None, self)
}

///|
/// Count values that have not been yielded. The loop keeps the operation
/// allocation-free and remains correct even for a validated zero-length range.
pub fn StaticIntMultiMapCursor::remaining(
  self : StaticIntMultiMapCursor,
) -> Int {
  let mut total = 0
  for slot in self.slot.. start {
      total += stop - self.value_index
    } else {
      total += stop - start
    }
  }
  total
}

///|
/// Materialize every unread multimap entry by advancing a local cursor.
pub fn StaticIntMultiMapCursor::collect_remaining(
  self : StaticIntMultiMapCursor,
) -> Array[IntMultiEntry] {
  let entries : Array[IntMultiEntry] = []
  let mut cursor = self
  while cursor.remaining() > 0 {
    let (item, successor) = cursor.next()
    match item {
      Some(entry) => entries.push(entry)
      None => return entries
    }
    cursor = successor
  }
  entries
}