///|
// Internal compilation pipeline.
// The full pass sequence (mirroring cmd/main in upstream qbe) lives here so it
// is reusable as a library. Debug dumps are written into a caller-provided
// buffer instead of stderr, which keeps the pipeline synchronous and makes the
// staged output testable.

///|
// Debug flags (P/A/I/L/M/N/C/F/S/R as in QBE main.c).
priv struct DbFlags {
  mut p : Bool
  mut a : Bool
  mut i : Bool
  mut l : Bool
  mut m : Bool
  mut n : Bool
  mut c : Bool
  mut f : Bool
  mut s : Bool
  mut r : Bool
}

///|
fn DbFlags::new() -> DbFlags {
  DbFlags::{
    p: false,
    a: false,
    i: false,
    l: false,
    m: false,
    n: false,
    c: false,
    f: false,
    s: false,
    r: false,
  }
}

///|
fn set_dbg(dbg : DbFlags, c : UInt16) -> Unit {
  match c {
    'P' | 'p' => dbg.p = true
    'A' | 'a' => dbg.a = true
    'I' | 'i' => dbg.i = true
    'L' | 'l' => dbg.l = true
    'M' | 'm' => dbg.m = true
    'N' | 'n' => dbg.n = true
    'C' | 'c' => dbg.c = true
    'F' | 'f' => dbg.f = true
    'S' | 's' => dbg.s = true
    'R' | 'r' => dbg.r = true
    _ => ()
  }
}

