///|
/// Store-local callable identities, built and owned by MoonBit.
pub struct NativeCallableRegistry {
  priv view : NativeCallableView
  priv mut parents : FixedArray[Int32]
  priv mut addresses : FixedArray[Int64]
  priv mut identities : FixedArray[Int32]
  priv mut ordinals : FixedArray[Int32]
}

///|
#deprecated
pub extend NativeCallableRegistry with Debug::{to_repr}

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

///|
pub fn NativeCallableRegistry::new() -> NativeCallableRegistry {
  {
    view: new_view(),
    parents: [],
    addresses: [],
    identities: [],
    ordinals: [],
  }
}

///|
/// Native consumers retain this RC-managed view; it keeps its arrays alive.
pub fn NativeCallableRegistry::native_view(
  self : NativeCallableRegistry,
) -> NativeCallableView {
  self.view
}

///|
pub fn NativeCallableRegistry::clear(self : NativeCallableRegistry) -> Unit {
  self.parents = []
  self.addresses = []
  self.identities = []
  self.ordinals = []
  publish(self.view, self.parents, 0, self.addresses, self.identities, 0)
  self.replace_functions([], [])
}

///|
/// Replace Store metadata outside concurrent guest execution. Native consumers
/// share the typed arrays; they do not own an independent copy of the metadata.
/// The Bool result is retained for compatibility. Allocation uses MoonBit's
/// allocator, so there is no separately recoverable native allocation failure.
pub fn NativeCallableRegistry::replace(
  self : NativeCallableRegistry,
  parents : Array[Int32],
  entries : Array[(Int64, Int32)],
) -> Bool {
  let mut parents_changed = parents.length() != self.parents.length()
  if !parents_changed {
    for i, parent in parents {
      if parent != self.parents[i] {
        parents_changed = true
        break
      }
    }
  }
  let mut addresses_changed = entries.length() != self.addresses.length()
  if !addresses_changed {
    for i, ordinal in self.ordinals {
      if self.addresses[i] != entries[ordinal].0 {
        addresses_changed = true
        break
      }
    }
  }
  let new_parents = if parents_changed {
    FixedArray::makei(parents.length(), i => parents[i])
  } else {
    self.parents
  }
  if addresses_changed {
    let ordinals = FixedArray::makei(entries.length(), i => i)
    // An input-position tie-break preserves the original first-duplicate rule.
    ordinals.sort_by((a, b) => {
      let order = entries[a].0
        .reinterpret_as_uint64()
        .compare(entries[b].0.reinterpret_as_uint64())
      if order == 0 {
        a.compare(b)
      } else {
        order
      }
    })
    let addresses = FixedArray::makei(entries.length(), i => {
      entries[ordinals[i]].0
    })
    let identities = FixedArray::makei(entries.length(), i => {
      entries[ordinals[i]].1
    })
    self.addresses = addresses
    self.identities = identities
    self.ordinals = ordinals
  } else {
    for i, ordinal in self.ordinals {
      self.identities[i] = entries[ordinal].1
    }
  }
  self.parents = new_parents
  if parents_changed || addresses_changed {
    publish(
      self.view,
      self.parents,
      self.parents.length(),
      self.addresses,
      self.identities,
      self.addresses.length(),
    )
  }
  true
}

///|
/// Function addresses in table storage stay Store-local across JIT registrations.
/// Both sorted views are retained by the native view, without a Store callback.
pub fn NativeCallableRegistry::replace_functions(
  self : NativeCallableRegistry,
  functions : Array[(Int32, Int64)],
  references : Array[(Int64, Int32)],
) -> Unit {
  functions.sort_by((a, b) => a.0.compare(b.0))
  references.sort_by((a, b) => {
    a.0.reinterpret_as_uint64().compare(b.0.reinterpret_as_uint64())
  })
  publish_functions(
    self.view,
    FixedArray::makei(functions.length(), i => functions[i].0),
    FixedArray::makei(functions.length(), i => functions[i].1),
    functions.length(),
    FixedArray::makei(references.length(), i => references[i].0),
    FixedArray::makei(references.length(), i => references[i].1),
    references.length(),
  )
}