///|
pub struct Regular {
  ptr_field_offset : UInt
  n_ptr_fields : UInt
  tag : UInt
} derive(Show, ToJson)

///|
pub struct RefArray {
  object_size_shift : UInt
  len : UInt
} derive(Show, ToJson)

///|
pub struct ValArray {
  len : UInt
} derive(Show, ToJson)

///|
pub struct External {
  size : UInt
} derive(Show, ToJson)

///|
#external
type Finalizer

///|
pub fn Finalizer::to_funcref(
  self : Finalizer,
) -> FuncRef[(@c.Pointer[Unit]) -> Unit] = "%identity"

///|
pub(all) enum Meta {
  Regular(Regular)
  RefArray(RefArray)
  ValArray(ValArray)
  External(External)
} derive(Show, ToJson)

///|
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()
  @c.unsafe_return(drop_ptr.load())
}

///|
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.unsafe_borrow(finalizer))
}

///|
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 unused
      // 28 bits
      let len = meta & 0x0FFFFFFF
      ValArray({ len, })
    }
    3 => {
      // External
      // 30 bits
      let size = meta & 0x3FFFFFFF
      External({ size, })
    }
    _ => abort("invalid kind: \{kind}")
  }
}

///|
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)
  }
}

///|
struct Object(@c.Pointer[Int])

///|
pub fn Object::to_pointer(self : Object) -> @c.Pointer[Unit] {
  self.0.cast()
}

///|
pub fn Object::rc(self : Object) -> Int {
  self.0[0]
}

///|
pub fn Object::set_rc(self : Object, rc : Int) -> Unit {
  self.0[0] = rc
}

///|
pub fn Object::meta(self : Object) -> Meta {
  Meta::of_uint(self.0[1].reinterpret_as_uint())
}

///|
pub fn Object::set_meta(self : Object, meta : Meta) -> Unit {
  self.0[1] = meta.to_uint().reinterpret_as_int()
}

///|
pub fn object_header(object : @c.Pointer[Unit]) -> Object {
  Object(object.cast().sub(2))
}

///|
pub extern "c" fn make_external_object(
  drop : FuncRef[(@c.Pointer[Unit]) -> Unit],
  size : UInt64,
) -> @c.Pointer[Unit] = "tonyfettes_moonbit_make_external_object"

///|
pub extern "c" fn incref(object : @c.Pointer[Unit]) -> Unit = "tonyfettes_moonbit_incref"

///|
pub extern "c" fn decref(object : @c.Pointer[Unit]) -> Unit = "tonyfettes_moonbit_decref"