///|
fn validate_gc_setup(
  context : JITContext,
  func_type_count : Int32,
  func_table_ptr : Int64,
  num_funcs : Int32,
) -> Unit raise GCSetupError {
  if c_jit_context_is_valid(context) == 0 {
    raise MissingJITContext
  }
  if num_funcs < 0 {
    raise InvalidFuncCount(num_funcs~)
  }
  if func_type_count != num_funcs {
    raise FuncTypeIndicesLengthMismatch(
      func_type_indices_len=func_type_count,
      num_funcs~,
    )
  }
  if num_funcs > 0 && func_table_ptr == 0L {
    raise MissingFunctionTableContext(num_funcs~)
  }
}

///|
/// Immutable module metadata for repeated binding to one native context.
/// Preparation snapshots type mappings and native function addresses. Code must
/// outlive this environment; prepare again after changing the function table.
/// Heap/root/execution state is deliberately not captured here.
pub struct PreparedGCEnvironment {
  priv context : JITContext
  priv types : FixedArray[Int32]
  priv canonical : FixedArray[Int32]
  priv functions : FixedArray[Int32]
  priv addresses : FixedArray[Int64]
}

///|
/// Freeze metadata once after installing the context's complete function table.
pub fn NativeJITContext::prepare_gc_environment(
  self : NativeJITContext,
  types : ArrayView[@types.SubType],
  canonical_indices : ArrayView[Int32],
  func_type_indices : ArrayView[Int32],
) -> PreparedGCEnvironment raise GCSetupError {
  let count = context_func_count(self.handle)
  let table = context_func_table_ptr(self.handle)
  validate_gc_setup(self.handle, func_type_indices.length(), table, count)
  {
    context: self.handle,
    types: gc_type_data(types),
    canonical: FixedArray::makei(canonical_indices.length(), i => {
      canonical_indices[i]
    }),
    functions: FixedArray::makei(func_type_indices.length(), i => {
      func_type_indices[i]
    }),
    addresses: @jit_ffi.snapshot_function_addresses(table, count),
  }
}

///|
/// Restore every binding outside guest execution, even after another binding
/// replaced or cleared it. Native views retain these same immutable payloads.
pub fn NativeJITContext::setup_gc_environment(
  self : NativeJITContext,
  heap : CHeap,
  environment : PreparedGCEnvironment,
) -> Unit raise GCSetupError {
  if c_jit_context_is_valid(self.handle) == 0 {
    raise MissingJITContext
  }
  guard physical_equal(self.handle, environment.context) else {
    raise InvalidMetadata
  }
  gc_set_heap(self.handle, heap)
  self.handle.bind_gc_metadata(
    environment.types,
    environment.canonical,
    environment.functions,
  ) catch {
    _ => raise InvalidMetadata
  }
  self.handle.bind_gc_function_addresses(environment.addresses)
}