///|
fn hot_intern_type(
  func : HotFunc,
  info : HotTypeInfo,
  key_formats~ : Array[Int32]? = None,
) -> TypeId {
  // Common functions have only a few result shapes. Structural comparison
  // avoids allocating a rendered key on repeated tuple/control queries.
  // Keep the existing indexed lookup for larger heterogeneous type tables.
  if func.types.length() <= 16 {
    for id = 0; id < func.types.length(); id = id + 1 {
      if func.types[id] == info {
        return id
      }
    }
  }
  if key_formats is Some(count) {
    count[0] += 1
  }
  let key = info.to_string()
  match func.type_ids.get(key) {
    Some(id) => id
    None => {
      let id = func.types.length()
      // Caller rows can be scratch or mutable module metadata. Own them only
      // on an intern miss; repeated type queries must not copy another row.
      let owned = match info {
        HotTypeInfo::Results(values) => HotTypeInfo::Results(values.copy())
        HotTypeInfo::BlockResultResolved(block_type, params, results) =>
          HotTypeInfo::BlockResultResolved(
            block_type,
            params.copy(),
            results.copy(),
          )
        _ => info
      }
      func.types.push(owned)
      func.type_ids.set(key, id)
      id
    }
  }
}

///|
pub fn hot_type_intern_void(_func : HotFunc) -> TypeId {
  0
}

///|
pub fn hot_type_intern_value(func : HotFunc, val_type : ValType) -> TypeId {
  for idx = 0; idx < func.value_types.length(); idx = idx + 1 {
    if func.value_types[idx] == val_type {
      return func.value_type_ids[idx]
    }
  }
  let info = HotTypeInfo::Value(val_type)
  let id = func.types.length()
  func.types.push(info)
  func.type_ids.set(info.to_string(), id)
  func.value_types.push(val_type)
  func.value_type_ids.push(id)
  id
}

///|
pub fn hot_type_intern_block_result(
  func : HotFunc,
  block_type : BlockType,
) -> TypeId {
  for idx = 0; idx < func.block_types.length(); idx = idx + 1 {
    if func.block_types[idx] == block_type {
      return func.block_type_ids[idx]
    }
  }
  let info = HotTypeInfo::BlockResult(block_type)
  let id = func.types.length()
  func.types.push(info)
  func.type_ids.set(info.to_string(), id)
  func.block_types.push(block_type)
  func.block_type_ids.push(id)
  id
}

///|
pub fn hot_type_intern_resolved_block_result(
  func : HotFunc,
  block_type : BlockType,
  params : Array[ValType],
  results : Array[ValType],
) -> TypeId {
  hot_intern_type(
    func,
    HotTypeInfo::BlockResultResolved(block_type, params, results),
  )
}

///|
pub fn hot_type_intern_results(
  func : HotFunc,
  result_types : Array[ValType],
) -> TypeId {
  hot_intern_type(func, HotTypeInfo::Results(result_types))
}

///|
pub fn hot_type_void(func : HotFunc) -> TypeId {
  hot_type_intern_void(func)
}

///|
pub fn hot_type_get(func : HotFunc, type_id : TypeId) -> HotTypeInfo {
  func.types[type_id]
}

///|
fn hot_type_block_result_values(block_type : BlockType) -> Array[ValType] {
  match block_type {
    VoidBlockType => []
    ValTypeBlockType(val_type) => [val_type]
    TypeIdxBlockType(_) =>
      abort(
        "type-index block types are not supported by the current hot type queries",
      )
  }
}

///|
pub fn hot_type_result_arity(func : HotFunc, type_id : TypeId) -> Int32 {
  hot_type_result_arity_impl(func, type_id, None)
}

///|
#inline
fn hot_type_result_arity_impl(
  func : HotFunc,
  type_id : TypeId,
  copied_values : Array[Int32]?,
) -> Int32 {
  // Counting results does not require the owned arrays returned by
  // hot_type_results. Keep its unresolved-block error behavior unchanged.
  ignore(copied_values)
  match hot_type_get(func, type_id) {
    VoidType => 0
    Value(_) => 1
    BlockResult(block_type) =>
      match block_type {
        VoidBlockType => 0
        ValTypeBlockType(_) => 1
        TypeIdxBlockType(_) =>
          abort(
            "type-index block types are not supported by the current hot type queries",
          )
      }
    BlockResultResolved(_, _, values) | Results(values) => values.length()
  }
}

///|
pub fn hot_type_is_void(func : HotFunc, type_id : TypeId) -> Bool {
  match hot_type_get(func, type_id) {
    VoidType => true
    _ => false
  }
}

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

///|
pub fn hot_param_count(func : HotFunc) -> Int32 {
  func.locals.param_count
}

///|
pub fn hot_local_count(func : HotFunc) -> Int32 {
  hot_param_count(func) + func.locals.locals.length()
}

///|
pub fn hot_local_type(func : HotFunc, local_id : Int32) -> ValType? {
  if local_id < 0 {
    return None
  }
  if local_id < func.locals.params.length() {
    return Some(func.locals.params[local_id])
  }
  let body_local_id = local_id - hot_param_count(func)
  if body_local_id < 0 || body_local_id >= func.locals.locals.length() {
    None
  } else {
    Some(func.locals.locals[body_local_id])
  }
}