///|
/// Runtime instance of a WebAssembly module
pub struct ModuleInstance {
  types : ReadOnlyArray[@types.SubType] // GC: now stores SubType
  type_rec_groups : ReadOnlyArray[Int32] // rec group IDs for each type
  canonical_type_indices : ReadOnlyArray[Int32] // canonical indices for structural equivalence
  priv mut store_idx : Int32 // index in the store's instances array, -1 if not registered
  mut func_addrs : ReadOnlyArray[Int32] // addresses into function store
  func_type_indices : ReadOnlyArray[Int32] // type indices for each function
  mut table_addrs : ReadOnlyArray[Int32] // indices into table store
  mut mem_addrs : ReadOnlyArray[Int32] // indices into memory store
  mut global_addrs : ReadOnlyArray[Int32] // indices into global store
  tag_addrs : ReadOnlyArray[Int32] // indices into tag store
  mut exports : ReadOnlyArray[@types.Export]
  priv elem_values : Array[Array[@types.Value]]
  elem_segments : ReadOnlyArray[@types.Element]
  data_segments : ReadOnlyArray[@types.Data]
  priv dropped_elems : FixedArray[Bool] // true if elem segment has been dropped
  priv dropped_datas : FixedArray[Bool] // true if data segment has been dropped
  priv mut owner : Ref[Bool]?
} derive(Debug)

///|
/// Keep `Show` behavior while migrating from deprecated `derive(Show)` to
/// `derive(Debug)`.
pub impl Show for ModuleInstance with fn output(self, logger) {
  logger.write_string(@types.compact_show_repr(Repr(self).to_string()))
}

///|
pub fn ModuleInstance::ModuleInstance() -> ModuleInstance {
  {
    owner: None,
    types: [],
    type_rec_groups: [],
    canonical_type_indices: [],
    store_idx: -1,
    func_addrs: [],
    func_type_indices: [],
    table_addrs: [],
    mem_addrs: [],
    global_addrs: [],
    tag_addrs: [],
    exports: [],
    elem_values: [],
    elem_segments: [],
    data_segments: [],
    dropped_elems: [],
    dropped_datas: [],
  }
}

// Type-kind lookups on an instance.
//
// An instance exists only after its module passed validation, so a type
// index used as a function type names one. That is a claim about wasmoon,
// not about the input, and the names now say so: `validated_` marks every
// caller that leans on validation having run (ISS-396).
//
// The bounds check is not redundant with that claim. Reaching it means an
// index arrived from somewhere validation does not cover, and saying so
// beats dying on an array access with nothing on either stream.

///|
/// The function type at `idx`, in an instance whose module passed validation.
pub fn ModuleInstance::validated_func_type_at(
  self : ModuleInstance,
  idx : Int32,
) -> @types.FuncType {
  guard idx >= 0 && idx < self.types.length() else {
    abort("validated instance has no type at index \{idx}")
  }
  match self.types[idx].composite {
    Func(ft) => ft
    _ => abort("validated instance has no function type at index \{idx}")
  }
}

///|
/// The struct type at `idx`, in an instance whose module passed validation.
pub fn ModuleInstance::validated_struct_type_at(
  self : ModuleInstance,
  idx : Int32,
) -> @types.StructType {
  guard idx >= 0 && idx < self.types.length() else {
    abort("validated instance has no type at index \{idx}")
  }
  match self.types[idx].composite {
    Struct(st) => st
    _ => abort("validated instance has no struct type at index \{idx}")
  }
}

///|
/// The array type at `idx`, in an instance whose module passed validation.
pub fn ModuleInstance::validated_array_type_at(
  self : ModuleInstance,
  idx : Int32,
) -> @types.ArrayType {
  guard idx >= 0 && idx < self.types.length() else {
    abort("validated instance has no type at index \{idx}")
  }
  match self.types[idx].composite {
    Array(at) => at
    _ => abort("validated instance has no array type at index \{idx}")
  }
}

