///|
/// C Heap wrapper for GC objects
/// This wraps the C-managed GC heap that can be used by both
/// the interpreter and JIT-compiled code.

// ============================================================
// Error Types
// ============================================================

///|
/// CHeap error types for runtime errors
pub(all) suberror CHeapError {
  OutOfBoundsArrayAccess
  NullReference
  OutOfMemory
}

// ============================================================
// Value Encoding Constants
// ============================================================

// Object kinds (must match GC_KIND_* in gc_heap.h)

///|
const GC_KIND_STRUCT : Int32 = 1

///|
const GC_KIND_ARRAY : Int32 = 2

///|
/// Managed GC heap handle. Copies retain the heap independently of its owner.
pub using @jit_ffi {type GCHeapHandle}

///|
/// Logical owner of a shared managed GC heap.
pub struct CHeap {
  priv mut ptr : GCHeapHandle
} derive(Debug)

///|
/// Create a new C heap with the given initial capacity
pub fn CHeap::CHeap(
  capacity~ : Int32 = @types.ONE_MIB_BYTES,
) -> CHeap raise CHeapError {
  let ptr = GCHeapHandle::new(capacity.to_int64())
  guard !ptr.is_null() else { raise OutOfMemory }
  { ptr, }
}

///|
/// Close this owner. Other managed handles, contexts and root leases stay live.
pub fn CHeap::free(self : CHeap) -> Unit {
  self.ptr.clear_root_visitor()
  self.ptr = GCHeapHandle::null()
}

///|
/// Retain the managed C heap for JIT or exception use.
pub fn CHeap::get_ptr(self : CHeap) -> GCHeapHandle {
  self.ptr
}

///|
/// Return an owner-bound action that reclaims later allocations unless they
/// remain reachable when the action runs. Retained object-table indices are
/// never reused.
pub fn CHeap::make_allocation_rollback(
  self : CHeap,
  roots : () -> Array[@types.Value],
) -> () -> Unit {
  let object_count = self.get_object_count()
  fn() {
    let current_roots = roots()
    let num_roots = current_roots.length()
    let raw_roots = FixedArray::makei(num_roots, fn(i) {
      value_to_i64(current_roots[i])
    })
    c_gc_heap_rollback_allocations(self.ptr, object_count, raw_roots, num_roots)
    |> ignore
  }
}

// ============================================================
// Value Encoding/Decoding
// ============================================================

///|
/// Flatten values into the [lo, hi] word pairs the wide C entry points take.
fn slots_to_words(values : Array[@types.Value]) -> FixedArray[Int64] {
  FixedArray::makei(values.length() * 2, fn(i) {
    let slot = value_to_slot(values[i / 2])
    if i % 2 == 0 {
      slot.lo
    } else {
      slot.hi
    }
  })
}

// ============================================================
// Struct Operations
// ============================================================

///|
/// Allocate a new struct
/// Returns gc_ref (0-based index for external use)
pub fn CHeap::alloc_struct(
  self : CHeap,
  type_idx : Int32,
  fields : Array[@types.Value],
) -> Int32 raise CHeapError {
  let num_fields = fields.length()
  let raw_fields = slots_to_words(fields)
  let gc_ref = c_gc_heap_alloc_struct_wide(
    self.ptr,
    type_idx,
    raw_fields,
    num_fields,
  )
  if gc_ref <= 0 {
    raise OutOfMemory
  }
  // C heap returns 1-based gc_ref, convert to 0-based for external use
  gc_ref - 1
}

///|
/// Get a struct field value
pub fn CHeap::struct_get(
  self : CHeap,
  struct_idx : Int32,
  field_idx : Int32,
  field_type : @types.ValueType,
) -> @types.Value {
  // Convert 0-based to 1-based for C heap
  let gc_ref = struct_idx + 1
  let out = FixedArray::make(2, 0L)
  c_gc_heap_struct_get_wide(self.ptr, gc_ref, field_idx, out)
  slot_to_value({ lo: out[0], hi: out[1], }, field_type)
}

///|
/// Set a struct field value
pub fn CHeap::struct_set(
  self : CHeap,
  struct_idx : Int32,
  field_idx : Int32,
  value : @types.Value,
) -> Unit {
  let gc_ref = struct_idx + 1
  let slot = value_to_slot(value)
  c_gc_heap_struct_set_wide(self.ptr, gc_ref, field_idx, slot.lo, slot.hi)
}

// ============================================================
// Array Operations
// ============================================================

///|
/// Allocate a new array with initial value
/// Returns gc_ref (0-based index for external use)
pub fn CHeap::alloc_array(
  self : CHeap,
  type_idx : Int32,
  len : Int32,
  init_value : @types.Value,
) -> Int32 raise CHeapError {
  let slot = value_to_slot(init_value)
  let gc_ref = c_gc_heap_alloc_array_wide(
    self.ptr,
    type_idx,
    len,
    slot.lo,
    slot.hi,
  )
  if gc_ref <= 0 {
    raise OutOfMemory
  }
  gc_ref - 1
}

