///|
// azhzx/qbe — a lightweight QBE-style compiler backend in MoonBit.
//
// This package exposes a unified compilation entry point on top of the
// per-stage packages (`lexer`, `parser`, `cfg`, `ssa`, `fold`,
// `target_amd64/abi`, `target_amd64/isel`, `live`, `spill`, `rega`,
// `target_amd64/emit`).
//
// # Example
// ```mbt check
// test {
//   let src =
//     #|export function w $add(w %a, w %b) {
//     #|@start
//     #|  %s =w add %a, %b
//     #|  ret %s
//     #|}
//     #|
//   match compile(src) {
//     Ok(assembly) =>
//       assert_true(
//         assembly.contains("addl") &&
//         assembly.contains("add:") &&
//         assembly.contains("ret"),
//       )
//     Err(_) => fail("compile failed")
//   }
// }
// ```

///|
// Compile IL source text to GAS assembly (target: amd64_sysv).
//
// `gas` selects the GAS flavor: "e" for ELF (Linux, `.L` labels) and "m" for
// Mach-O (macOS, `L` labels and `_` symbol prefix).
//
// Returns the generated assembly, or a `@util.QbeError` describing a lexing,
// parsing, or internal compiler error.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let assembly = match compile(src) {
//     Ok(assembly) => assembly
//     Err(_) => fail("compile failed")
//   }
//   assert_true(assembly.contains("one:"))
// }
// ```
pub fn compile(
  text : String,
  gas? : String = "e",
) -> Result[String, @util.QbeError] {
  try compile_raise(text, gas) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// Compile IL source text and return the staged debug dumps selected by `flags`.
//
// `flags` is a string of QBE debug flag characters (combinable): `P` parse,
// `M` memory optimization, `N` SSA construction, `C` copy elimination, `F`
// constant folding, `A` ABI lowering, `I` instruction selection, `L` liveness,
// `S` spill, `R` register allocation. Assembly output is suppressed in debug
// mode, matching the reference CLI.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let dump = match compile_debug(src, "PN") {
//     Ok(dump) => dump
//     Err(_) => fail("compile failed")
//   }
//   assert_true(dump.contains("> After parsing:"))
// }
// ```
pub fn compile_debug(
  text : String,
  flags : String,
) -> Result[String, @util.QbeError] {
  try compile_debug_raise(text, flags) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// Assembly-producing compile, raising `@util.QbeError` on failure.
fn compile_raise(text : String, gas : String) -> String raise {
  @types.init_amd64_target()
  let (gasloc, gassym, apple) = gas_setting(gas)
  let (funcs, datas, order, typs, interner) = parse_module(text, "")
  let sb = StringBuilder::StringBuilder()
  emit_module(funcs, datas, order, typs, interner, gasloc, gassym, apple, sb)
  sb.to_string()
}

///|
// Debug-dump compile, raising `@util.QbeError` on failure.
fn compile_debug_raise(text : String, flags : String) -> String raise {
  @types.init_amd64_target()
  let (funcs, _, _, typs, interner) = parse_module(text, "")
  let dbg = dbg_from_flags(flags)
  let out = StringBuilder::StringBuilder()
  for fn_ in funcs {
    out.write_string("**** Function \{fn_.name} ****")
    run_passes(fn_, interner, typs, dbg, out)
    out.write_string("\n")
  }
  out.to_string()
}

///|
// Compile IL source text to WAT (WebAssembly Text format).
//
// Returns the generated WAT module, or a `@util.QbeError` describing
// a lexing, parsing, or internal compiler error.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let wat = match compile_wasm(src) {
//     Ok(wat) => wat
//     Err(_) => fail("wasm compile failed")
//   }
//   assert_true(wat.contains("(func $one"))
//   assert_true(wat.contains("i32.const 1"))
// }
// ```
pub fn compile_wasm(text : String) -> Result[String, @util.QbeError] {
  try compile_wasm_raise(text) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// Compile IL source text to WAT and return staged debug dumps.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let dump = match compile_wasm_debug(src, "PN") {
//     Ok(dump) => dump
//     Err(_) => fail("wasm compile failed")
//   }
//   assert_true(dump.contains("> After parsing:"))
// }
// ```
pub fn compile_wasm_debug(
  text : String,
  flags : String,
) -> Result[String, @util.QbeError] {
  try compile_wasm_debug_raise(text, flags) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// Wasm compile, raising `@util.QbeError` on failure.
fn compile_wasm_raise(text : String) -> String raise {
  let (funcs, datas, order, typs, interner) = parse_module(text, "")
  let sb = StringBuilder::StringBuilder()
  emit_wasm_module(funcs, datas, order, typs, interner, sb)
  sb.to_string()
}

///|
// Wasm debug-dump compile, raising `@util.QbeError` on failure.
fn compile_wasm_debug_raise(text : String, flags : String) -> String raise {
  let (funcs, _, _, typs, interner) = parse_module(text, "")
  let dbg = dbg_from_flags(flags)
  let out = StringBuilder::StringBuilder()
  for fn_ in funcs {
    out.write_string("**** Function \{fn_.name} ****")
    run_passes_wasm(fn_, interner, typs, dbg, out)
    out.write_string("\n")
  }
  out.to_string()
}

///|
// Compile IL source text to RISC-V GAS assembly (target: rv64).
//
// Returns the generated assembly, or a `@util.QbeError` describing a lexing,
// parsing, or internal compiler error.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let assembly = match compile_rv64(src) {
//     Ok(assembly) => assembly
//     Err(_) => fail("rv64 compile failed")
//   }
//   assert_true(assembly.contains("one:"))
//   assert_true(assembly.contains("li"))
// }
// ```
pub fn compile_rv64(
  text : String,
  gas? : String = "e",
) -> Result[String, @util.QbeError] {
  try compile_rv64_raise(text, gas) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// Compile IL source text to RISC-V assembly and return staged debug dumps.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let dump = match compile_rv64_debug(src, "PN") {
//     Ok(dump) => dump
//     Err(_) => fail("rv64 compile failed")
//   }
//   assert_true(dump.contains("> After parsing:"))
// }
// ```
pub fn compile_rv64_debug(
  text : String,
  flags : String,
) -> Result[String, @util.QbeError] {
  try compile_rv64_debug_raise(text, flags) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// RISC-V compile, raising `@util.QbeError` on failure.
fn compile_rv64_raise(text : String, gas : String) -> String raise {
  @types.init_rv64_target()
  let (funcs, datas, order, typs, interner) = parse_module(text, "")
  let (gasloc, gassym, _apple) = gas_setting(gas)
  let sb = StringBuilder::StringBuilder()
  emit_rv64_module(funcs, datas, order, typs, interner, gasloc, gassym, sb)
  sb.to_string()
}

///|
// RISC-V debug-dump compile, raising `@util.QbeError` on failure.
fn compile_rv64_debug_raise(text : String, flags : String) -> String raise {
  @types.init_rv64_target()
  let (funcs, _, _, typs, interner) = parse_module(text, "")
  let dbg = dbg_from_flags(flags)
  let out = StringBuilder::StringBuilder()
  for fn_ in funcs {
    out.write_string("**** Function \{fn_.name} ****")
    run_passes_rv64(fn_, interner, typs, dbg, out)
    out.write_string("\n")
  }
  out.to_string()
}

///|
// Compile IL source text to LoongArch64 GAS assembly (target: la64, LP64D
// ABI).
//
// `gas` selects the GAS flavor: "e" for ELF (Linux, `.L` labels) and "m" for
// Mach-O (macOS, `L` labels and `_` symbol prefix).
//
// Returns the generated assembly, or a `@util.QbeError` describing a lexing,
// parsing, or internal compiler error.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let assembly = match compile_la64(src) {
//     Ok(assembly) => assembly
//     Err(_) => fail("la64 compile failed")
//   }
//   assert_true(assembly.contains("one:"))
//   assert_true(assembly.contains("li.d $a0, 1"))
// }
// ```
pub fn compile_la64(
  text : String,
  gas? : String = "e",
) -> Result[String, @util.QbeError] {
  try compile_la64_raise(text, gas) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// Compile IL source text to LoongArch assembly and return staged debug dumps.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let dump = match compile_la64_debug(src, "PN") {
//     Ok(dump) => dump
//     Err(_) => fail("la64 compile failed")
//   }
//   assert_true(dump.contains("> After parsing:"))
// }
// ```
pub fn compile_la64_debug(
  text : String,
  flags : String,
) -> Result[String, @util.QbeError] {
  try compile_la64_debug_raise(text, flags) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// LoongArch compile, raising `@util.QbeError` on failure.
fn compile_la64_raise(text : String, gas : String) -> String raise {
  @types.init_la64_target()
  let (funcs, datas, order, typs, interner) = parse_module(text, "")
  let (gasloc, gassym, _apple) = gas_setting(gas)
  let sb = StringBuilder::StringBuilder()
  emit_la64_module(funcs, datas, order, typs, interner, gasloc, gassym, sb)
  sb.to_string()
}

///|
// LoongArch debug-dump compile, raising `@util.QbeError` on failure.
fn compile_la64_debug_raise(text : String, flags : String) -> String raise {
  @types.init_la64_target()
  let (funcs, _, _, typs, interner) = parse_module(text, "")
  let dbg = dbg_from_flags(flags)
  let out = StringBuilder::StringBuilder()
  for fn_ in funcs {
    out.write_string("**** Function \{fn_.name} ****")
    run_passes_la64(fn_, interner, typs, dbg, out)
    out.write_string("\n")
  }
  out.to_string()
}

///|
// Compile IL source text to ARM64 (AArch64, AAPCS64 ELF) GAS assembly.
//
// `gas` selects the GAS flavor: "e" for ELF (Linux, `.L` labels) and "m" for
// Mach-O (macOS, `L` labels and `_` symbol prefix).
//
// Returns the generated assembly, or a `@util.QbeError` describing a lexing,
// parsing, or internal compiler error.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let assembly = match compile_arm64(src) {
//     Ok(assembly) => assembly
//     Err(_) => fail("arm64 compile failed")
//   }
//   assert_true(assembly.contains("one:"))
//   assert_true(assembly.contains("mov\tw0, #1"))
// }
// ```
pub fn compile_arm64(
  text : String,
  gas? : String = "e",
) -> Result[String, @util.QbeError] {
  try compile_arm64_raise(text, gas) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// Supported machine targets. Keeping target selection in the library prevents
// callers such as the CLI from duplicating the backend dispatch policy.
pub(all) enum Target {
  Amd64Sysv
  Wasm32
  Rv64
  La64
  Arm64
}

///|
// Compile using a selected backend. `gas` is ignored by wasm and is accepted
// for all targets so callers can share one output path.
pub fn compile_target(
  text : String,
  target : Target,
  gas? : String = "e",
) -> Result[String, @util.QbeError] {
  match target {
    Target::Amd64Sysv => compile(text, gas~)
    Target::Wasm32 => compile_wasm(text)
    Target::Rv64 => compile_rv64(text, gas~)
    Target::La64 => compile_la64(text, gas~)
    Target::Arm64 => compile_arm64(text, gas~)
  }
}

///|
// Compile debug dumps using a selected backend.
pub fn compile_target_debug(
  text : String,
  target : Target,
  flags : String,
) -> Result[String, @util.QbeError] {
  match target {
    Target::Amd64Sysv => compile_debug(text, flags)
    Target::Wasm32 => compile_wasm_debug(text, flags)
    Target::Rv64 => compile_rv64_debug(text, flags)
    Target::La64 => compile_la64_debug(text, flags)
    Target::Arm64 => compile_arm64_debug(text, flags)
  }
}

///|
// Compile IL source text to ARM64 assembly and return staged debug dumps.
//
// # Example
// ```mbt check
// test {
//   let src = "export function w $one() { @start ret 1 }\n"
//   let dump = match compile_arm64_debug(src, "PN") {
//     Ok(dump) => dump
//     Err(_) => fail("arm64 compile failed")
//   }
//   assert_true(dump.contains("> After parsing:"))
// }
// ```
pub fn compile_arm64_debug(
  text : String,
  flags : String,
) -> Result[String, @util.QbeError] {
  try compile_arm64_debug_raise(text, flags) catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  } noraise {
    r => Ok(r)
  }
}

///|
// ARM64 compile, raising `@util.QbeError` on failure.
fn compile_arm64_raise(text : String, gas : String) -> String raise {
  @types.init_arm64_target()
  let (funcs, datas, order, typs, interner) = parse_module(text, "")
  let (gasloc, gassym, apple) = gas_setting(gas)
  let sb = StringBuilder::StringBuilder()
  emit_arm64_module(
    funcs, datas, order, typs, interner, gasloc, gassym, apple, sb,
  )
  sb.to_string()
}

///|
// ARM64 debug-dump compile, raising `@util.QbeError` on failure.
fn compile_arm64_debug_raise(text : String, flags : String) -> String raise {
  @types.init_arm64_target()
  let (funcs, _, _, typs, interner) = parse_module(text, "")
  let dbg = dbg_from_flags(flags)
  let out = StringBuilder::StringBuilder()
  for fn_ in funcs {
    out.write_string("**** Function \{fn_.name} ****")
    run_passes_arm64(fn_, interner, typs, dbg, false, out)
    out.write_string("\n")
  }
  out.to_string()
}

///|
// Route B (self-contained): compile the module and return the binary arm64
// instruction words of every function, in definition order. No assembler or
// linker is involved; the words feed object/macho.mbt or the in-memory JIT.
pub fn compile_arm64_bin(text : String) -> Array[Array[Int]] raise {
  @types.init_arm64_target()
  let (funcs, _datas, _order, typs, interner) = parse_module(text, "")
  let out = StringBuilder::StringBuilder()
  @types.fp_stash_reset()
  @emit_arm64.arm64_emit_reset()
  let res : Array[Array[Int]] = []
  for fn_ in funcs {
    run_passes_arm64(fn_, interner, typs, DbFlags::new(), true, out)
    res.push(@emit_arm64.emit_arm64_bin_fn(fn_, interner, true))
  }
  res
}

///|
// Route B (self-contained), linked: all functions in one contiguous text blob
// with intra-module direct calls patched. This is what the in-memory JIT and
// the future Mach-O writer consume.
pub fn compile_arm64_bin_module(
  text : String,
) -> @emit_arm64.Arm64BinModule raise {
  @types.init_arm64_target()
  let (funcs, datas, _order, typs, interner) = parse_module(text, "")
  let out = StringBuilder::StringBuilder()
  @types.fp_stash_reset()
  for fn_ in funcs {
    run_passes_arm64(fn_, interner, typs, DbFlags::new(), true, out)
  }
  @emit_arm64.emit_arm64_bin_module(funcs, datas, interner, true)
}

///|
// Route B (self-contained) object emission: a Mach-O arm64 object built from
// the binary emitter, with BRANCH26 / PAGE21 / PAGEOFF12 relocations. No clang.
pub fn compile_arm64_object(text : String) -> Bytes raise {
  @types.init_arm64_target()
  let (funcs, datas, _order, typs, interner) = parse_module(text, "")
  let out = StringBuilder::StringBuilder()
  @types.fp_stash_reset()
  for fn_ in funcs {
    run_passes_arm64(fn_, interner, typs, DbFlags::new(), true, out)
  }
  @emit_arm64.emit_arm64_object(funcs, datas, interner, true)
}

///|
pub type Arm64BinModule = @emit_arm64.Arm64BinModule

///|
// Convenience re-exports of the core IR types from `types`.
// Use `@qbe.Fn`, `@qbe.Ref`, ... instead of reaching into `@types` when the
// intent is "the module's public IR".

pub type Fn = @types.Fn

///|
pub type Blk = @types.Blk

///|
pub type Ins = @types.Ins

///|
pub type Tmp = @types.Tmp

///|
pub type Ref = @types.Ref

///|
pub type Con = @types.Con

///|
pub type Op = @types.Op

///|
pub type Jump = @types.Jump

///|
pub type Dat = @types.Dat

///|
pub type Typ = @types.Typ

///|
pub type BSet = @types.BSet

///|
pub type Class = @types.Class

///|
// Re-export the interpreter value type: `@qbe.VInt`, `@qbe.VFloat`,
// `@qbe.VDouble`, `@qbe.VVoid`.
pub using @interp {type InterpValue}

///|
// Interpret the IL source text directly: parse the module, lay out the data
// segment, and execute the function named `entry` (like `lli` for LLVM IR).
//
// Arguments are passed positionally; `w`/`l` parameters take `VInt`, `s`
// takes `VFloat` and `d` takes `VDouble`. External calls are resolved
// against the built-in runtime (`putchar`, `puts`, `printf`, `malloc`,
// `free`, `exit`) and, when provided, the `hook` callback first. `exit(n)`
// inside the program ends interpretation with `VInt(n)`.
//
// # Example
// ```mbt check
// async test "interpret factorial" {
//   let src =
//     #|export function l $fact(l %n) {
//     #|@start
//     #|  %c =w cslel %n, 1
//     #|  jnz %c, @base, @rec
//     #|
//     #|@base
//     #|  ret %n
//     #|
//     #|@rec
//     #|  %m =l sub %n, 1
//     #|  %r =l call $fact(l %m)
//     #|  %p =l mul %n, %r
//     #|  ret %p
//     #|}
//     #|
//   match interpret(src, args=[@interp.VInt(10)]) {
//     Ok(@interp.VInt(v)) => assert_eq(v, 3628800L)
//     Ok(_) => fail("unexpected result kind")
//     Err(_) => fail("interpret failed")
//   }
// }
// ```
pub fn interpret(
  src : String,
  entry? : String = "main",
  args? : Array[@interp.InterpValue] = [],
  max_steps? : Int = 10000000,
  max_depth? : Int = 10000,
  hook? : ((String, Array[@interp.InterpValue]) -> @interp.InterpValue?)? = None,
) -> Result[(@interp.InterpValue, String), @util.QbeError] {
  try {
    let (funcs, datas, _order, _typs, interner) = parse_module(src, "")
    @interp.run_module(
      funcs,
      datas,
      interner,
      entry,
      args,
      max_steps~,
      max_depth~,
      hook~,
    )
  } catch {
    @util.QbeError::ParseError(f, l, m) =>
      Err(@util.QbeError::ParseError(f, l, m))
    @util.QbeError::CompileError(m) => Err(@util.QbeError::CompileError(m))
    @util.QbeError::Ice(m) => Err(@util.QbeError::Ice(m))
    e => Err(@util.QbeError::Ice("\{e}"))
  }
}