///|
/// Function instance - either WASM code or host function
pub(all) enum FuncInst {
  /// WASM function with code
  WasmFunc(@types.FunctionCode)
  /// Host function provided by the embedder
  HostFunc(
    (Caller, Array[@types.Value]) -> Array[@types.Value] raise RuntimeError
  )
  /// Asynchronous host function provided by the embedder.
  AsyncHostFunc(AsyncHostFunction)
}

///|
/// Keep `Debug` support for `Store` while `FuncInst` contains non-debuggable
/// host function closures.
pub impl Debug for FuncInst with fn to_repr(self) {
  match self {
    WasmFunc(code) => Repr::ctor("WasmFunc", [(None, Repr(code))])
    HostFunc(_) => Repr::ctor("HostFunc", [(None, Repr::literal(""))])
    AsyncHostFunc(_) =>
      Repr::ctor("AsyncHostFunc", [(None, Repr::literal(""))])
  }
}

///|
pub impl Show for FuncInst with fn output(self, logger) {
  match self {
    WasmFunc(code) => logger.write_string("WasmFunc(\{code})")
    HostFunc(_) => logger.write_string("HostFunc()")
    AsyncHostFunc(_) => logger.write_string("AsyncHostFunc()")
  }
}

///|
/// External value for imports
pub(all) enum ExternVal {
  Func(Int32) // function address in store
  Table(Int32) // table address in store
  Memory(Int32) // memory address in store
  Global(Int32) // global address in store
  Tag(Int32) // tag address in store (type index)
}

///|
/// Import object: maps (module_name, name) to external values
struct Imports {
  owner : Store
  entries : Array[(String, String, ExternVal)]
}

///|
pub fn Imports::Imports(store : Store) -> Imports {
  { entries: [], owner: store, }
}

///|
pub fn Imports::add_func(
  self : Imports,
  mod_name : String,
  name : String,
  func_addr : Int32,
) -> Unit {
  self.entries.push((mod_name, name, Func(func_addr)))
}

///|
pub fn Imports::add_memory(
  self : Imports,
  mod_name : String,
  name : String,
  mem_addr : Int32,
) -> Unit {
  self.entries.push((mod_name, name, Memory(mem_addr)))
}

///|
pub fn Imports::add_table(
  self : Imports,
  mod_name : String,
  name : String,
  table_addr : Int32,
) -> Unit {
  self.entries.push((mod_name, name, Table(table_addr)))
}

///|
pub fn Imports::add_global(
  self : Imports,
  mod_name : String,
  name : String,
  global_addr : Int32,
) -> Unit {
  self.entries.push((mod_name, name, Global(global_addr)))
}

///|
pub fn Imports::add_tag(
  self : Imports,
  mod_name : String,
  name : String,
  tag_addr : Int32,
) -> Unit {
  self.entries.push((mod_name, name, Tag(tag_addr)))
}

///|
pub fn Imports::resolve(
  self : Imports,
  mod_name : String,
  name : String,
) -> ExternVal? {
  for entry in self.entries {
    let (m, n, v) = entry
    if m == mod_name && n == name {
      return Some(v)
    }
  }
  None
}

///|
pub fn Imports::check_store(
  self : Imports,
  store : Store,
) -> Unit raise RuntimeError {
  store.ensure_open()
  guard physical_equal(self.owner, store) else { raise ForeignStore }
}