///|
pub struct HotModuleContext {
  subtypes : Array[SubType]
  type_groups : Array[Array[SubType]]
  func_type_idxs : Array[TypeIdx]
  func_types : Array[FuncType]
  func_type_slots : Array[TypeIdx?]
  func_imports : Array[(@lib.Name, @lib.Name)?]
  func_annotations : Array[Array[@lib.FuncAnnotation]]
  tables : Array[TableType]
  mems : Array[@lib.MemType]
  globals : Array[GlobalType]
  global_inits : Array[@lib.Expr?]
  global_const_inits : Array[HotConstPayload?]
  elems : Array[Elem]
  datas : Array[Data]
  tags : Array[TagType]
  imported_tag_count : Int32
  uses_custom_descriptors : Bool
} derive(Eq, Debug)

///|
/// Build a reusable body-validation environment from one immutable module
/// context. Function-local labels, locals and return state are intentionally
/// absent; the body validator adds them independently for each function.
pub fn hot_module_context_validation_env(
  ctx : HotModuleContext,
) -> @validate.Env {
  @validate.Env::new()
  .with_exact_reference_inference(ctx.uses_custom_descriptors)
  .with_types(ctx.subtypes)
  .with_funcs_and_type_idxs(ctx.func_types, ctx.func_type_slots)
  .with_tables(ctx.tables)
  .with_mems(ctx.mems)
  .with_globals(ctx.globals)
  .with_elems(ctx.elems)
  .with_datas(ctx.datas)
  .with_tags(ctx.tags)
}

///|
fn hot_module_context_flatten_subtypes(
  type_sec : @lib.TypeSec?,
) -> Array[SubType] {
  let out : Array[SubType] = []
  match type_sec {
    Some(@lib.TypeSec(rec_types)) =>
      for rec_type in rec_types {
        match rec_type {
          @lib.SingleRecType(subtype) => out.push(subtype)
          @lib.GroupRecType(group) =>
            for subtype in group {
              out.push(subtype)
            }
        }
      }
    None => ()
  }
  out
}

///|
fn hot_module_context_resolve_func_types(
  subtypes : Array[SubType],
  func_type_idxs : Array[TypeIdx],
) -> Array[FuncType] {
  let out : Array[FuncType] = []
  for type_idx in func_type_idxs {
    match type_idx {
      @lib.TypeIdx(raw) =>
        match subtypes.get(raw.reinterpret_as_int()) {
          Some(subtype) =>
            match subtype.get_comptype() {
              @lib.FuncCompType(params, results) =>
                out.push(@lib.FuncType::new(params, results))
              _ =>
                abort(
                  "hot module context function indices must resolve to function types",
                )
            }
          None =>
            abort(
              "hot module context function indices must resolve within the subtype table",
            )
        }
      @lib.RecIdx(_) =>
        abort(
          "hot module context function indices must not use rec-group indices",
        )
    }
  }
  out
}

///|
fn hot_module_context_func_type_slots(
  func_type_idxs : Array[TypeIdx],
) -> Array[TypeIdx?] {
  let out : Array[TypeIdx?] = []
  for type_idx in func_type_idxs {
    out.push(Some(type_idx))
  }
  out
}

///|
fn hot_module_context_func_annotations(
  sec : @lib.FuncAnnotationSec?,
  total_funcs : Int32,
) -> Array[Array[@lib.FuncAnnotation]] {
  let by_func : Array[Array[@lib.FuncAnnotation]] = []
  let mut idx = 0
  while idx < total_funcs {
    by_func.push([])
    idx += 1
  }
  match sec {
    Some(@lib.FuncAnnotationSec(entries)) =>
      for entry in entries {
        let @lib.FuncAnnotationAssoc(raw_idx, annotations) = entry
        let slot = raw_idx.reinterpret_as_int()
        if slot >= 0 && slot < by_func.length() {
          by_func[slot] = annotations
        }
      }
    None => ()
  }
  by_func
}