///|
/// Retire evaluated element values as well as their guest-visible availability.
pub fn ModuleInstance::drop_element(
  self : ModuleInstance,
  index : Int32,
) -> Unit raise RuntimeError {
  self.ensure_live()
  guard index >= 0 && index < self.dropped_elems.length() else {
    raise UndefinedElement
  }
  self.dropped_elems[index] = true
  self.elem_values[index].clear()
  self.elem_values[index].shrink_to_fit()
}

///|
pub fn ModuleInstance::store_index(self : ModuleInstance) -> Int32 {
  self.store_idx
}

///|
pub fn Store::check_instance(
  self : Store,
  instance : ModuleInstance,
) -> Unit raise RuntimeError {
  self.ensure_open()
  guard instance.owner is Some(owner) else { raise InvalidHandle }
  guard physical_equal(owner, self.closed) else { raise ForeignStore }
  guard instance.store_idx >= 0 &&
    instance.store_idx < self.instances.length() &&
    physical_equal(self.instances[instance.store_idx], instance) else {
    raise InvalidHandle
  }
}

///|
pub fn ModuleInstance::element_dropped(
  self : ModuleInstance,
  index : Int32,
) -> Bool {
  self.store_idx < 0 ||
  self.owner is Some({ val: true, }) ||
  index < 0 ||
  index >= self.dropped_elems.length() ||
  self.dropped_elems[index]
}

///|
pub fn ModuleInstance::data_dropped(
  self : ModuleInstance,
  index : Int32,
) -> Bool {
  self.store_idx < 0 ||
  self.owner is Some({ val: true, }) ||
  index < 0 ||
  index >= self.dropped_datas.length() ||
  self.dropped_datas[index]
}

///|
pub fn ModuleInstance::drop_data(
  self : ModuleInstance,
  index : Int32,
) -> Unit raise RuntimeError {
  self.ensure_live()
  guard index >= 0 && index < self.dropped_datas.length() else {
    raise UndefinedElement
  }
  self.dropped_datas[index] = true
}

///|
pub fn ModuleInstance::element_values(
  self : ModuleInstance,
  index : Int32,
) -> ReadOnlyArray[@types.Value] raise RuntimeError {
  self.ensure_live()
  guard index >= 0 && index < self.elem_values.length() else {
    raise UndefinedElement
  }
  ReadOnlyArray::from_array(self.elem_values[index])
}

///|
/// Native integration: bind the instance's drop state without exposing mutable arrays.
pub fn[T : SegmentDropBinding] ModuleInstance::bind_native_segments(
  self : ModuleInstance,
  context : T,
) -> Unit {
  context.bind_segment_drop_flags(self.dropped_datas, self.dropped_elems)
}