///|
/// Allocate a new array from existing values
/// Returns gc_ref (0-based index for external use)
pub fn CHeap::alloc_array_from_values(
  self : CHeap,
  type_idx : Int32,
  elements : Array[@types.Value],
) -> Int32 raise CHeapError {
  let len = elements.length()
  let raw_values = slots_to_words(elements)
  let gc_ref = c_gc_heap_alloc_array_from_slots(
    self.ptr,
    type_idx,
    raw_values,
    len,
  )
  if gc_ref <= 0 {
    raise OutOfMemory
  }
  gc_ref - 1
}

///|
/// Get array length
pub fn CHeap::array_len(self : CHeap, array_idx : Int32) -> Int32 {
  let gc_ref = array_idx + 1
  c_gc_heap_array_len(self.ptr, gc_ref)
}

///|
/// Get an array element
pub fn CHeap::array_get(
  self : CHeap,
  array_idx : Int32,
  elem_idx : Int32,
  elem_type : @types.ValueType,
) -> @types.Value raise CHeapError {
  let gc_ref = array_idx + 1
  // Check bounds before accessing
  let len = c_gc_heap_array_len(self.ptr, gc_ref)
  if elem_idx < 0 || elem_idx >= len {
    raise OutOfBoundsArrayAccess
  }
  let out = FixedArray::make(2, 0L)
  c_gc_heap_array_get_wide(self.ptr, gc_ref, elem_idx, out)
  slot_to_value({ lo: out[0], hi: out[1], }, elem_type)
}

///|
/// Set an array element
pub fn CHeap::array_set(
  self : CHeap,
  array_idx : Int32,
  elem_idx : Int32,
  value : @types.Value,
) -> Unit raise CHeapError {
  let gc_ref = array_idx + 1
  // Check bounds before accessing
  let len = c_gc_heap_array_len(self.ptr, gc_ref)
  if elem_idx < 0 || elem_idx >= len {
    raise OutOfBoundsArrayAccess
  }
  let slot = value_to_slot(value)
  c_gc_heap_array_set_wide(self.ptr, gc_ref, elem_idx, slot.lo, slot.hi)
}

///|
/// Fill array elements with a value
pub fn CHeap::array_fill(
  self : CHeap,
  array_idx : Int32,
  offset : Int32,
  value : @types.Value,
  count : Int32,
) -> Unit raise CHeapError {
  let gc_ref = array_idx + 1
  // Check bounds before filling
  let len = c_gc_heap_array_len(self.ptr, gc_ref)
  if offset < 0 || count < 0 || offset + count > len {
    raise OutOfBoundsArrayAccess
  }
  let slot = value_to_slot(value)
  c_gc_heap_array_fill_wide(self.ptr, gc_ref, offset, slot.lo, slot.hi, count)
}

///|
/// Copy array elements
pub fn CHeap::array_copy(
  self : CHeap,
  dst_idx : Int32,
  dst_offset : Int32,
  src_idx : Int32,
  src_offset : Int32,
  count : Int32,
) -> Unit raise CHeapError {
  let dst_ref = dst_idx + 1
  let src_ref = src_idx + 1
  // Check bounds before copying
  let dst_len = c_gc_heap_array_len(self.ptr, dst_ref)
  let src_len = c_gc_heap_array_len(self.ptr, src_ref)
  if dst_offset < 0 ||
    src_offset < 0 ||
    count < 0 ||
    dst_offset + count > dst_len ||
    src_offset + count > src_len {
    raise OutOfBoundsArrayAccess
  }
  c_gc_heap_array_copy(
    self.ptr,
    dst_ref,
    dst_offset,
    src_ref,
    src_offset,
    count,
  )
}

// ============================================================
// Type Information
// ============================================================

///|
/// Get the type index of an object
pub fn CHeap::get_type_idx(self : CHeap, idx : Int32) -> Int32 {
  let gc_ref = idx + 1
  c_gc_heap_get_type_idx(self.ptr, gc_ref)
}

///|
/// Get the kind of an object (1=struct, 2=array)
pub fn CHeap::get_kind(self : CHeap, idx : Int32) -> Int32 {
  let gc_ref = idx + 1
  c_gc_heap_get_kind(self.ptr, gc_ref)
}

///|
/// Check if this is a struct
pub fn CHeap::is_struct(self : CHeap, idx : Int32) -> Bool {
  self.get_kind(idx) == GC_KIND_STRUCT
}

///|
/// Check if this is an array
pub fn CHeap::is_array(self : CHeap, idx : Int32) -> Bool {
  self.get_kind(idx) == GC_KIND_ARRAY
}

///|
/// Check if an object reference is valid
pub fn CHeap::is_valid(self : CHeap, idx : Int32) -> Bool {
  let gc_ref = idx + 1
  c_gc_heap_is_valid(self.ptr, gc_ref) != 0
}

// ============================================================
// GC Operations
// ============================================================

