///|
pub(all) struct RuntimeSymbol {
  name : String
  address : Int64
  fixup_idx : Int?
} derive(Eq, Debug)

///|
pub(all) struct RuntimeSymbolResolver {
  symbols : Array[RuntimeSymbol]
} derive(Debug)

///|
pub suberror LinkError {
  UnknownRuntimeSymbol(String)
} derive(Debug, Eq)

///|
pub impl Show for LinkError with fn output(self, logger) {
  logger.write_string(to_repr(self).to_string())
}

///|
/// Reserve negative function-index values for cwasm runtime-helper fixups.
pub fn gc_runtime_fixup_idx(ordinal : Int) -> Int {
  -20_000 - ordinal
}

///|
/// Reserve negative function-index values for cwasm runtime-helper fixups.
pub fn jit_runtime_fixup_idx(ordinal : Int) -> Int {
  -21_000 - ordinal
}

///|
/// Reserve negative function-index values for cwasm runtime-helper fixups.
pub fn exception_runtime_fixup_idx(ordinal : Int) -> Int {
  -22_000 - ordinal
}

///|
pub fn runtime_hostcall_symbol() -> @instr.ExternalName {
  ExternalName("wasmoon.runtime.hostcall")
}

///|
pub fn wasm_runtime_symbols() -> @wasm_milkir.RuntimeSymbols {
  @wasm_milkir.RuntimeSymbols::with_runtime_prefix("wasmoon.runtime")
}

///|
pub fn RuntimeSymbolResolver::RuntimeSymbolResolver() -> RuntimeSymbolResolver {
  { symbols: [] }
}

