///|
/// A generation-checked handle for host-owned objects referenced by foreign
/// Wren instances. It prevents stale identifiers from observing reused slots.
pub(all) struct HostObjectHandle {
  slot : Int
  generation : Int
} derive(Eq, Debug)

///|
pub extend HostObjectHandle with Eq::{equal, not_equal}

///|
pub extend HostObjectHandle with Debug::{to_repr}

///|
pub struct HostObjectSlot[T] {
  mut value : T?
  mut generation : Int
}

///|
/// Small host-side arena useful when implementing `ForeignClassBinding`.
pub struct HostObjectTable[T] {
  slots : Array[HostObjectSlot[T]]
}

///|
pub fn[T] HostObjectTable::new() -> HostObjectTable[T] {
  { slots: [], }
}

///|
pub fn[T] HostObjectTable::insert(
  self : HostObjectTable[T],
  value : T,
) -> HostObjectHandle {
  for index = 0; index < self.slots.length(); index = index + 1 {
    if self.slots[index].value is None {
      let slot = self.slots[index]
      slot.value = Some(value)
      return { slot: index, generation: slot.generation, }
    }
  }
  self.slots.push({ value: Some(value), generation: 0, })
  { slot: self.slots.length() - 1, generation: 0, }
}

///|
pub fn[T] HostObjectTable::get(
  self : HostObjectTable[T],
  handle : HostObjectHandle,
) -> T? {
  if handle.slot < 0 || handle.slot >= self.slots.length() {
    return None
  }
  let slot = self.slots[handle.slot]
  if slot.generation != handle.generation {
    None
  } else {
    slot.value
  }
}

///|
pub fn[T] HostObjectTable::release(
  self : HostObjectTable[T],
  handle : HostObjectHandle,
) -> Bool {
  if handle.slot < 0 || handle.slot >= self.slots.length() {
    return false
  }
  let slot = self.slots[handle.slot]
  if slot.generation != handle.generation || slot.value is None {
    false
  } else {
    slot.value = None
    slot.generation = slot.generation + 1
    true
  }
}

///|
pub fn[T] HostObjectTable::live_count(self : HostObjectTable[T]) -> Int {
  let mut count = 0
  for slot in self.slots {
    if slot.value is Some(_) {
      count = count + 1
    }
  }
  count
}