///|
/// Metadata for regular objects with pointer and non-pointer fields.
///
/// Regular objects have a fixed layout with a specific number of pointer fields
/// and non-pointer fields. The `ptr_field_offset` indicates where pointer
/// fields start, `n_ptr_fields` indicates how many pointer fields there are,
/// and `tag` is an 8-bit tag for enum type.
pub struct Regular {
  ptr_field_offset : UInt
  n_ptr_fields : UInt
  tag : UInt
} derive(Show, ToJson)

///|
/// Metadata for reference arrays (arrays of pointers).
///
/// Reference arrays store pointers to other objects.
pub struct RefArray {
  /// - `1` for array of value type with reference fields
  /// - `2`/`3` for array of references. Whether it's 2 or 3 depends on the
  ///   size of pointer on that platform.
  object_size_shift : UInt
  /// The number of elements in the array.
  len : UInt
} derive(Show, ToJson)

///|
/// Metadata for value arrays (arrays of non-pointer values).
///
/// Value arrays store primitive values or values of value types.
pub struct ValArray {
  /// The size of each element in the array, encoded as a shift value.
  /// The size of each element can be calculated as `1 << object_size_shift`.
  /// Note that this field is not precise when the array elements are value
  /// types
  object_size_shift : UInt
  /// The number of elements in the array.
  len : UInt
} derive(Show, ToJson)

///|
/// Metadata for external objects with custom finalizers.
///
/// External objects are used for managing resources that require custom
/// finalization logic.
pub struct External {
  /// Size of the object's data portion in bytes
  size : UInt
} derive(Show, ToJson)

///|
/// Abstract type representing a finalizer function for an external object.
#external
type Finalizer

///|
/// Convert a Finalizer to a function reference.
///
/// This function converts a Finalizer object into a function reference that can
/// be called to clean up resources associated with an external object.
pub fn Finalizer::to_funcref(
  self : Finalizer,
) -> FuncRef[(@c.Pointer[Unit]) -> Unit] = "%identity"

///|
/// Metadata descriptor for MoonBit objects.
pub(all) enum Meta {
  /// Objects with fixed layouts containing pointer and non-pointer fields
  Regular(Regular)
  /// Arrays of references/pointers
  RefArray(RefArray)
  /// Arrays of primitive values or values of value types
  ValArray(ValArray)
  /// Objects with custom finalizers for resource management
  External(External)
} derive(Show, ToJson)

///|
/// Get the finalizer associated with an external object.
///
/// Retrieves the finalizer function pointer stored at the end of the external
/// object's data. The finalizer is located at offset `size` from the object
/// pointer.
///
/// Parameters
///
/// - `object`: Pointer to the external object
///
/// Returns the finalizer function for this object.
pub fn External::finalizer(
  self : External,
  object : @c.Pointer[Unit],
) -> Finalizer {
  let byte_ptr : @c.Pointer[Byte] = object.cast()
  let drop_ptr : @c.Pointer[@c.Pointer[Unit]] = byte_ptr
    .add(self.size.to_uint64())
    .cast()
  drop_ptr.load().unsafe_into()
}

///|
/// Set the finalizer for an external object.
///
/// Stores a finalizer function pointer at the end of the external object's data
/// (at offset `size` from the object pointer). This finalizer will be called
/// when the object is garbage collected.
///
/// Parameters
///
/// - `object`: Pointer to the external object
/// - `finalizer`: Function to be called when the object is finalized
pub fn External::set_finalizer(
  self : External,
  object : @c.Pointer[Unit],
  finalizer : FuncRef[(@c.Pointer[Unit]) -> Unit],
) -> Unit {
  let byte_ptr : @c.Pointer[Byte] = object.cast()
  let drop_ptr : @c.Pointer[@c.Pointer[Unit]] = byte_ptr
    .add(self.size.to_uint64())
    .cast()
  drop_ptr.store(@c.Pointer::unsafe_from(finalizer))
}

///|
/// Decode metadata from a UInt value.
///
/// Parses a 32-bit unsigned integer to extract object metadata. The top 2 bits
/// determine the object kind (Regular, RefArray, ValArray, or External), and
/// the remaining bits encode kind-specific information.
///
/// Parameters
///
/// - `meta`: The encoded metadata as a UInt
///
/// Returns the decoded Meta enum value.
pub fn Meta::of_uint(meta : UInt) -> Meta {
  let kind = meta >> 30
  match kind {
    0 => {
      // Regular
      // 11 bits
      let ptr_field_offset = (meta >> 19) & 0x7FF
      // 11 bits
      let n_ptr_fields = (meta >> 8) & 0x7FF
      // 8 bits
      let tag = meta & 0xFF
      Regular({ ptr_field_offset, n_ptr_fields, tag })
    }
    1 => {
      // RefArray
      // 2 bits
      let object_size_shift = (meta >> 28) & 0x3
      // 28 bits
      let len = meta & 0x0FFFFFFF
      RefArray({ object_size_shift, len })
    }
    2 => {
      // ValArray
      // 2 bits
      let object_size_shift = (meta >> 28) & 0x3
      // 28 bits
      let len = meta & 0x0FFFFFFF
      ValArray({ object_size_shift, len })
    }
    3 => {
      // External
      // 30 bits
      let size = meta & 0x3FFFFFFF
      External({ size, })
    }
    _ => abort("invalid kind: \{kind}")
  }
}

