// mir_builder_min.hako — Minimal MIR(JSON v0) builder for selfhost tests
// Scope: selfhost only (apps/selfhost/...); no core/runtime changes.

using selfhost.shared.common.string_helpers as StringHelpers
using selfhost.shared.common.box_helpers as BoxHelpers
using lang.compiler.emit.common.json_emit as JsonEmitBox
using lang.compiler.emit.common.mir_emit as MirEmitBox
using lang.compiler.emit.common.call_emit as CallEmitBox
using lang.compiler.emit.common.newbox_emit as NewBoxEmitBox
using selfhost.shared.json.json_inst_encode_box as JsonInstEncodeBox

box MirJsonBuilderMin {
  buf: StringBox
  phase: IntegerBox
  first_inst: IntegerBox
  blocks: ArrayBox
  cur_block_index: IntegerBox
  fn_name: StringBox
  trace: IntegerBox
  verify: IntegerBox

  birth() {
    me.buf = ""
    me.phase = 0
    me.first_inst = 1
    me.blocks = new ArrayBox()
    me.cur_block_index = -1
    me.fn_name = ""
    me.trace = 0
    me.verify = 0
  }

  // Internal helpers
  _get_buf() { return me.buf }
  _set_buf(s) { me.buf = s return me }
  _append(s) {
    me.buf = me.buf + s
    return me
  }
  _int_to_str(n) { return StringHelpers.int_to_str(n) }
  _to_i64(x) { return StringHelpers.to_i64(x) }

  _quote(s) { return StringHelpers.json_quote(s) }

  _ids_range_json(start, count) {
    local i = 0
    local out = "["
    loop (i < count) {
      if i > 0 { out = out + "," }
      out = out + me._int_to_str(start + i)
      i = i + 1
    }
    out = out + "]"
    return out
  }

  _ids_array_from_json_text(arr_text) {
    local out = new ArrayBox()
    if arr_text == null { return out }
    local s = "" + arr_text
    local i = 0
    loop (i < s.length()) {
      local ch = s.substring(i, i+1)
      if ch >= "0" && ch <= "9" {
        local j = i
        loop (j < s.length()) {
          local cj = s.substring(j, j+1)
          if !(cj >= "0" && cj <= "9") { break }
          j = j + 1
        }
        local num = me._to_i64(s.substring(i, j))
        out.push(num)
        i = j
      } else {
        i = i + 1
      }
    }
    return out
  }

  _ensure_phase(want) {
    if me.phase + 1 < want { return me }
    return me
  }

  // Public builder steps
  start_module() {
    if me.trace == 1 { print("[DEBUG start_module] starting") }
    me.phase = 1
    return me._append("{\"functions\":[")
  }

  start_function(name) {
    if me.trace == 1 { print("[DEBUG start_function] name=" + name) }
    me.phase = 2
    me.fn_name = name
    me.blocks = new ArrayBox()
    me.cur_block_index = -1
    if me.trace == 1 { print("[DEBUG start_function] after set, fn_name=" + me.fn_name) }
    local b = "{\"name\":" + me._quote(name) + ",\"params\":[],\"blocks\":["
    return me._append(b)
  }

  start_block(id) {
    me.phase = 3
    me.first_inst = 1
    local blk = { id: id, instructions: new ArrayBox() }
    me.blocks.push(blk)
    me.cur_block_index = me.blocks.length() - 1
    local b = "{\"id\":" + me._int_to_str(id) + ",\"instructions\":["
    return me._append(b)
  }

  end_block_continue() {
    return me._append("]},")
  }

  _comma_if_needed() {
    if me.first_inst == 1 {
      me.first_inst = 0
      return me
    }
    return me._append(",")
  }

  add_const(dst, val) {
    me._comma_if_needed()
    local node = MirEmitBox.make_const(dst, val)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_compare(kind, lhs, rhs, dst) {
    me._comma_if_needed()
    local node = MirEmitBox.make_compare(kind, lhs, rhs, dst)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_binop(kind, lhs, rhs, dst) {
    me._comma_if_needed()
    local node = { op:"binop", op_kind:kind, lhs:lhs, rhs:rhs, dst:dst }
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_ret(val) {
    me._comma_if_needed()
    local node = MirEmitBox.make_ret(val)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_call_ids(name, args_json_text, dst) {
    me._comma_if_needed()
    local args = me._ids_array_from_json_text(args_json_text)
    local node = CallEmitBox.make_call(name, args, dst)
    node.set("args_text", args_json_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_call_range(name, start, count, dst) {
    me._comma_if_needed()
    local args = new ArrayBox()
    local i = 0
    loop(i < count) {
      args.push(start + i)
      i = i + 1
    }
    local args_text = me._ids_range_json(start, count)
    local node = CallEmitBox.make_call(name, args, dst)
    node.set("args_text", args_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_boxcall_range(method, recv_id, args_start, count, dst) {
    me._comma_if_needed()
    local args = new ArrayBox()
    local i = 0
    loop(i < count) {
      args.push(args_start + i)
      i = i + 1
    }
    local args_text = me._ids_range_json(args_start, count)
    local node = CallEmitBox.make_boxcall(method, recv_id, args, dst)
    node.set("args_text", args_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_newbox_range(box_type, args_start, count, dst) {
    me._comma_if_needed()
    local args = new ArrayBox()
    local i = 0
    loop(i < count) {
      args.push(args_start + i)
      i = i + 1
    }
    local node = NewBoxEmitBox.with_args_array(NewBoxEmitBox.make_new(box_type, args, dst), args)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_newbox_ids(box_type, args_json_text, dst) {
    me._comma_if_needed()
    local args = me._ids_array_from_json_text(args_json_text)
    local node = NewBoxEmitBox.make_new(box_type, args, dst)
    node = NewBoxEmitBox.with_args_text(node, args_json_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  _args_array_json_from_ids(ids_start, count) {
    return me._ids_range_json(ids_start, count)
  }

  add_mir_call_global_ids(name, args_json_text, dst) {
    me._comma_if_needed()
    local args = me._ids_array_from_json_text(args_json_text)
    local node = CallEmitBox.make_mir_call_global(name, args, dst)
    node.set("args_text", args_json_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_mir_call_extern_ids(name, args_json_text, dst) {
    me._comma_if_needed()
    local args = me._ids_array_from_json_text(args_json_text)
    local node = CallEmitBox.make_mir_call_extern(name, args, dst)
    node.set("args_text", args_json_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_mir_call_method_ids(method, recv_id, args_json_text, dst) {
    me._comma_if_needed()
    local args = me._ids_array_from_json_text(args_json_text)
    local node = CallEmitBox.make_mir_call_method(method, recv_id, args, dst)
    node.set("args_text", args_json_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_mir_call_constructor_ids(box_type, args_json_text, dst) {
    me._comma_if_needed()
    local args = me._ids_array_from_json_text(args_json_text)
    local node = CallEmitBox.make_mir_call_constructor(box_type, args, dst)
    node.set("args_text", args_json_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_mir_call_global_range(name, start, count, dst) {
    me._comma_if_needed()
    local args = new ArrayBox()
    local i = 0
    loop(i < count) {
      args.push(start + i)
      i = i + 1
    }
    local args_text = me._ids_range_json(start, count)
    local node = CallEmitBox.make_mir_call_global(name, args, dst)
    node.set("args_text", args_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_mir_call_extern_range(name, start, count, dst) {
    me._comma_if_needed()
    local args = new ArrayBox()
    local i = 0
    loop(i < count) { args.push(start + i) i = i + 1 }
    local args_text = me._ids_range_json(start, count)
    local node = CallEmitBox.make_mir_call_extern(name, args, dst)
    node.set("args_text", args_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_mir_call_method_range(method, recv_id, args_start, count, dst) {
    me._comma_if_needed()
    local args = new ArrayBox()
    local i = 0
    loop(i < count) {
      args.push(args_start + i)
      i = i + 1
    }
    local args_text = me._ids_range_json(args_start, count)
    local node = CallEmitBox.make_mir_call_method(method, recv_id, args, dst)
    node.set("args_text", args_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_mir_call_constructor_range(box_type, args_start, count, dst) {
    me._comma_if_needed()
    local args = new ArrayBox()
    local i = 0
    loop(i < count) {
      args.push(args_start + i)
      i = i + 1
    }
    local args_text = me._ids_range_json(args_start, count)
    local node = CallEmitBox.make_mir_call_constructor(box_type, args, dst)
    node.set("args_text", args_text)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_branch(cond, then_id, else_id) {
    me._comma_if_needed()
    local node = MirEmitBox.make_branch(cond, then_id, else_id)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_jump(target) {
    me._comma_if_needed()
    local node = MirEmitBox.make_jump(target)
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  add_copy(dst, src) {
    me._comma_if_needed()
    local node = { op:"copy", dst:dst, src:src }
    me._push_inst_map(node)
    return me._append(JsonEmitBox.to_json(node))
  }

  end_all() {
    me.phase = 5
    return me._append("]}]}]}")
  }

  to_string() { return me._get_buf() }

  _cur_insts() {
    local blks = me.blocks
    if blks == null { return null }
    local n = 0
    n = BoxHelpers.array_len(blks)
    if n <= 0 { return null }
    local idx = me.cur_block_index
    if idx == null { idx = -1 }
    if idx < 0 || idx >= n { idx = n - 1 }
    local blk = BoxHelpers.array_get(blks, idx)
    if blk == null { return null }
    return BoxHelpers.map_get(blk, "instructions")
  }

  verify_builder_state() { return 0 }

  _verify_enabled() {
    return me.verify
  }

  _trace_enabled() {
    return me.trace
  }

  _push_inst(node) {
    local before = 0
    local insts0 = me._cur_insts()
    before = BoxHelpers.array_len(insts0)
    if insts0 == null || insts0.push == null { return }
    insts0.push(node)
    if me._verify_enabled() == 1 {
      local insts1 = me._cur_insts()
      local after = BoxHelpers.array_len(insts1)
      local op_str = BoxHelpers.map_get(node, "op")
      if after != before + 1 { print("[builder-verify] size mismatch before=" + before + " after=" + after + " op=" + op_str) }
    }
    if me._trace_enabled() == 1 {
      local op_str_trace = BoxHelpers.map_get(node, "op")
      print("[builder-trace] push op=" + op_str_trace)
    }
  }

  _push_inst_map(node) { return me._push_inst(node) }

  enable_trace(on) {
    if on == null { on = 1 }
    me.trace = on
    return me
  }

  enable_verify(on) {
    if on == null { on = 1 }
    me.verify = on
    return me
  }

  get_current_instructions() { return me._cur_insts() }

  get_blocks_array() { return me.blocks }

  get_function_structure() {
    local blks = me.get_blocks_array()
    return { name: me.fn_name, params: new ArrayBox(), blocks: blks }
  }

  emit_to_string() { return me.to_string() }

  _inst_json(node) { return JsonInstEncodeBox.encode(node) }

  to_string_rebuild() {
    local name = me.fn_name
    local blks = me.get_blocks_array()
    local blks_size_str = "null"
    local blks_len = BoxHelpers.array_len(blks)
    if blks_len >= 0 { blks_size_str = me._int_to_str(blks_len) }
    print("[DEBUG rebuild] fn_name=" + name + " blks.length()=" + blks_size_str)
    local out = "{\"functions\":[{\"name\":" + me._quote(name) + ",\"params\":[],\"blocks\":["
    local n = blks_len
    print("[DEBUG rebuild] n=" + me._int_to_str(n))
    local i = 0
    loop (i < n) {
      if i > 0 { out = out + "," }
      local blk = BoxHelpers.array_get(blks, i)
      local bid = BoxHelpers.map_get(blk, "id")
      out = out + "{\"id\":" + me._int_to_str(bid) + ",\"instructions\":["
      local insts = BoxHelpers.map_get(blk, "instructions")
      local m = BoxHelpers.array_len(insts)
      local j = 0
      loop (j < m) {
        if j > 0 { out = out + "," }
        out = out + me._inst_json(BoxHelpers.array_get(insts, j))
        j = j + 1
      }
      out = out + "]}"
      i = i + 1
    }
    out = out + "]}]}"
    return out
  }
}