///|
/// Perform garbage collection with given roots
/// Returns the number of objects collected
pub fn CHeap::collect(self : CHeap, roots : Array[@types.Value]) -> Int32 {
  let num_roots = roots.length()
  let raw_roots = FixedArray::makei(num_roots, fn(i) { value_to_i64(roots[i]) })
  c_gc_heap_collect(self.ptr, raw_roots, num_roots)
}

///|
/// Complete native root words, including tagged exception snapshots.
pub fn CHeap::collect_raw(self : CHeap, roots : Array[Int64]) -> Int32 {
  let buffer = FixedArray::makei(roots.length(), i => roots[i])
  c_gc_heap_collect(self.ptr, buffer, buffer.length())
}

///|
/// Supply a transitive typed root set before collection (phase 0), and release
/// unreachable auxiliary handles after sweep (phase 1). Bind outside execution.
pub fn CHeap::bind_root_visitor(
  self : CHeap,
  visitor : (Int32, FixedArray[Int64]) -> FixedArray[Int64],
) -> Unit {
  self.ptr.bind_root_visitor(visitor)
}

///|
/// Low words for conservative tracing of allocations without a declared type.
pub fn CHeap::reference_candidates(
  self : CHeap,
  index : Int32,
) -> FixedArray[Int64] {
  self.ptr.reference_candidates(index + 1)
}

///|
/// Verify heap invariants (for debugging)
pub fn CHeap::verify(self : CHeap, verbose~ : Bool = false) -> Bool {
  c_gc_heap_verify(self.ptr, if verbose { 1 } else { 0 }) != 0
}

// ============================================================
// JIT Utilities
// ============================================================

///|
/// Get heap base pointer (for JIT inline access)
pub fn CHeap::get_base(self : CHeap) -> Int64 {
  c_gc_heap_get_base(self.ptr)
}

///|
/// Get object offset in heap (for JIT inline access)
pub fn CHeap::get_offset(self : CHeap, idx : Int32) -> Int32 {
  let gc_ref = idx + 1
  c_gc_heap_get_offset(self.ptr, gc_ref)
}

// ============================================================
// GC Statistics
// ============================================================

///|
/// Get current heap size (bytes used)
pub fn CHeap::get_size(self : CHeap) -> Int64 {
  c_gc_heap_get_size(self.ptr)
}

///|
/// Get heap capacity (total allocated bytes)
pub fn CHeap::get_capacity(self : CHeap) -> Int64 {
  c_gc_heap_get_capacity(self.ptr)
}

///|
/// Get heap usage ratio (0.0 to 1.0)
pub fn CHeap::get_usage_ratio(self : CHeap) -> Double {
  let size = self.get_size()
  let capacity = self.get_capacity()
  if capacity == 0L {
    0.0
  } else {
    size.to_double() / capacity.to_double()
  }
}

///|
/// Get number of objects in heap
pub fn CHeap::get_object_count(self : CHeap) -> Int32 {
  c_gc_heap_get_object_count(self.ptr)
}

///|
/// Get total number of write-barrier calls recorded
pub fn CHeap::get_barrier_writes(self : CHeap) -> Int32 {
  c_gc_heap_get_barrier_writes(self.ptr)
}

///|
/// Get total number of allocations since heap creation
pub fn CHeap::get_total_allocations(self : CHeap) -> Int32 {
  c_gc_heap_get_total_allocations(self.ptr)
}

///|
/// Get total number of GC cycles performed
pub fn CHeap::get_total_collections(self : CHeap) -> Int32 {
  c_gc_heap_get_total_collections(self.ptr)
}

///|
/// Check if GC should be triggered based on heap usage
/// Default threshold is 75% of capacity
pub fn CHeap::should_collect(self : CHeap, threshold~ : Double = 0.75) -> Bool {
  self.get_usage_ratio() >= threshold
}

///|
/// Configure allocation fault injection for debugging
pub fn gc_debug_set_fail_alloc(
  fail_at : Int32,
  fail_every~ : Int32 = 0,
) -> Unit {
  c_gc_heap_debug_set_fail_alloc(fail_at, fail_every)
}

///|
#borrow(pointer)
extern "c" fn get_runtime_type(
  pointer : GCHeapHandle,
  reference : Int32,
) -> Int32 = "wasmoon_gc_heap_get_runtime_type"

///|
#borrow(pointer)
extern "c" fn set_runtime_type(
  pointer : GCHeapHandle,
  reference : Int32,
  identity : Int32,
) -> Unit = "wasmoon_gc_heap_set_runtime_type"

///|
pub fn CHeap::runtime_type(self : CHeap, index : Int32) -> Int32 {
  get_runtime_type(self.ptr, index + 1)
}

///|
pub fn CHeap::set_runtime_type(
  self : CHeap,
  index : Int32,
  identity : Int32,
) -> Unit {
  set_runtime_type(self.ptr, index + 1, identity)
}

///|
/// Release a scoped embedding visitor after execution or collection completes.
pub fn CHeap::clear_root_visitor(self : CHeap) -> Unit {
  self.ptr.clear_root_visitor()
}