///|
/// Publish a validated module's address maps into this Store.
/// Addresses are Store-local low-level integration indices, checked before publication.
pub fn Store::new_instance(
  self : Store,
  validated : @validator.ValidatedModule,
  func_addrs~ : ArrayView[Int32],
  table_addrs~ : ArrayView[Int32],
  mem_addrs~ : ArrayView[Int32],
  global_addrs~ : ArrayView[Int32],
  tag_addrs~ : ArrayView[Int32],
  elem_values~ : Array[Array[@types.Value]],
  admission? : InstanceAdmission,
) -> ModuleInstance raise RuntimeError {
  self.ensure_open()
  let admission = admission.unwrap_or_else(() => self.reserve_instance())
  self.check_instance_admission(admission, validated~)
  let module_ = validated.snapshot()
  let expected_functions : Array[Int32] = []
  let mut functions = module_.codes.length()
  let mut tables = module_.tables.length()
  let mut memories = module_.memories.length()
  let mut globals = module_.globals.length()
  let mut tags = module_.tags.length()
  for import_ in module_.imports {
    match import_.desc {
      Func(index) => {
        functions += 1
        expected_functions.push(index)
      }
      Table(_) => tables += 1
      Memory(_) => memories += 1
      Global(_) => globals += 1
      Tag(_) => tags += 1
    }
  }
  for index in module_.funcs {
    expected_functions.push(index)
  }
  let func_type_indices = expected_functions
  guard func_addrs.length() == functions &&
    table_addrs.length() == tables &&
    mem_addrs.length() == memories &&
    global_addrs.length() == globals &&
    tag_addrs.length() == tags &&
    elem_values.length() == module_.elems.length() else {
    raise InvalidHandle
  }
  for address in func_addrs {
    self.get_func_inst(address) |> ignore
  }
  for address in table_addrs {
    self.get_table(address) |> ignore
  }
  for address in mem_addrs {
    self.get_mem(address) |> ignore
  }
  for address in global_addrs {
    self.get_global(address) |> ignore
  }
  for address in tag_addrs {
    self.get_tag_type(address) |> ignore
  }
  for index, type_index in func_type_indices {
    let address = func_addrs[index]
    let expected = module_.validated_func_type_at(type_index)
    let matches = match self.get_func_owner(address) {
      Some(owner) =>
        @types.SubtypingContext::cross_module(
          owner.types,
          module_.types,
          rec_groups1=owner.type_rec_groups,
          rec_groups2=module_.type_rec_groups,
        ).is_subtype(self.get_func_type_idx(address), type_index)
      None => self.get_func_type(address) == expected
    }
    guard matches else { raise TypeMismatch }
  }
  let imported_functions = functions - module_.codes.length()
  for index, code in module_.codes {
    let address = func_addrs[imported_functions + index]
    guard self.get_func_owner(address) is None &&
      physical_equal(self.get_func(address), code) else {
      raise InvalidHandle
    }
  }
  let expected_memories : Array[@types.MemoryType] = []
  let expected_tables : Array[@types.TableType] = []
  let expected_globals : Array[@types.GlobalType] = []
  let expected_tags : Array[Int32] = []
  for import_ in module_.imports {
    match import_.desc {
      Memory(type_) => expected_memories.push(type_)
      Table(type_) => expected_tables.push(type_)
      Global(type_) => expected_globals.push(type_)
      Tag(type_) => expected_tags.push(type_)
      Func(_) => ()
    }
  }
  for type_ in module_.memories {
    expected_memories.push(type_)
  }
  for table in module_.tables {
    expected_tables.push(table.type_)
  }
  for global in module_.globals {
    expected_globals.push(global.type_)
  }
  for tag in module_.tags {
    expected_tags.push(tag.type_idx)
  }
  for index, expected in expected_memories {
    let memory = self.get_mem(mem_addrs[index])
    let (minimum, maximum) = memory.get_limits64()
    guard memory.is_memory64() == expected.is_memory64 &&
      memory.is_shared() == expected.is_shared &&
      memory.page_size_bytes() == 1L << expected.page_size_log2 &&
      minimum.reinterpret_as_uint64() >=
      expected.limits.min.reinterpret_as_uint64() else {
      raise TypeMismatch
    }
    if expected.limits.max is Some(limit) {
      guard maximum is Some(actual) &&
        actual.reinterpret_as_uint64() <= limit.reinterpret_as_uint64() else {
        raise TypeMismatch
      }
    }
  }
  for index, expected in expected_tables {
    let table = self.get_table(table_addrs[index])
    let (minimum, maximum) = table.get_limits64()
    guard table.is_table64() == expected.is_table64 &&
      minimum.reinterpret_as_uint64() >=
      expected.limits.min.reinterpret_as_uint64() else {
      raise TypeMismatch
    }
    let context = self.resource_type_context(Table(table_addrs[index]), module_)
    guard context.value_type_subtype(table.get_elem_type(), expected.elem_type) &&
      reverse_type_context(context).value_type_subtype(
        expected.elem_type,
        table.get_elem_type(),
      ) else {
      raise TypeMismatch
    }
    if expected.limits.max is Some(limit) {
      guard maximum is Some(actual) &&
        actual.reinterpret_as_uint64() <= limit.reinterpret_as_uint64() else {
        raise TypeMismatch
      }
    }
  }
  for index, expected in expected_globals {
    let actual = self.get_global(global_addrs[index]).get_type()
    let context = self.resource_type_context(
      Global(global_addrs[index]),
      module_,
    )
    guard actual.mutable == expected.mutable &&
      context.value_type_subtype(actual.value_type, expected.value_type) else {
      raise TypeMismatch
    }
    if expected.mutable &&
      !reverse_type_context(context).value_type_subtype(
        expected.value_type,
        actual.value_type,
      ) {
      raise TypeMismatch
    }
  }
  for index, expected in expected_tags {
    let actual = self.get_tag_type(tag_addrs[index])
    let expected_signature = module_.validated_func_type_at(expected)
    guard actual.params.length() == expected_signature.params.length() else {
      raise TypeMismatch
    }
    let context = self.resource_type_context(Tag(tag_addrs[index]), module_)
    for parameter, actual in actual.params {
      let expected = expected_signature.params[parameter]
      guard context.value_type_subtype(actual, expected) &&
        reverse_type_context(context).value_type_subtype(expected, actual) else {
        raise TypeMismatch
      }
    }
  }
  let instance = {
    types: ReadOnlyArray::from_array(module_.types),
    type_rec_groups: ReadOnlyArray::from_array(module_.type_rec_groups),
    canonical_type_indices: ReadOnlyArray::from_array(
      @types.compute_canonical_type_indices(
        module_.types,
        type_rec_groups=module_.type_rec_groups,
      ),
    ),
    store_idx: -1,
    owner: None,
    func_addrs: ReadOnlyArray::from_array(func_addrs),
    func_type_indices: ReadOnlyArray::from_array(func_type_indices),
    table_addrs: ReadOnlyArray::from_array(table_addrs),
    mem_addrs: ReadOnlyArray::from_array(mem_addrs),
    global_addrs: ReadOnlyArray::from_array(global_addrs),
    tag_addrs: ReadOnlyArray::from_array(tag_addrs),
    exports: ReadOnlyArray::from_array(module_.exports),
    elem_values: elem_values.map(fn(values) { values.copy() }),
    elem_segments: ReadOnlyArray::from_array(module_.elems),
    data_segments: ReadOnlyArray::from_array(module_.datas),
    dropped_elems: FixedArray::make(module_.elems.length(), false),
    dropped_datas: FixedArray::make(module_.datas.length(), false),
  }
  self.register_instance(instance, admission~) |> ignore
  for index, element in module_.elems {
    if element.mode is Declarative {
      instance.drop_element(index)
    }
  }
  instance
}

