///|
pub(all) enum NativeGlueKind {
  ExecutableMemory
  TrapHandler
  StackSwitch
  HostCallBridge
  WasmMemoryOp
  GcRuntime
  ExceptionHandling
  WasiPreview1
  DebugInfo
} derive(Eq, Debug)

///|
pub(all) struct NativeGlueBinding {
  kind : NativeGlueKind
  symbol : String
  sources : Array[String]
} derive(Eq, Debug)

///|
pub(all) struct JitIntegrationPlan {
  entry_symbol : String
  target : @machv_emit.EmitTarget
  native_glue : Array[NativeGlueBinding]
  object : @machv_emit.MachineCode
}

///|
pub suberror JitPipelineError {
  PipelineFailure(message~ : String)
} derive(Debug, Eq)

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

///|
pub fn jit_ffi_native_stub_sources() -> Array[String] {
  [
    "jit.c", "trap.c", "exec_mem.c", "jit_context.c", "memory_ops.c", "segment_ops.c",
    "gc_type_cache.c", "gc_ops.c", "gc_allocator.c", "gc_heap.c", "exception.c",
    "wasi.c", "wasm_stack.c", "trampoline_call_x86_64.S", "stack_switch_aarch64.S",
    "stack_switch_x86_64.S", "dwarf.c",
  ]
}

///|
pub fn default_native_glue() -> Array[NativeGlueBinding] {
  [
    {
      kind: ExecutableMemory,
      symbol: "wasmoon_jit_exec_mem",
      sources: ["exec_mem.c"],
    },
    { kind: TrapHandler, symbol: "wasmoon_jit_trap", sources: ["trap.c"] },
    {
      kind: StackSwitch,
      symbol: "wasmoon_jit_stack_switch",
      sources: [
        "wasm_stack.c", "trampoline_call_x86_64.S", "stack_switch_aarch64.S", "stack_switch_x86_64.S",
      ],
    },
    {
      kind: HostCallBridge,
      symbol: "wasmoon_jit_hostcall",
      sources: ["jit.c", "jit_context.c"],
    },
    {
      kind: WasmMemoryOp,
      symbol: "wasmoon_jit_memory_op",
      sources: ["memory_ops.c", "segment_ops.c"],
    },
    {
      kind: GcRuntime,
      symbol: "wasmoon_jit_gc",
      sources: ["gc_type_cache.c", "gc_ops.c", "gc_allocator.c", "gc_heap.c"],
    },
    {
      kind: ExceptionHandling,
      symbol: "wasmoon_jit_exception",
      sources: ["exception.c"],
    },
    { kind: WasiPreview1, symbol: "wasmoon_jit_wasi", sources: ["wasi.c"] },
    { kind: DebugInfo, symbol: "wasmoon_jit_dwarf", sources: ["dwarf.c"] },
  ]
}

///|
pub fn native_glue_covers_source(
  glue : Array[NativeGlueBinding],
  source : String,
) -> Bool {
  for binding in glue {
    for owned in binding.sources {
      if owned == source {
        return true
      }
    }
  }
  false
}

///|
pub fn native_glue_covers_jit_ffi_stubs(
  glue? : Array[NativeGlueBinding] = default_native_glue(),
) -> Bool {
  for source in jit_ffi_native_stub_sources() {
    if !native_glue_covers_source(glue, source) {
      return false
    }
  }
  true
}

///|
pub fn plan_integration_for_target(
  func : @milkir.Function,
  machv_func : @machv.Function,
  regalloc_output : @machv_regalloc.Output,
  target : @machv_emit.EmitTarget,
) -> JitIntegrationPlan {
  let isa = emit_target_to_isa(target)
  let embedding_abi = wasm_embedding_abi_for_isa(isa)
  {
    entry_symbol: func.name,
    target,
    native_glue: default_native_glue(),
    object: @machv_emit.emit_function_with_regalloc(
      machv_func,
      regalloc_output,
      isa~,
      embedding_abi=Some(embedding_abi),
    ),
  }
}

///|
pub fn emit_target_to_isa(target : @machv_emit.EmitTarget) -> @isa.ISA {
  match target {
    AArch64 => AArch64
    X64 => AMD64
  }
}

///|
pub fn lower_milkir_for_target(
  func : @milkir.Function,
  target : @machv_emit.EmitTarget,
) -> @machv.Function {
  let isa = emit_target_to_isa(target)
  let embedding_abi = wasm_embedding_abi_for_isa(isa)
  @wasm_isa_lower.lower_function(
    func,
    wasm_context_slots(),
    isa~,
    embedding_abi=Some(embedding_abi),
    runtime_symbols=wasm_runtime_symbols(),
    trap_payload_resolver=wasm_trap_payload,
  )
}

///|
pub fn lower_wasm_body_milkir_for_target(
  func : @milkir.Function,
  target : @machv_emit.EmitTarget,
) -> @machv.Function {
  let isa = emit_target_to_isa(target)
  let embedding_abi = wasm_embedding_abi_for_isa(isa)
  @wasm_isa_lower.lower_function(
    func,
    wasm_context_slots(),
    isa~,
    run_ir_opt=false,
    embedding_abi=Some(embedding_abi),
    runtime_symbols=wasm_runtime_symbols(),
    trap_payload_resolver=wasm_trap_payload,
  )
}

///|
pub fn plan_milkir_integration_for_target(
  func : @milkir.Function,
  target : @machv_emit.EmitTarget,
) -> JitIntegrationPlan {
  let isa = emit_target_to_isa(target)
  let embedding_abi = wasm_embedding_abi_for_isa(isa)
  let machv_func = @wasm_isa_lower.lower_function(
    func,
    wasm_context_slots(),
    isa~,
    embedding_abi=Some(embedding_abi),
    runtime_symbols=wasm_runtime_symbols(),
    trap_payload_resolver=wasm_trap_payload,
  )
  let (allocated_func, output) = @machv_regalloc.allocate_registers_backtracking_output_with_isa(
    machv_func,
    isa,
    embedding_abi=Some(embedding_abi),
  )
  plan_integration_for_target(func, allocated_func, output, target)
}

///|
pub fn plan_wasm_body_milkir_integration_for_target(
  func : @milkir.Function,
  target : @machv_emit.EmitTarget,
) -> JitIntegrationPlan {
  let isa = emit_target_to_isa(target)
  let embedding_abi = wasm_embedding_abi_for_isa(isa)
  let machv_func = @wasm_isa_lower.lower_function(
    func,
    wasm_context_slots(),
    isa~,
    run_ir_opt=false,
    embedding_abi=Some(embedding_abi),
    runtime_symbols=wasm_runtime_symbols(),
    trap_payload_resolver=wasm_trap_payload,
  )
  let (allocated_func, output) = @machv_regalloc.allocate_registers_backtracking_output_with_isa(
    machv_func,
    isa,
    embedding_abi=Some(embedding_abi),
  )
  plan_integration_for_target(func, allocated_func, output, target)
}