///|
/// Encode metadata to a UInt value.
///
/// Converts a Meta enum value into a 32-bit unsigned integer encoding. The top
/// 2 bits indicate the object kind, and the remaining bits encode kind-specific
/// information such as field offsets, array lengths, or object sizes.
///
/// Returns the encoded metadata as a UInt.
pub fn Meta::to_uint(self : Meta) -> UInt {
  match self {
    Regular(r) =>
      (0 << 30) |
      ((r.ptr_field_offset & 0x7FF) << 19) |
      ((r.n_ptr_fields & 0x7FF) << 8) |
      (r.tag & 0xFF)
    RefArray(ra) =>
      (1 << 30) | ((ra.object_size_shift & 0x3) << 28) | (ra.len & 0x0FFFFFFF)
    ValArray(va) => (2 << 30) | (va.len & 0x0FFFFFFF)
    External(e) => (3 << 30) | (e.size & 0x3FFFFFFF)
  }
}

///|
/// Wrapper type around `@c.Pointer` for easier access to object header fields.
struct Object(@c.Pointer[Int])

///|
/// Convert an Object to a generic pointer.
///
/// Returns a pointer to the beginning of the object's data (excluding the
/// header).
///
/// Returns a pointer to the object's data.
pub fn Object::to_pointer(self : Object) -> @c.Pointer[Unit] {
  self.0.cast()
}

///|
/// Get the reference count of an object.
///
/// Returns the current reference count stored in the object's header.
///
/// Returns the reference count as an Int.
pub fn Object::rc(self : Object) -> Int {
  self.0[0]
}

///|
/// Set the reference count of an object.
///
/// Updates the reference count stored in the object's header.
///
/// Parameters
/// - `rc`: The new reference count value
pub fn Object::set_rc(self : Object, rc : Int) -> Unit {
  self.0[0] = rc
}

///|
/// Get the metadata of an object.
///
/// Retrieves and decodes the metadata stored in the object's header.
///
/// Returns the decoded Meta enum describing the object's type and layout.
pub fn Object::meta(self : Object) -> Meta {
  Meta::of_uint(self.0[1].reinterpret_as_uint())
}

///|
/// Set the metadata of an object.
///
/// Encodes and stores metadata in the object's header.
///
/// Parameters
///
/// - `meta`: The Meta enum value to encode and store
pub fn Object::set_meta(self : Object, meta : Meta) -> Unit {
  self.0[1] = meta.to_uint().reinterpret_as_int()
}

///|
/// Get the object header from a data pointer.
///
/// Given a pointer to an object's data, returns an Object handle that provides
/// access to the object's header (containing reference count and metadata).
///
/// Parameters
///
/// - `object`: Pointer to the object's data
///
/// Returns an Object handle for accessing the header.
pub fn object_header(object : @c.Pointer[Unit]) -> Object {
  Object(object.cast().sub(2))
}

///|
/// Create a new external object with a custom finalizer.
///
/// Allocates memory for an external object and associates it with a finalizer
/// function that will be called when the object is garbage collected.
///
/// Parameters
///
/// - `drop`: The finalizer function to call when the object is collected
/// - `size`: The size of the object's data in bytes
///
/// Returns a pointer to the newly allocated external object.
pub extern "c" fn make_external_object(
  drop : FuncRef[(@c.Pointer[Unit]) -> Unit],
  size : UInt64,
) -> @c.Pointer[Unit] = "moonbit_tonyfettes_insomnia_make_external_object"

///|
/// Increment the reference count of an object.
///
/// Increases the reference count by one. This should be called when creating
/// a new reference to an existing object.
///
/// Parameters
///
/// - `object`: Pointer to the object whose reference count should be incremented
pub extern "c" fn incref(object : @c.Pointer[Unit]) -> Unit = "moonbit_tonyfettes_insomnia_incref"

///|
/// Decrement the reference count of an object.
///
/// Decreases the reference count by one. When the reference count reaches zero,
/// the object will be deallocated. If the object has a finalizer, it will be
/// called before deallocation.
///
/// Parameters
///
/// - `object`: Pointer to the object whose reference count should be decremented
pub extern "c" fn decref(object : @c.Pointer[Unit]) -> Unit = "moonbit_tonyfettes_insomnia_decref"