///|
/// Shared managed GC heap. Contexts and parked root registrations retain it.
pub type GCHeapHandle

///|
pub impl Debug for GCHeapHandle with fn to_repr(_self) {
  Repr::literal("GCHeapHandle")
}

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

///|
pub extern "c" fn GCHeapHandle::null() -> GCHeapHandle = "wasmoon_gc_heap_empty"

///|
#borrow(self)
pub extern "c" fn GCHeapHandle::is_null(self : GCHeapHandle) -> Bool = "wasmoon_gc_heap_is_empty"

///|
#borrow(self)
pub extern "c" fn GCHeapHandle::reference_candidates(
  self : GCHeapHandle,
  reference : Int32,
) -> FixedArray[Int64] = "wasmoon_gc_heap_reference_candidates"

///|
#borrow(self, bytes, objects)
pub extern "c" fn GCHeapHandle::bind_reservations(
  self : GCHeapHandle,
  bytes : Reservation,
  objects : Reservation,
) -> Bool = "wasmoon_gc_heap_bind_reservations"

///|
#borrow(self)
pub extern "c" fn GCHeapHandle::quota_denied(self : GCHeapHandle) -> Int32 = "wasmoon_gc_heap_quota_denied"

///|
// Borrowed C stack request. It must not escape the synchronous callback.
#external
priv type AllocationRequest

///|
extern "c" fn AllocationRequest::attempt(self : AllocationRequest) -> Int32 = "wasmoon_gc_allocation_attempt"

///|
extern "c" fn AllocationRequest::collect(self : AllocationRequest) -> Int32 = "wasmoon_gc_allocation_collect"

///|
extern "c" fn AllocationRequest::denial(self : AllocationRequest) -> Int32 = "wasmoon_gc_allocation_denial"

///|
#borrow(state)
extern "c" fn allocate_heap(
  capacity : Int64,
  policy : FuncRef[(AllocationRequest, Int32) -> Int32],
  state : CollectionState,
  collection : FuncRef[(CollectionState, CollectionRequest, Bool) -> Int32],
) -> GCHeapHandle = "wasmoon_gc_heap_new"

///|
// Positive results are native object identities; negative results are trap
// codes from trap_codes.h. Return before C frees buffers and transfers control.
fn allocate(request : AllocationRequest, wide : Int32) -> Int32 {
  // Preserve the existing word/slot retry budgets in this first migration.
  let collections = if wide != 0 { 1 } else { 3 }
  // C already attempted the first physical allocation. Enter this callback
  // only after failure, so ordinary successful allocation stays native.
  for attempt = 0; attempt < collections; attempt = attempt + 1 {
    if request.collect() < 0 {
      return -10 // Collection could not preserve precise roots
    }
    let reference = request.attempt()
    if reference > 0 {
      return reference
    }
  }
  match request.denial() {
    1 => -25 // GC payload byte quota
    2 => -26 // GC identity slot quota
    _ => -9 // Allocation exhaustion
  }
}

///|
/// Create a managed heap with the closed native allocation policy installed.
pub fn GCHeapHandle::new(capacity : Int64) -> GCHeapHandle {
  allocate_heap(
    capacity,
    fn(request, wide) { allocate(request, wide) },
    { visitor: None, collecting: false, },
    fn(state, request, rollback) { state.collect(request, rollback) },
  )
}

///|
/// Snapshot active and parked native execution roots without collecting.
/// A single -2 word reports that a complete snapshot was unavailable.
#borrow(self)
pub extern "c" fn GCHeapHandle::execution_roots(
  self : GCHeapHandle,
) -> FixedArray[Int64] = "wasmoon_gc_heap_execution_roots"