///|
pub fn RuntimeSymbolResolver::with_wasmoon_runtime_symbols() -> RuntimeSymbolResolver {
  let resolver = RuntimeSymbolResolver::RuntimeSymbolResolver()
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.ref_test",
    @jit_ffi.c_jit_get_gc_ref_test_ptr(),
    gc_runtime_fixup_idx(0),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.ref_cast",
    @jit_ffi.c_jit_get_gc_ref_cast_ptr(),
    gc_runtime_fixup_idx(1),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.struct_new",
    @jit_ffi.c_jit_get_gc_struct_new_ptr(),
    gc_runtime_fixup_idx(2),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.struct_get",
    @jit_ffi.c_jit_get_gc_struct_get_ptr(),
    gc_runtime_fixup_idx(3),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.struct_set",
    @jit_ffi.c_jit_get_gc_struct_set_ptr(),
    gc_runtime_fixup_idx(4),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_new",
    @jit_ffi.c_jit_get_gc_array_new_ptr(),
    gc_runtime_fixup_idx(5),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_get",
    @jit_ffi.c_jit_get_gc_array_get_ptr(),
    gc_runtime_fixup_idx(6),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_set",
    @jit_ffi.c_jit_get_gc_array_set_ptr(),
    gc_runtime_fixup_idx(7),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_len",
    @jit_ffi.c_jit_get_gc_array_len_ptr(),
    gc_runtime_fixup_idx(8),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_fill",
    @jit_ffi.c_jit_get_gc_array_fill_ptr(),
    gc_runtime_fixup_idx(9),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_copy",
    @jit_ffi.c_jit_get_gc_array_copy_ptr(),
    gc_runtime_fixup_idx(10),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_new_data",
    @jit_ffi.c_jit_get_gc_array_new_data_ptr(),
    gc_runtime_fixup_idx(11),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_new_elem",
    @jit_ffi.c_jit_get_gc_array_new_elem_ptr(),
    gc_runtime_fixup_idx(12),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_init_data",
    @jit_ffi.c_jit_get_gc_array_init_data_ptr(),
    gc_runtime_fixup_idx(13),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.array_init_elem",
    @jit_ffi.c_jit_get_gc_array_init_elem_ptr(),
    gc_runtime_fixup_idx(14),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.type_check_subtype",
    @jit_ffi.c_jit_get_gc_type_check_subtype_ptr(),
    gc_runtime_fixup_idx(15),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.register_struct_inline",
    @jit_ffi.c_jit_get_gc_register_struct_inline_ptr(),
    gc_runtime_fixup_idx(16),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.register_array_inline",
    @jit_ffi.c_jit_get_gc_register_array_inline_ptr(),
    gc_runtime_fixup_idx(17),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.alloc_struct_slow",
    @jit_ffi.c_jit_get_gc_alloc_struct_slow_ptr(),
    gc_runtime_fixup_idx(18),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.alloc_array_slow",
    @jit_ffi.c_jit_get_gc_alloc_array_slow_ptr(),
    gc_runtime_fixup_idx(19),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.gc.alloc_array_from_values_slow",
    @jit_ffi.c_jit_get_gc_alloc_array_from_values_slow_ptr(),
    gc_runtime_fixup_idx(20),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.memory_grow",
    @jit_ffi.c_jit_get_memory_grow_ptr(),
    jit_runtime_fixup_idx(0),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.memory_size",
    @jit_ffi.c_jit_get_memory_size_ptr(),
    jit_runtime_fixup_idx(1),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.memory_fill",
    @jit_ffi.c_jit_get_memory_fill_ptr(),
    jit_runtime_fixup_idx(2),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.memory_fill_mem0",
    @jit_ffi.c_jit_get_memory_fill_mem0_ptr(),
    jit_runtime_fixup_idx(3),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.memory_copy",
    @jit_ffi.c_jit_get_memory_copy_ptr(),
    jit_runtime_fixup_idx(4),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.memory_copy_mem0",
    @jit_ffi.c_jit_get_memory_copy_mem0_ptr(),
    jit_runtime_fixup_idx(5),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.memory_init",
    @jit_ffi.c_jit_get_memory_init_ptr(),
    jit_runtime_fixup_idx(6),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.data_drop",
    @jit_ffi.c_jit_get_data_drop_ptr(),
    jit_runtime_fixup_idx(7),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.table_grow",
    @jit_ffi.c_jit_get_table_grow_ptr(),
    jit_runtime_fixup_idx(8),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.table_fill",
    @jit_ffi.c_jit_get_table_fill_ptr(),
    jit_runtime_fixup_idx(9),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.table_copy",
    @jit_ffi.c_jit_get_table_copy_ptr(),
    jit_runtime_fixup_idx(10),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.table_init",
    @jit_ffi.c_jit_get_table_init_ptr(),
    jit_runtime_fixup_idx(11),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.elem_drop",
    @jit_ffi.c_jit_get_elem_drop_ptr(),
    jit_runtime_fixup_idx(12),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.hostcall",
    @jit_ffi.c_jit_get_hostcall_ptr(),
    jit_runtime_fixup_idx(13),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.cancel_poll",
    @jit_ffi.c_jit_get_cancel_poll_ptr(),
    jit_runtime_fixup_idx(14),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.try_begin",
    @jit_ffi.c_jit_get_exception_try_begin_ptr(),
    exception_runtime_fixup_idx(0),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.try_end",
    @jit_ffi.c_jit_get_exception_try_end_ptr(),
    exception_runtime_fixup_idx(1),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.throw",
    @jit_ffi.c_jit_get_exception_throw_ptr(),
    exception_runtime_fixup_idx(2),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.throw_tag",
    @jit_ffi.c_jit_get_exception_throw_tag_ptr(),
    exception_runtime_fixup_idx(3),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.throw_ref",
    @jit_ffi.c_jit_get_exception_throw_ref_ptr(),
    exception_runtime_fixup_idx(4),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.delegate",
    @jit_ffi.c_jit_get_exception_delegate_ptr(),
    exception_runtime_fixup_idx(5),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.get_tag",
    @jit_ffi.c_jit_get_exception_get_tag_ptr(),
    exception_runtime_fixup_idx(6),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.get_value",
    @jit_ffi.c_jit_get_exception_get_value_ptr(),
    exception_runtime_fixup_idx(7),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.get_value_count",
    @jit_ffi.c_jit_get_exception_get_value_count_ptr(),
    exception_runtime_fixup_idx(8),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.sigsetjmp",
    @jit_ffi.c_jit_get_sigsetjmp_ptr(),
    exception_runtime_fixup_idx(9),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.spill_locals",
    @jit_ffi.c_jit_get_exception_spill_locals_ptr(),
    exception_runtime_fixup_idx(10),
  )
  resolver.add_symbol_fixup(
    "wasmoon.runtime.exception.get_spilled_local",
    @jit_ffi.c_jit_get_exception_get_spilled_local_ptr(),
    exception_runtime_fixup_idx(11),
  )
  resolver
}

///|
pub fn RuntimeSymbolResolver::add_symbol(
  self : RuntimeSymbolResolver,
  name : String,
  address : Int64,
) -> Unit {
  self.symbols.push({ name, address, fixup_idx: None })
}

///|
pub fn RuntimeSymbolResolver::add_symbol_fixup(
  self : RuntimeSymbolResolver,
  name : String,
  address : Int64,
  fixup_idx : Int,
) -> Unit {
  self.symbols.push({ name, address, fixup_idx: Some(fixup_idx) })
}

///|
pub fn RuntimeSymbolResolver::resolve_symbol(
  self : RuntimeSymbolResolver,
  name : String,
) -> RuntimeSymbol? {
  for symbol in self.symbols {
    if symbol.name == name {
      return Some(symbol)
    }
  }
  None
}