///|
fn dbg_from_flags(flags : String) -> DbFlags {
  let dbg = DbFlags::new()
  for j in 0.. Unit {
  for id in fn_.def_order {
    let b1 = fn_.blks[id]
    if b1.dom_link < 0 {
      continue
    }
    let name = b1.name
    let pad = " ".repeat(
      if 10 > name.length() {
        10 - name.length()
      } else {
        0
      },
    )
    out.write_string(pad)
    out.write_string(name)
    out.write_string(":")
    let mut c = b1.dom_link
    while c >= 0 {
      out.write_string(" ")
      out.write_string(fn_.blks[c].name)
      c = fn_.blks[c].dom_next
    }
    out.write_string("\n")
  }
}

///|
// Print the current function in IL format, mirroring C's printfn + "\n".
fn dbg_function(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  out : StringBuilder,
) -> Unit {
  out.write_string(@parser.printfn(fn_, interner, typs))
  out.write_string("\n")
}

///|
// Run the full compilation pipeline on a function (C func() in main.c).
// Debug output is appended to `out` according to the flags; in non-debug mode
// all debug strings are empty so this is silent.
fn run_passes(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  out : StringBuilder,
) -> Unit raise {
  if dbg.p {
    out.write_string("\n> After parsing:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  @ssa.memopt(fn_)
  // mem.c: -dM section.
  if dbg.m {
    out.write_string("\n> After memory optimization:\n")
    dbg_function(fn_, interner, typs, out)
  }
  // ssa(): filldom + -dN dominators.
  @cfg.filldom(fn_)
  if dbg.n {
    out.write_string("\n> Dominators:\n")
    dbg_dominators(fn_, out)
  }
  @cfg.fillfron(fn_)
  // C ssa() sets debug['L'] = 0 during ssa, so the pre-abi liveness
  // analysis is never dumped.
  out.write_string(@live.filllive(fn_, false))
  @ssa.phiins(fn_)
  @ssa.renblk(fn_)
  @ssa.filluse(fn_)
  @ssa.ssacheck(fn_)
  // ssa.c: -dN After SSA construction.
  if dbg.n {
    out.write_string("\n> After SSA construction:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.fillloop(fn_)
  @cfg.fillalias(fn_)
  // load.c: -dM After load elimination.
  out.write_string(@ssa.loadopt(fn_, dbg.m, interner, typs))
  @ssa.filluse(fn_)
  @ssa.ssacheck(fn_)
  // copy.c: -dC.
  out.write_string(@ssa.copy(fn_, dbg.c, interner, typs))
  @ssa.filluse(fn_)
  // fold.c: -dF.
  out.write_string(@fold.fold(fn_, dbg.f, interner, typs))
  // T.abi (sysv.c): -dA.
  out.write_string(@abi.abi(fn_, typs, dbg.a, interner, typs))
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // T.isel (amd64/isel.c): -dI.
  out.write_string(@isel.isel(fn_, interner, dbg.i, typs))
  // Post-isel pipeline (main.c): fillrpo, filllive, fillcost, spill, rega.
  @types.init_amd64_target()
  @cfg.fillrpo(fn_)
  out.write_string(@live.filllive(fn_, dbg.l))
  out.write_string(@spill.fillcost(fn_, dbg.s))
  out.write_string(@spill.spill(fn_, dbg.s, interner, typs))
  out.write_string(@rega.rega(fn_, dbg.r, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.simpljmp(fn_)
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
}

///|
// Parse IL text into functions, data sections, top-level order and types.
fn parse_module(
  text : String,
  file_name : String,
) -> (
  Array[@types.Fn],
  Array[@types.Dat],
  Array[String],
  Array[@types.Typ],
  @util.Interner,
) raise {
  let lexer = @lexer.Lexer::new(text, file_name)
  let tokens = lexer.tokenize()
  let parser = @parser.Parser::new(tokens, file_name)
  parser.parse()
  (
    parser.get_funcs(),
    parser.get_datas(),
    parser.get_order(),
    parser.get_typs(),
    parser.interner_ref(),
  )
}

///|
// Translate the GAS flavor string into (gasloc, gassym), matching QBE's -G.
fn gas_setting(gas : String) -> (String, String) raise {
  match gas {
    "e" => (".L", "")
    "m" => ("L", "_")
    g => raise @util.QbeError::CompileError("unknown gas flavor '\{g}'")
  }
}

///|
// Emit assembly for the whole module in input order (data and functions
// interleaved as parsed).
fn emit_module(
  funcs : Array[@types.Fn],
  datas : Array[@types.Dat],
  order : Array[String],
  typs : Array[@types.Typ],
  interner : @util.Interner,
  gasloc : String,
  gassym : String,
  sb : StringBuilder,
) -> Unit raise {
  @types.fp_stash_reset()
  let mut fi = 0
  let mut di = 0
  for item in order {
    if item == "f" {
      let fn_ = funcs[fi]
      fi = fi + 1
      run_passes(fn_, interner, typs, DbFlags::new(), sb)
      @emit.emitfn(fn_, interner, gasloc, gassym, sb)
      sb.write_string("/* end function \{fn_.name} */\n\n")
    } else {
      while di < datas.length() {
        let d = datas[di]
        di = di + 1
        @emit.gasemitdat(d, gasloc, gassym, sb)
        if d.kind == @types.DEnd {
          sb.write_string("/* end data */\n\n")
          break
        }
      }
    }
  }
  @emit.gasemitfin(gasloc, sb)
}

///|
// Run wasm compilation pipeline on a function.
// Reuses SSA passes (target-agnostic), skips spill/rega (wasm has no
// physical registers), and runs wasm-specific ABI + isel.
fn run_passes_wasm(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  out : StringBuilder,
) -> Unit raise {
  if dbg.p {
    out.write_string("\n> After parsing:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  @ssa.memopt(fn_)
  if dbg.m {
    out.write_string("\n> After memory optimization:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.filldom(fn_)
  if dbg.n {
    out.write_string("\n> Dominators:\n")
    dbg_dominators(fn_, out)
  }
  @cfg.fillfron(fn_)
  out.write_string(@live.filllive(fn_, false))
  @ssa.phiins(fn_)
  @ssa.renblk(fn_)
  @ssa.filluse(fn_)
  @ssa.ssacheck(fn_)
  if dbg.n {
    out.write_string("\n> After SSA construction:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.fillloop(fn_)
  @cfg.fillalias(fn_)
  out.write_string(@ssa.loadopt(fn_, dbg.m, interner, typs))
  @ssa.filluse(fn_)
  @ssa.ssacheck(fn_)
  out.write_string(@ssa.copy(fn_, dbg.c, interner, typs))
  @ssa.filluse(fn_)
  out.write_string(@fold.fold(fn_, dbg.f, interner, typs))
  // Wasm ABI lowering
  out.write_string(@abi_wasm.abi_wasm(fn_, typs, dbg.a, interner, typs))
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // Wasm instruction selection
  out.write_string(@isel_wasm.isel_wasm(fn_, interner, dbg.i, typs))
  // Skip spill/rega — wasm has no physical registers
}

///|
// Emit WAT module for the whole module in input order.
fn emit_wasm_module(
  funcs : Array[@types.Fn],
  datas : Array[@types.Dat],
  order : Array[String],
  typs : Array[@types.Typ],
  interner : @util.Interner,
  sb : StringBuilder,
) -> Unit raise {
  @types.fp_stash_reset()
  for fn_ in funcs {
    run_passes_wasm(fn_, interner, typs, DbFlags::new(), sb)
  }
  @emit_wasm.emit_wat_module(funcs, datas, order, interner, sb)
}

///|
// Run RISC-V compilation pipeline on a function.
// Reuses SSA passes (target-agnostic), skips spill/rega (RISC-V uses a
// different register allocation strategy), and runs RISC-V-specific ABI + isel.
fn run_passes_rv64(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  out : StringBuilder,
) -> Unit raise {
  if dbg.p {
    out.write_string("\n> After parsing:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  @ssa.memopt(fn_)
  if dbg.m {
    out.write_string("\n> After memory optimization:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.filldom(fn_)
  if dbg.n {
    out.write_string("\n> Dominators:\n")
    dbg_dominators(fn_, out)
  }
  @cfg.fillfron(fn_)
  out.write_string(@live.filllive(fn_, false))
  @ssa.phiins(fn_)
  @ssa.renblk(fn_)
  @ssa.filluse(fn_)
  @ssa.ssacheck(fn_)
  if dbg.n {
    out.write_string("\n> After SSA construction:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.fillloop(fn_)
  @cfg.fillalias(fn_)
  out.write_string(@ssa.loadopt(fn_, dbg.m, interner, typs))
  @ssa.filluse(fn_)
  @ssa.ssacheck(fn_)
  out.write_string(@ssa.copy(fn_, dbg.c, interner, typs))
  @ssa.filluse(fn_)
  out.write_string(@fold.fold(fn_, dbg.f, interner, typs))
  // RISC-V ABI lowering
  out.write_string(@abi_rv64.abi_rv64(fn_, typs, dbg.a, interner, typs))
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // RISC-V instruction selection
  out.write_string(@isel_rv64.isel_rv64(fn_, interner, dbg.i, typs))
  // Post-isel: fillrpo, filllive, fillcost, spill, rega.
  @types.init_rv64_target()
  @cfg.fillrpo(fn_)
  out.write_string(@live.filllive(fn_, dbg.l))
  out.write_string(@spill.fillcost(fn_, dbg.s))
  out.write_string(@spill.spill(fn_, dbg.s, interner, typs))
  out.write_string(@rega.rega(fn_, dbg.r, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.simpljmp(fn_)
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
}

///|
// Emit RISC-V assembly for the whole module in input order.
fn emit_rv64_module(
  funcs : Array[@types.Fn],
  datas : Array[@types.Dat],
  order : Array[String],
  typs : Array[@types.Typ],
  interner : @util.Interner,
  sb : StringBuilder,
) -> Unit raise {
  @types.fp_stash_reset()
  for fn_ in funcs {
    run_passes_rv64(fn_, interner, typs, DbFlags::new(), sb)
    sb.write_string(@emit_rv64.emit_rv64(fn_, interner, false, typs))
  }
}