///|
fn entry_trampoline_type(
  ty : @types.ValueType,
) -> @milkir.Type raise JitPipelineError {
  match ty {
    I32 => I32
    I64 => I64
    F32 => F32
    F64 => F64
    V128 =>
      raise UnsupportedEntrySignature(
        message="unsupported V128 MilkIR entry trampoline: values-vector packing needs 16-byte slot handling",
      )
    FuncRef
    | ExternRef
    | RefFunc
    | RefExtern
    | RefFuncTyped(_)
    | RefNullFuncTyped(_)
    | AnyRef
    | ExnRef
    | NullRef
    | NullFuncRef
    | NullExnRef
    | NullExternRef
    | RefStruct(_)
    | RefNullStruct(_)
    | RefArray(_)
    | RefNullArray(_)
    | RefAny
    | RefEq
    | RefNullEq
    | RefI31
    | RefNullI31
    | RefStructAbs
    | RefArrayAbs
    | StructRef
    | ArrayRef
    | RefNone => I64
  }
}

///|
fn build_entry_trampoline_milkir(
  param_types : Array[@types.ValueType],
  result_types : Array[@types.ValueType],
) -> @milkir.Function raise JitPipelineError {
  let builder = @milkir.FunctionBuilder::FunctionBuilder("entry_trampoline")
  let vmctx = builder.add_param(Ptr)
  let values_vec = builder.add_param(Ptr)
  let func_ptr = builder.add_param(Ptr)
  builder.add_result(I64)
  let entry = builder.current_block()
  let wasm_args : Array[@milkir.Value] = []
  let mut offset = 0
  for ty in param_types {
    let milk_ty = entry_trampoline_type(ty)
    let loaded = builder.load_ptr(
      milk_ty,
      values_vec,
      builder.iconst_i64(offset.to_int64()),
    )
    wasm_args.push(loaded)
    offset = offset + 8
  }
  let result_values : Array[@milkir.Value] = []
  let result_milk_types : Array[@milkir.Type] = []
  for ty in result_types {
    let milk_ty = entry_trampoline_type(ty)
    result_milk_types.push(milk_ty)
    result_values.push(builder.get_function().new_value(milk_ty))
  }
  let call_args : Array[@milkir.Value] = [func_ptr, vmctx]
  for arg in wasm_args {
    call_args.push(arg)
  }
  entry.append_inst(
    builder
    .get_function()
    .new_inst(
      Call(Pointer(wasm_args.length() + 1, result_values.length())),
      call_args,
      result_values,
    ),
  )
  for i, result in result_values {
    builder.store_ptr(
      result_milk_types[i],
      values_vec,
      result,
      builder.iconst_i64(offset.to_int64()),
    )
    offset = offset + 8
  }
  builder.return_([builder.iconst_i64(0L)])
  builder.get_function()
}

///|
pub fn entry_trampoline_value_type_code(ty : @types.ValueType) -> Int {
  match ty {
    I32 => 0
    I64 => 1
    F32 => 2
    F64 => 3
    V128 => 4
    FuncRef
    | ExternRef
    | RefFunc
    | RefExtern
    | RefFuncTyped(_)
    | RefNullFuncTyped(_)
    | AnyRef
    | ExnRef
    | NullRef
    | NullFuncRef
    | NullExnRef
    | NullExternRef
    | RefStruct(_)
    | RefNullStruct(_)
    | RefArray(_)
    | RefNullArray(_)
    | RefAny
    | RefEq
    | RefNullEq
    | RefI31
    | RefNullI31
    | RefStructAbs
    | RefArrayAbs
    | StructRef
    | ArrayRef
    | RefNone => 1
  }
}

///|
pub fn entry_trampoline_type_codes(
  types : Array[@types.ValueType],
) -> Array[Int] {
  let codes : Array[Int] = []
  for ty in types {
    codes.push(entry_trampoline_value_type_code(ty))
  }
  codes
}

///|
pub fn count_entry_trampoline_slots(types : Array[@types.ValueType]) -> Int {
  let mut slots = 0
  for ty in types {
    if ty is V128 {
      slots = slots + 2
    } else {
      slots = slots + 1
    }
  }
  slots
}

///|
pub fn entry_trampoline_signature_hash(
  param_types : Array[@types.ValueType],
  result_types : Array[@types.ValueType],
) -> Int64 {
  let mut hash = 0xcbf29ce484222325L
  let fnv_prime = 0x100000001b3L
  hash = hash ^ param_types.length().to_int64()
  hash = hash * fnv_prime
  for ty in param_types {
    hash = hash ^ entry_trampoline_value_type_code(ty).to_int64()
    hash = hash * fnv_prime
  }
  hash = hash ^ 0xffL
  hash = hash * fnv_prime
  hash = hash ^ result_types.length().to_int64()
  hash = hash * fnv_prime
  for ty in result_types {
    hash = hash ^ entry_trampoline_value_type_code(ty).to_int64()
    hash = hash * fnv_prime
  }
  hash
}

///|
pub fn can_use_reusable_entry_trampoline(
  param_types : Array[@types.ValueType],
  result_types : Array[@types.ValueType],
) -> Bool {
  // Keep the MilkIR-planned entry trampoline conservative. The direct
  // machine-code entry trampoline covers the broader production ABI; this path
  // remains limited to the scalar register-only signature covered by the
  // reusable MilkIR/MachV pipeline test.
  if param_types.length() != 2 || result_types.length() != 1 {
    return false
  }
  param_types[0] is I64 && param_types[1] is I64 && result_types[0] is I64
}

///|
pub fn plan_entry_trampoline_for_target(
  param_types : Array[@types.ValueType],
  result_types : Array[@types.ValueType],
  target : NativeTarget,
) -> JitIntegrationPlan raise JitPipelineError {
  plan_milkir_integration_for_target(
    build_entry_trampoline_milkir(param_types, result_types),
    target,
  )
}