///|
pub fn RuntimeSymbolResolver::resolve(
  self : RuntimeSymbolResolver,
  name : String,
) -> Int64? {
  match self.resolve_symbol(name) {
    Some(symbol) => Some(symbol.address)
    None => None
  }
}

///|
fn parse_decimal_int(text : StringView) -> Int? {
  if text.length() == 0 {
    return None
  }
  let mut value = 0
  for i in 0.. '9' {
      return None
    }
    value = value * 10 + (ch.to_int() - '0')
  }
  Some(value)
}

///|
pub fn resolve_wasm_func_symbol(name : String) -> Int? {
  match name {
    [.. "wasm.func.", .. rest] => parse_decimal_int(rest)
    _ => None
  }
}

///|
pub fn resolve_wasm_func_code_symbol(symbol : @instr.CodeSymbol) -> Int? {
  if symbol.scope == "wasm.func" {
    Some(symbol.ordinal)
  } else {
    None
  }
}

///|
/// Resolve the negative cwasm runtime-helper function index used by Wasmoon
/// JIT call fixups. MachV emission uses named external-symbol relocations, and
/// the cwasm artifact stores those runtime helper targets as negative function
/// indices for the loader to patch.
pub fn resolve_runtime_func_fixup_idx(idx : Int) -> Int64? {
  match idx {
    -20000 => Some(@jit_ffi.c_jit_get_gc_ref_test_ptr())
    -20001 => Some(@jit_ffi.c_jit_get_gc_ref_cast_ptr())
    -20002 => Some(@jit_ffi.c_jit_get_gc_struct_new_ptr())
    -20003 => Some(@jit_ffi.c_jit_get_gc_struct_get_ptr())
    -20004 => Some(@jit_ffi.c_jit_get_gc_struct_set_ptr())
    -20005 => Some(@jit_ffi.c_jit_get_gc_array_new_ptr())
    -20006 => Some(@jit_ffi.c_jit_get_gc_array_get_ptr())
    -20007 => Some(@jit_ffi.c_jit_get_gc_array_set_ptr())
    -20008 => Some(@jit_ffi.c_jit_get_gc_array_len_ptr())
    -20009 => Some(@jit_ffi.c_jit_get_gc_array_fill_ptr())
    -20010 => Some(@jit_ffi.c_jit_get_gc_array_copy_ptr())
    -20011 => Some(@jit_ffi.c_jit_get_gc_array_new_data_ptr())
    -20012 => Some(@jit_ffi.c_jit_get_gc_array_new_elem_ptr())
    -20013 => Some(@jit_ffi.c_jit_get_gc_array_init_data_ptr())
    -20014 => Some(@jit_ffi.c_jit_get_gc_array_init_elem_ptr())
    -20015 => Some(@jit_ffi.c_jit_get_gc_type_check_subtype_ptr())
    -20016 => Some(@jit_ffi.c_jit_get_gc_register_struct_inline_ptr())
    -20017 => Some(@jit_ffi.c_jit_get_gc_register_array_inline_ptr())
    -20018 => Some(@jit_ffi.c_jit_get_gc_alloc_struct_slow_ptr())
    -20019 => Some(@jit_ffi.c_jit_get_gc_alloc_array_slow_ptr())
    -20020 => Some(@jit_ffi.c_jit_get_gc_alloc_array_from_values_slow_ptr())
    -21000 => Some(@jit_ffi.c_jit_get_memory_grow_ptr())
    -21001 => Some(@jit_ffi.c_jit_get_memory_size_ptr())
    -21002 => Some(@jit_ffi.c_jit_get_memory_fill_ptr())
    -21003 => Some(@jit_ffi.c_jit_get_memory_fill_mem0_ptr())
    -21004 => Some(@jit_ffi.c_jit_get_memory_copy_ptr())
    -21005 => Some(@jit_ffi.c_jit_get_memory_copy_mem0_ptr())
    -21006 => Some(@jit_ffi.c_jit_get_memory_init_ptr())
    -21007 => Some(@jit_ffi.c_jit_get_data_drop_ptr())
    -21008 => Some(@jit_ffi.c_jit_get_table_grow_ptr())
    -21009 => Some(@jit_ffi.c_jit_get_table_fill_ptr())
    -21010 => Some(@jit_ffi.c_jit_get_table_copy_ptr())
    -21011 => Some(@jit_ffi.c_jit_get_table_init_ptr())
    -21012 => Some(@jit_ffi.c_jit_get_elem_drop_ptr())
    -21013 => Some(@jit_ffi.c_jit_get_hostcall_ptr())
    -21014 => Some(@jit_ffi.c_jit_get_cancel_poll_ptr())
    -22000 => Some(@jit_ffi.c_jit_get_exception_try_begin_ptr())
    -22001 => Some(@jit_ffi.c_jit_get_exception_try_end_ptr())
    -22002 => Some(@jit_ffi.c_jit_get_exception_throw_ptr())
    -22003 => Some(@jit_ffi.c_jit_get_exception_throw_tag_ptr())
    -22004 => Some(@jit_ffi.c_jit_get_exception_throw_ref_ptr())
    -22005 => Some(@jit_ffi.c_jit_get_exception_delegate_ptr())
    -22006 => Some(@jit_ffi.c_jit_get_exception_get_tag_ptr())
    -22007 => Some(@jit_ffi.c_jit_get_exception_get_value_ptr())
    -22008 => Some(@jit_ffi.c_jit_get_exception_get_value_count_ptr())
    -22009 => Some(@jit_ffi.c_jit_get_sigsetjmp_ptr())
    -22010 => Some(@jit_ffi.c_jit_get_exception_spill_locals_ptr())
    -22011 => Some(@jit_ffi.c_jit_get_exception_get_spilled_local_ptr())
    _ => None
  }
}