///|
/// Construct a Store-owned call context for an existing host function.
pub fn Store::host_call_instance(
  self : Store,
  address : Int32,
) -> ModuleInstance raise RuntimeError {
  self.ensure_open()
  guard self.get_func_inst(address) is (HostFunc(_) | AsyncHostFunc(_)) else {
    raise TypeMismatch
  }
  let signature = self.get_func_type(address)
  let instance = {
    ..ModuleInstance(),
    types: [@types.SubType::from_func(signature)],
    canonical_type_indices: [0],
    func_addrs: [address],
    func_type_indices: [0],
  }
  self.register_instance(instance) |> ignore
  instance
}

///|
/// Publish a checked namespace of host exports in this Store.
pub fn Store::host_exports(
  self : Store,
  exports : ArrayView[(String, ExternVal)],
) -> ModuleInstance raise RuntimeError {
  self.ensure_open()
  let instance = ModuleInstance()
  let funcs : Array[Int32] = []
  let tables : Array[Int32] = []
  let memories : Array[Int32] = []
  let globals : Array[Int32] = []
  let tags : Array[Int32] = []
  let definitions : Array[@types.Export] = []
  for (name, value) in exports {
    let desc : @types.ExportDesc = match value {
      Func(address) => {
        guard self.get_func_inst(address) is (HostFunc(_) | AsyncHostFunc(_)) else {
          raise TypeMismatch
        }
        funcs.push(address)
        Func(funcs.length() - 1)
      }
      Table(address) => {
        self.get_table(address) |> ignore
        tables.push(address)
        Table(tables.length() - 1)
      }
      Memory(address) => {
        self.get_mem(address) |> ignore
        memories.push(address)
        Memory(memories.length() - 1)
      }
      Global(address) => {
        self.get_global(address) |> ignore
        globals.push(address)
        Global(globals.length() - 1)
      }
      Tag(address) => {
        self.get_tag_type(address) |> ignore
        tags.push(address)
        Tag(tags.length() - 1)
      }
    }
    definitions.push({ name, desc, })
  }
  let instance = {
    ..instance,
    func_addrs: ReadOnlyArray::from_array(funcs),
    table_addrs: ReadOnlyArray::from_array(tables),
    mem_addrs: ReadOnlyArray::from_array(memories),
    global_addrs: ReadOnlyArray::from_array(globals),
    tag_addrs: ReadOnlyArray::from_array(tags),
    exports: ReadOnlyArray::from_array(definitions),
  }
  self.register_instance(instance) |> ignore
  instance
}

