///|
/// Validate memory limits according to WASM spec
fn validate_memory_limits(
  mem : @types.MemoryType,
) -> Unit raise ValidationError {
  if mem.page_size_log2 != 0 && mem.page_size_log2 != 16 {
    raise InvalidLimits("invalid custom page size")
  }
  if mem.is_shared && mem.limits.max is None {
    raise InvalidLimits("shared memory requires a maximum")
  }
  let limits = mem.limits
  // memory32: max 65536 pages (2^16) = 4GB
  // memory64: max 2^48 pages = 16EB (theoretical, but limit to 2^48 pages)
  // Note: 2^48 = 281474976710656, but we use the representation that fits in Int64
  let max_pages : Int64 = if mem.is_memory64 {
    if mem.page_size_log2 == 0 {
      -1L
    } else {
      281474976710656L
    }
  } else {
    1L << (32 - mem.page_size_log2)
  }
  let error_msg = if mem.is_memory64 {
    "memory size must be at most 2^48 pages"
  } else {
    "memory size must be at most 65536 pages"
  }

  // Check min doesn't exceed max allowed
  if limits.min.reinterpret_as_uint64() > max_pages.reinterpret_as_uint64() {
    raise InvalidLimits(error_msg)
  }

  // Check max if specified
  if limits.max is Some(max_val) {
    // max must not exceed allowed maximum
    if max_val.reinterpret_as_uint64() > max_pages.reinterpret_as_uint64() {
      raise InvalidLimits(error_msg)
    }
    // min must not be greater than max (unsigned comparison)
    if limits.min.reinterpret_as_uint64() > max_val.reinterpret_as_uint64() {
      raise InvalidLimits("size minimum must not be greater than maximum")
    }
  }
}

///|
/// Validate table limits according to WASM spec
fn validate_table_limits(
  limits : @types.Limits,
  is_table64~ : Bool = false,
) -> Unit raise ValidationError {
  // For table32, min and max must fit in 32 bits (< 2^32)
  let max_table32_size = 0x100000000L // 2^32
  if !is_table64 {
    if limits.min.reinterpret_as_uint64() >=
      max_table32_size.reinterpret_as_uint64() {
      raise InvalidLimits("table size")
    }
    if limits.max is Some(max_val) &&
      max_val.reinterpret_as_uint64() >=
      max_table32_size.reinterpret_as_uint64() {
      raise InvalidLimits("table size")
    }
  }
  // Check max if specified
  if limits.max is Some(max_val) {
    // min must not be greater than max (unsigned comparison)
    if limits.min.reinterpret_as_uint64() > max_val.reinterpret_as_uint64() {
      raise InvalidLimits("size minimum must not be greater than maximum")
    }
  }
}

///|
/// Validate memory offset for load/store instructions
/// For memory32, offset must be < 2^32
fn validate_mem_offset(
  mem : @types.MemoryType,
  offset : Int64,
) -> Unit raise ValidationError {
  // For memory32, offset must fit in 32 bits (< 2^32)
  // Use unsigned comparison to handle large values like 0xFFFF_FFFF_FFFF_FFFF
  let max_offset = 0x100000000L.reinterpret_as_uint64() // 2^32
  if !mem.is_memory64 && offset.reinterpret_as_uint64() >= max_offset {
    raise InvalidLimits("offset out of range")
  }
}

///|
/// Check if a reference type is non-nullable
fn is_non_nullable_ref_type(ty : @types.ValueType) -> Bool {
  match ty {
    RefFunc
    | RefExtern
    | RefFuncTyped(_)
    | RefCont
    | RefContTyped(_)
    | RefNoCont => true
    _ => false
  }
}

///|
/// Validate table type with init expression
fn validate_table_type(
  table : @types.Table,
  ctx : ValidationContext,
  imported_globals : ArrayView[@types.GlobalType],
) -> Unit raise ValidationError {
  // Validate table limits
  validate_table_limits(table.type_.limits, is_table64=table.type_.is_table64)
  // Validate table init expression if present
  if table.init is Some(init_expr) {
    // Validate the init expression produces the correct type
    validate_const_expr(
      imported_globals,
      init_expr,
      table.type_.elem_type,
      funcs=ctx.funcs,
      types=ctx.types,
      subtyping_ctx=Some(ctx.subtyping_ctx),
      stack_switching=ctx.stack_switching,
    )
  } else if is_non_nullable_ref_type(table.type_.elem_type) {
    // If no init expression and element type is non-nullable, this is always an error
    // per WebAssembly spec, regardless of table size
    raise TypeMismatch(
      "table with non-nullable element type requires an initializer",
    )
  }
}

///|
fn count_func_imports(imports : Array[@types.Import]) -> Int32 {
  let mut count = 0
  for imp in imports {
    if imp.desc is Func(_) {
      count = count + 1
    }
  }
  count
}

///|
fn count_global_imports(imports : Array[@types.Import]) -> Int32 {
  let mut count = 0
  for imp in imports {
    if imp.desc is Global(_) {
      count = count + 1
    }
  }
  count
}

///|
fn count_tag_imports(imports : Array[@types.Import]) -> Int32 {
  let mut count = 0
  for imp in imports {
    if imp.desc is Tag(_) {
      count = count + 1
    }
  }
  count
}