///|
fn hot_module_context_eval_const_instr(
  instr : @lib.Instruction,
  global_const_inits : Array[HotConstPayload?],
) -> HotConstPayload? {
  match instr {
    @lib.I32Const(value) => Some(HotConstPayload::i32_const(value))
    @lib.I64Const(value) => Some(HotConstPayload::i64_const(value))
    @lib.F32Const(value) => Some(HotConstPayload::f32_const(value))
    @lib.F64Const(value) => Some(HotConstPayload::f64_const(value))
    @lib.RefNull(ref_type) => Some(HotConstPayload::ref_null_const(ref_type))
    @lib.StringConst(bytes) => Some(HotConstPayload::string_const(bytes))
    @lib.GlobalGet(global_idx) =>
      match global_idx {
        @lib.GlobalIdx(raw) =>
          match global_const_inits.get(raw.reinterpret_as_int()) {
            Some(payload) => payload
            None => None
          }
      }
    _ => None
  }
}

///|
fn hot_module_context_eval_global_const_init(
  global_type : GlobalType,
  init : @lib.Expr,
  global_const_inits : Array[HotConstPayload?],
) -> HotConstPayload? {
  let @lib.GlobalType(_, mutable) = global_type
  if mutable {
    return None
  }
  match init {
    @lib.Expr([instr]) =>
      hot_module_context_eval_const_instr(instr, global_const_inits)
    _ => None
  }
}

///|
pub fn hot_module_context_from_module(mod_ : Module) -> HotModuleContext {
  let func_type_idxs : Array[TypeIdx] = []
  let func_imports : Array[(@lib.Name, @lib.Name)?] = []
  let tables : Array[TableType] = []
  let mems : Array[@lib.MemType] = []
  let globals : Array[GlobalType] = []
  let global_inits : Array[@lib.Expr?] = []
  let global_const_inits : Array[HotConstPayload?] = []
  let elems : Array[Elem] = []
  let datas : Array[Data] = []
  let tags : Array[TagType] = []

  match mod_.import_sec {
    Some(@lib.ImportSec(imports)) =>
      for import_ in imports {
        match import_ {
          @lib.Import(module_name, base_name, @lib.FuncExternType(type_idx)) => {
            func_type_idxs.push(type_idx)
            func_imports.push(Some((module_name, base_name)))
          }
          @lib.Import(_, _, @lib.TableExternType(table_type)) =>
            tables.push(table_type)
          @lib.Import(_, _, @lib.MemExternType(mem_type)) => mems.push(mem_type)
          @lib.Import(_, _, @lib.GlobalExternType(global_type)) => {
            globals.push(global_type)
            global_inits.push(None)
            global_const_inits.push(None)
          }
          @lib.Import(_, _, @lib.TagExternType(tag_type)) => tags.push(tag_type)
        }
      }
    None => ()
  }

  let imported_tag_count = tags.length()

  match mod_.func_sec {
    Some(@lib.FuncSec(type_idxs)) =>
      for type_idx in type_idxs {
        func_type_idxs.push(type_idx)
        func_imports.push(None)
      }
    None => ()
  }
  match mod_.table_sec {
    Some(@lib.TableSec(defs)) =>
      for table in defs {
        let @lib.Table(table_type, _) = table
        tables.push(table_type)
      }
    None => ()
  }
  match mod_.mem_sec {
    Some(@lib.MemSec(defs)) =>
      for mem_type in defs {
        mems.push(mem_type)
      }
    None => ()
  }
  match mod_.global_sec {
    Some(@lib.GlobalSec(defs)) =>
      for global in defs {
        let @lib.Global(global_type, init) = global
        globals.push(global_type)
        global_inits.push(Some(init))
        global_const_inits.push(
          hot_module_context_eval_global_const_init(
            global_type, init, global_const_inits,
          ),
        )
      }
    None => ()
  }
  match mod_.tag_sec {
    Some(@lib.TagSec(defs)) =>
      for tag_type in defs {
        tags.push(tag_type)
      }
    None => ()
  }
  match mod_.elem_sec {
    Some(@lib.ElemSec(defs)) =>
      for elem in defs {
        elems.push(elem)
      }
    None => ()
  }
  match mod_.data_sec {
    Some(@lib.DataSec(defs)) =>
      for data in defs {
        datas.push(data)
      }
    None => ()
  }

  let subtypes = hot_module_context_flatten_subtypes(mod_.type_sec)
  let type_groups : Array[Array[SubType]] = []
  match mod_.type_sec {
    Some(@lib.TypeSec(groups)) =>
      for group in groups {
        match group {
          @lib.SingleRecType(subtype) => type_groups.push([subtype])
          @lib.GroupRecType(subtypes) => type_groups.push(subtypes)
        }
      }
    None => ()
  }

  {
    subtypes,
    type_groups,
    func_type_idxs,
    func_types: hot_module_context_resolve_func_types(subtypes, func_type_idxs),
    func_type_slots: hot_module_context_func_type_slots(func_type_idxs),
    func_imports,
    func_annotations: hot_module_context_func_annotations(
      mod_.func_annotation_sec,
      func_type_idxs.length(),
    ),
    tables,
    mems,
    globals,
    global_inits,
    global_const_inits,
    elems,
    datas,
    tags,
    imported_tag_count,
    uses_custom_descriptors: @lib.module_uses_custom_descriptors(mod_),
  }
}

