///|
priv struct StoreFuncrefMappingOwner {
  registration : Int32
  store_addr : Int32
  func_ptr : Int64
} derive(Debug)

///|
/// One Store-local function address and its native executable pointer.
pub struct StoreFuncrefEntry {
  store_addr : Int32
  func_ptr : Int64
} derive(Debug, Eq)

///|
pub fn StoreFuncrefEntry::StoreFuncrefEntry(
  store_addr~ : Int32,
  func_ptr~ : Int64,
) -> StoreFuncrefEntry {
  { store_addr, func_ptr, }
}

///|
priv struct StoreFuncrefRegistry {
  store_to_ptr : Map[Int32, Array[StoreFuncrefMappingOwner]]
  ptr_to_store : Map[Int64, Array[StoreFuncrefMappingOwner]]
  next_registration : Ref[Int32]
  mut native : @native_bridge.NativeCallableRegistry?
  owners : Array[NativeCallableOwner]
} derive(Debug)

///|
struct StoreFuncrefRegistration {
  registry : StoreFuncrefRegistry
  registration : Int32
  entries : Array[StoreFuncrefEntry]
  active : Ref[Bool]
}

///|
fn StoreFuncrefRegistry::new() -> StoreFuncrefRegistry {
  {
    store_to_ptr: Map([]),
    ptr_to_store: Map([]),
    next_registration: Ref(0),
    native: None,
    owners: [],
  }
}

///|
fn remove_store_funcref_owner(
  owners : Array[StoreFuncrefMappingOwner],
  registration : Int32,
) -> Unit {
  for i in 0.. ignore
      return
    }
  }
}

///|
fn StoreFuncrefRegistry::register(
  self : StoreFuncrefRegistry,
  entries : Array[StoreFuncrefEntry],
) -> StoreFuncrefRegistration {
  let registration = self.next_registration.val
  self.next_registration.val = registration + 1
  for entry in entries {
    let { store_addr, func_ptr, } = entry
    if store_addr < 0 || func_ptr == 0L {
      continue
    }
    let owner = { registration, store_addr, func_ptr, }
    match self.store_to_ptr.get(store_addr) {
      Some(owners) => owners.push(owner)
      None => self.store_to_ptr.set(store_addr, [owner])
    }
    match self.ptr_to_store.get(func_ptr) {
      Some(owners) => owners.push(owner)
      None => self.ptr_to_store.set(func_ptr, [owner])
    }
  }
  self.publish_functions()
  { registry: self, registration, entries, active: Ref(true), }
}

///|
pub fn StoreFuncrefRegistration::release(
  self : StoreFuncrefRegistration,
) -> Unit {
  if !self.active.val {
    return
  }
  self.active.val = false
  for entry in self.entries {
    let { store_addr, func_ptr, } = entry
    if self.registry.store_to_ptr.get(store_addr) is Some(owners) {
      remove_store_funcref_owner(owners, self.registration)
      if owners.is_empty() {
        self.registry.store_to_ptr.remove(store_addr)
      }
    }
    if self.registry.ptr_to_store.get(func_ptr) is Some(owners) {
      remove_store_funcref_owner(owners, self.registration)
      if owners.is_empty() {
        self.registry.ptr_to_store.remove(func_ptr)
      }
    }
  }
  self.registry.publish_functions()
}

///|
fn StoreFuncrefRegistry::lookup_ptr(
  self : StoreFuncrefRegistry,
  store_addr : Int32,
) -> Int64? {
  match self.store_to_ptr.get(store_addr) {
    Some(owners) if !owners.is_empty() =>
      Some(owners[owners.length() - 1].func_ptr)
    _ => None
  }
}

///|
fn StoreFuncrefRegistry::lookup_store_addr(
  self : StoreFuncrefRegistry,
  func_ptr : Int64,
) -> Int32? {
  match self.ptr_to_store.get(func_ptr) {
    Some(owners) if !owners.is_empty() =>
      Some(owners[owners.length() - 1].store_addr)
    _ => None
  }
}

///|
fn StoreFuncrefRegistry::entries(
  self : StoreFuncrefRegistry,
) -> Array[StoreFuncrefEntry] {
  let entries : Array[StoreFuncrefEntry] = []
  for func_ptr, owners in self.ptr_to_store {
    if func_ptr != 0L && !owners.is_empty() {
      entries.push({
        store_addr: owners[owners.length() - 1].store_addr,
        func_ptr,
      })
    }
  }
  entries
}

///|
fn StoreFuncrefRegistry::clear(self : StoreFuncrefRegistry) -> Unit {
  self.store_to_ptr.clear()
  self.ptr_to_store.clear()
  self.publish_functions()
  self.native = None
  let owners = self.owners.copy()
  self.owners.clear()
  self.owners.shrink_to_fit()
  for owner in owners {
    (owner.release)()
  }
}

///|
pub fn Store::register_jit_funcrefs(
  self : Store,
  entries : Array[StoreFuncrefEntry],
  release_owner~ : () -> Unit = fn() {  },
) -> StoreFuncrefRegistration {
  self.funcref_registry.owners.push({ release: release_owner, })
  self.funcref_registry.register(entries)
}

///|
pub fn Store::lookup_jit_funcref_ptr(
  self : Store,
  store_addr : Int32,
) -> Int64? {
  self.funcref_registry.lookup_ptr(store_addr)
}

///|
pub fn Store::lookup_jit_funcref_store_addr(
  self : Store,
  func_ptr : Int64,
) -> Int32? {
  self.funcref_registry.lookup_store_addr(func_ptr)
}

///|
pub fn Store::jit_funcref_entries(self : Store) -> Array[StoreFuncrefEntry] {
  self.funcref_registry.entries()
}

///|
priv struct NativeCallableOwner {
  release : () -> Unit
}

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

///|
fn StoreFuncrefRegistry::publish_functions(self : StoreFuncrefRegistry) -> Unit {
  if self.native is Some(registry) {
    let functions : Array[(Int32, Int64)] = []
    for address, owners in self.store_to_ptr {
      if !owners.is_empty() {
        functions.push((address, owners[owners.length() - 1].func_ptr))
      }
    }
    registry.replace_functions(
      functions,
      self.entries().map(entry => (entry.func_ptr, entry.store_addr)),
    )
  }
}