///|
pub fn machv_reloc_target_to_cwasm_func_idx(
  target : @instr.RelocTarget,
) -> Int? {
  match target {
    Code(symbol) => resolve_wasm_func_code_symbol(symbol)
    External(symbol) => {
      let resolver = RuntimeSymbolResolver::with_wasmoon_runtime_symbols()
      match resolver.resolve_symbol(symbol.name) {
        Some(runtime_symbol) => runtime_symbol.fixup_idx
        None => None
      }
    }
  }
}

///|
fn resolve_machv_reloc_target(
  target : @instr.RelocTarget,
  resolver : RuntimeSymbolResolver,
) -> ResolvedRuntimeRelocation? {
  match target {
    Code(symbol) =>
      match resolve_wasm_func_code_symbol(symbol) {
        Some(func_idx) =>
          Some({
            offset: 0,
            symbol: symbol.to_string(),
            address: 0L,
            fixup_idx: Some(func_idx),
          })
        None => None
      }
    External(symbol) =>
      match resolver.resolve_symbol(symbol.name) {
        Some(runtime_symbol) =>
          Some({
            offset: 0,
            symbol: symbol.name,
            address: runtime_symbol.address,
            fixup_idx: runtime_symbol.fixup_idx,
          })
        None => None
      }
  }
}

///|
pub(all) struct ResolvedRuntimeRelocation {
  offset : Int
  symbol : String
  address : Int64
  fixup_idx : Int?
} derive(Eq, Debug)

///|
pub(all) struct LinkedCode {
  code : Array[Int]
  frame_size : Int
  runtime_relocations : Array[ResolvedRuntimeRelocation]
  func_addr_fixups : Array[@machv_emit.FuncAddrFixup]
  call_fixups : Array[@machv_emit.CallFixup]
  gc_safepoints : Array[GcSafepoint]
  gc_stackmap_blob : Bytes
} derive(Debug)

///|
fn runtime_relocations_from_machine_code(
  object : @machv_emit.MachineCode,
  resolver : RuntimeSymbolResolver,
) -> Array[ResolvedRuntimeRelocation] raise LinkError {
  let runtime_relocations : Array[ResolvedRuntimeRelocation] = []
  for fixup in object.get_call_fixups() {
    match resolve_machv_reloc_target(fixup.target, resolver) {
      Some(relocation) =>
        runtime_relocations.push({
          offset: fixup.offset,
          symbol: relocation.symbol,
          address: relocation.address,
          fixup_idx: relocation.fixup_idx,
        })
      None => {
        let name = match fixup.target {
          Code(symbol) => symbol.to_string()
          External(symbol) => symbol.name
        }
        raise UnknownRuntimeSymbol(name)
      }
    }
  }
  runtime_relocations
}

///|
pub fn link_machine_code(
  object : @machv_emit.MachineCode,
  resolver : RuntimeSymbolResolver,
) -> LinkedCode raise LinkError {
  {
    code: object.get_bytes(),
    frame_size: 0,
    runtime_relocations: runtime_relocations_from_machine_code(object, resolver),
    func_addr_fixups: object.get_func_addr_fixups(),
    call_fixups: object.get_call_fixups(),
    gc_safepoints: CompiledFunction::CompiledFunction("", object, 0).get_gc_safepoints(),
    gc_stackmap_blob: CompiledFunction::CompiledFunction("", object, 0).get_gc_stackmap_blob(),
  }
}

///|
pub fn link_code_object(
  object : @machv_emit.MachineCode,
  resolver : RuntimeSymbolResolver,
) -> LinkedCode raise LinkError {
  link_machine_code(object, resolver)
}