///|
pub fn hot_module_resolve_subtype(
  ctx : HotModuleContext,
  idx : TypeIdx,
) -> SubType? {
  match idx {
    @lib.TypeIdx(raw) => ctx.subtypes.get(raw.reinterpret_as_int())
    @lib.RecIdx(_) => None
  }
}

///|
pub fn hot_module_subtype_count(ctx : HotModuleContext) -> Int32 {
  ctx.subtypes.length()
}

///|
pub fn hot_module_resolve_type_func_type(
  ctx : HotModuleContext,
  idx : TypeIdx,
) -> FuncType? {
  match hot_module_resolve_subtype(ctx, idx) {
    Some(subtype) =>
      match subtype.get_comptype() {
        @lib.FuncCompType(params, results) =>
          Some(@lib.FuncType::new(params, results))
        _ => None
      }
    None => None
  }
}

///|
pub fn hot_module_resolve_func_type_idx(
  ctx : HotModuleContext,
  func_idx : @lib.FuncIdx,
) -> TypeIdx? {
  let @lib.FuncIdx(raw) = func_idx
  ctx.func_type_idxs.get(raw.reinterpret_as_int())
}

///|
pub fn hot_module_resolve_func_type(
  ctx : HotModuleContext,
  func_idx : @lib.FuncIdx,
) -> FuncType? {
  let @lib.FuncIdx(raw) = func_idx
  ctx.func_types.get(raw.reinterpret_as_int())
}

///|
pub fn hot_module_resolve_func_import(
  ctx : HotModuleContext,
  func_idx : @lib.FuncIdx,
) -> (@lib.Name, @lib.Name)? {
  let @lib.FuncIdx(raw) = func_idx
  match ctx.func_imports.get(raw.reinterpret_as_int()) {
    Some(import_) => import_
    None => None
  }
}

///|
pub fn hot_module_func_import_matches(
  ctx : HotModuleContext,
  func_idx : @lib.FuncIdx,
  module_name : String,
  base_name : String,
) -> Bool {
  match hot_module_resolve_func_import(ctx, func_idx) {
    Some((actual_module, actual_base)) =>
      actual_module == @lib.Name::from_string(module_name) &&
      actual_base == @lib.Name::from_string(base_name)
    None => false
  }
}

///|
pub fn hot_module_func_has_annotation(
  ctx : HotModuleContext,
  func_idx : @lib.FuncIdx,
  name : String,
) -> Bool {
  let @lib.FuncIdx(raw) = func_idx
  match ctx.func_annotations.get(raw.reinterpret_as_int()) {
    Some(annotations) =>
      for annotation in annotations {
        if annotation.name == name && annotation.args.length() == 0 {
          return true
        }
      }
    None => ()
  }
  false
}