///|
fn Store::resource_type_context(
  self : Store,
  value : ExternVal,
  target : @types.Module,
) -> @types.SubtypingContext {
  let owner = match value {
    Tag(address) => self.get_tag_owner(address)
    _ => {
      let mut found : ModuleInstance? = None
      for instance in self.instances {
        if instance.types.is_empty() {
          continue
        }
        let contains = match value {
          Table(address) => instance.table_addrs.contains(address)
          Global(address) => instance.global_addrs.contains(address)
          _ => false
        }
        if contains {
          found = Some(instance)
          break
        }
      }
      found
    }
  }
  match owner {
    Some(owner) =>
      @types.SubtypingContext::cross_module(
        owner.types,
        target.types,
        rec_groups1=owner.type_rec_groups,
        rec_groups2=target.type_rec_groups,
      )
    None =>
      @types.SubtypingContext::same_module(
        target.types,
        rec_groups=target.type_rec_groups,
      )
  }
}

///|
fn reverse_type_context(
  context : @types.SubtypingContext,
) -> @types.SubtypingContext {
  if !context.cross_module {
    return context
  }
  @types.SubtypingContext::cross_module(
    context.types2,
    context.types1,
    rec_groups1=context.rec_groups2,
    rec_groups2=context.rec_groups1,
    canonical1=context.canonical2,
    canonical2=context.canonical1,
  )
}

///|
/// Read one evaluated element without copying a segment during bulk initialization.
pub fn ModuleInstance::element_value(
  self : ModuleInstance,
  segment : Int32,
  index : Int32,
) -> @types.Value raise RuntimeError {
  self.ensure_live()
  guard segment >= 0 && segment < self.elem_values.length() else {
    raise UndefinedElement
  }
  guard self.elem_values[segment].get(index) is Some(value) else {
    raise UndefinedElement
  }
  value
}

///|
fn ModuleInstance::ensure_live(
  self : ModuleInstance,
) -> Unit raise RuntimeError {
  guard self.owner is Some(owner) else { raise InvalidHandle }
  if owner.val {
    raise StoreClosed
  }
  if self.store_idx < 0 {
    raise InvalidHandle
  }
}

///|
/// A call-scoped sink for the instance's authoritative segment drop flags.
pub(open) trait SegmentDropBinding {
  fn bind_segment_drop_flags(Self, FixedArray[Bool], FixedArray[Bool]) -> Unit
}