///|
pub fn hot_module_resolve_block_results(
  ctx : HotModuleContext,
  block_type : BlockType,
) -> Array[ValType]? {
  match block_type {
    @lib.VoidBlockType => Some([])
    @lib.ValTypeBlockType(val_type) => Some([val_type])
    @lib.TypeIdxBlockType(type_idx) =>
      match hot_module_resolve_type_func_type(ctx, type_idx) {
        Some(@lib.FuncType(_, results)) => Some(results)
        None => None
      }
  }
}

///|
pub fn hot_module_resolve_table_type(
  ctx : HotModuleContext,
  table_idx : @lib.TableIdx,
) -> TableType? {
  let @lib.TableIdx(raw) = table_idx
  ctx.tables.get(raw.reinterpret_as_int())
}

///|
pub fn hot_module_resolve_mem_type(
  ctx : HotModuleContext,
  mem_idx : @lib.MemIdx,
) -> @lib.MemType? {
  let @lib.MemIdx(raw) = mem_idx
  ctx.mems.get(raw.reinterpret_as_int())
}

///|
pub fn hot_module_resolve_global_type(
  ctx : HotModuleContext,
  global_idx : @lib.GlobalIdx,
) -> GlobalType? {
  let @lib.GlobalIdx(raw) = global_idx
  ctx.globals.get(raw.reinterpret_as_int())
}

///|
pub fn hot_module_resolve_global_init(
  ctx : HotModuleContext,
  global_idx : @lib.GlobalIdx,
) -> @lib.Expr? {
  let @lib.GlobalIdx(raw) = global_idx
  match ctx.global_inits.get(raw.reinterpret_as_int()) {
    Some(init) => init
    None => None
  }
}

///|
pub fn hot_module_resolve_global_const_init(
  ctx : HotModuleContext,
  global_idx : @lib.GlobalIdx,
) -> HotConstPayload? {
  let @lib.GlobalIdx(raw) = global_idx
  match ctx.global_const_inits.get(raw.reinterpret_as_int()) {
    Some(payload) => payload
    None => None
  }
}

///|
pub fn hot_module_resolve_tag_type(
  ctx : HotModuleContext,
  tag_idx : @lib.TagIdx,
) -> TagType? {
  let @lib.TagIdx(raw) = tag_idx
  ctx.tags.get(raw.reinterpret_as_int())
}

///|
pub fn hot_module_resolve_mem_index_type(
  ctx : HotModuleContext,
  mem_idx : @lib.MemIdx,
) -> ValType? {
  match hot_module_resolve_mem_type(ctx, mem_idx) {
    Some(@lib.MemType(limits, _)) => Some(limits.addr_valtype())
    None => None
  }
}

///|
pub fn hot_module_resolve_table_index_type(
  ctx : HotModuleContext,
  table_idx : @lib.TableIdx,
) -> ValType? {
  match hot_module_resolve_table_type(ctx, table_idx) {
    Some(@lib.TableType(_, limits)) => Some(limits.addr_valtype())
    None => None
  }
}

///|
pub fn hot_module_resolve_struct_fields(
  ctx : HotModuleContext,
  type_idx : TypeIdx,
) -> Array[FieldType]? {
  match hot_module_resolve_subtype(ctx, type_idx) {
    Some(subtype) =>
      match subtype.get_comptype() {
        @lib.StructCompType(fields) => Some(fields)
        _ => None
      }
    None => None
  }
}

///|
pub fn hot_module_resolve_array_field(
  ctx : HotModuleContext,
  type_idx : TypeIdx,
) -> FieldType? {
  match hot_module_resolve_subtype(ctx, type_idx) {
    Some(subtype) =>
      match subtype.get_comptype() {
        @lib.ArrayCompType(field) => Some(field)
        _ => None
      }
    None => None
  }
}

///|
pub fn hot_type_resolve_results(
  func : HotFunc,
  ctx : HotModuleContext,
  type_id : TypeId,
) -> Array[ValType]? {
  match hot_type_get(func, type_id) {
    VoidType => Some([])
    BlockResult(block_type) => hot_module_resolve_block_results(ctx, block_type)
    BlockResultResolved(_, _, values) => Some(values.copy())
    Value(val_type) => Some([val_type])
    Results(values) => Some(values)
  }
}