// mir_io_box.hako — MirIoBox (Phase A, Hako-side implementation)
// Responsibility: provide minimal read/validate/cursor API using existing locator boxes.
// Provider selection:
//   - Default: scan (string scanning, no plugin required)
//   - Optional: yyjson (JsonDoc/JsonNode via HostBridge extern) when JSON plugin is available
// Fail‑Fast:
//   - validate() checks kind/schema/functions
//   - validate_function(): terminator required + jump/branch target existence (scan path ensured; provider path WIP)

using selfhost.vm.boxes.result_box as Result
using selfhost.vm.boxes.result_helpers as ResultHelpers
using selfhost.shared.json.core.json_cursor as JsonCursorBox
using selfhost.shared.json.core.json_canonical as JsonCanonicalBox
using selfhost.vm.hakorune-vm.function_locator as FunctionLocatorBox
using selfhost.vm.hakorune-vm.blocks_locator as BlocksLocatorBox
using selfhost.vm.hakorune-vm.instrs_locator as InstrsLocatorBox
using selfhost.vm.hakorune-vm.backward_object_scanner as BackwardObjectScannerBox
using selfhost.vm.hakorune-vm.block_iterator as BlockIteratorBox
using selfhost.shared.common.common_imports as CommonImports
using selfhost.shared.common.box_helpers as BoxHelpers

static box MirIoBox {
  // Internal: provider gate (yyjson)
  _provider_gate_on() {
    // Accept HAKO_JSON_PROVIDER or NYASH_JSON_PROVIDER (alias). Value: 'yyjson'
    // Use extern env.local.get to avoid direct field access to `env`.
    local v = env.get("HAKO_JSON_PROVIDER")
    if v == null || v == "" { v = env.get("NYASH_JSON_PROVIDER") }
    if v == null || v == "" { return 0 }
    if v == "yyjson" || v == "YYJSON" { return 1 }
    return 0
  }
  // Provider policy
  // - Default path uses string scanning (no plugin dependency)
  // - When JSON provider (yyjson) is available via HostBridge, provider path becomes eligible
  // - This box must remain Fail-Fast: invalid kind/schema/functions should be rejected deterministically
  // - Provider enablement is transparent; do not add silent fallbacks
  // Return canonicalized full MIR JSON (keys sorted, arrays preserve order)
  normalize(json) {
    return JsonCanonicalBox.canonicalize(json)
  }
  // Provider selection: prefer JSON provider when available; otherwise use scan locators
  // 判定は _json_root() の成否で行う（ENV不要）。



  // Helper: parse JSON and return root JsonNodeBox (provider=yyjson)
  _json_root(json) {
    // Minimal provider (default OFF). When ON, attempt JsonDocBox.parse+root via HostBridge.
    if me._provider_gate_on() == 0 { return null }
    if json == null { return Result.Err("json provider: input is null") }
    // Construct JsonDocBox without ArrayBox dependency
    local doc = hostbridge.box_new0("JsonDocBox")
    if doc == null { return Result.Err("json provider: box_new failed") }
    // parse(json) — 1-arg convenience wrapper
    hostbridge.box_call1(doc, "parse", json)
    // check error()
    local err = hostbridge.box_call0(doc, "error")
    if err != null && err != "" { return Result.Err("json provider: parse failed: " + err) }
    // root() — existence確認のみ（成功時は Ok(0) を返す）
    local node = hostbridge.box_call0(doc, "root")
    if node == null { return Result.Err("json provider: root failed") }
    return Result.Ok(0)
  }
  validate(json) {
    if json == null { return Result.Err("null json") }
    // Optional: canonicalize full MIR JSON at ingress (identity/no-op for now)
    json = JsonCanonicalBox.canonicalize(json)
    // Accept full root (preferred)
    if json.indexOf("\"kind\":\"MIR\"") >= 0 {
      if json.indexOf("\"schema_version\":\"1.0\"") < 0 { return Result.Err("invalid schema_version") }
      if json.indexOf("\"functions\"") < 0 { return Result.Err("functions missing") }
      return Result.Ok(0)
    }
    // Tolerate narrowed JSON (single function object) used by nyvm bridge
    if json.indexOf("\"instructions\"") >= 0 && json.indexOf("\"id\"") >= 0 {
      return Result.Ok(0)
    }
    return Result.Err("invalid kind")
  }

  // Return function JSON meta (content)
  functions(json) {
    // Canonicalize full root when present (identity until host bridge is wired)
    json = JsonCanonicalBox.canonicalize(json)
    // Provider disabled: always use scan locator
    local loc = FunctionLocatorBox.locate(json)
    if loc.is_Err() { return loc }
    return loc
  }

  // Return blocks[] meta (content without brackets)
  blocks(func_json) {
    // Provider disabled: always use scan locator
    local loc = BlocksLocatorBox.locate(func_json)
    if loc.is_Err() { return loc }
    return loc
  }

  // Return instructions content and single flag
  instructions(block_json) {
    // Provider disabled: always use scan locator
    local loc = InstrsLocatorBox.locate(block_json)
    if loc.is_Err() { return loc }
    return loc
  }

  // Return last terminator object (as string)
  terminator(block_json) {
    // Provider disabled: use scan locator then parse last object
    local loc = InstrsLocatorBox.locate(block_json)
    if loc.is_Err() { return loc }
    local meta = loc.as_Ok()
    local insts = BoxHelpers.map_get(meta, "content")
    if insts == "" { return Result.Err("terminator not found") }
    local single = BoxHelpers.map_get(meta, "single")
    if single != null && single == 1 { return Result.Ok(insts) }
    // use small window for large JSON (8192 bytes)
    local last = BackwardObjectScannerBox.scan_last_object_opt_window(insts, 200000, 8192)
    if last.is_Err() { return Result.Err("terminator scan failed: " + last.as_Err()) }
    return Result.Ok(last.as_Ok())
  }

  // Strict validate for function JSON (narrow): terminator required and references valid
  validate_function(func_json) {
    // collect block ids
    local bl = me.blocks(func_json)
    if bl.is_Err() { return bl }
    local meta = bl.as_Ok()
    local content = BoxHelpers.map_get(meta, "content")
    local ids = new MapBox()
    // iterate blocks
    local pos = 0
    loop(true) {
      local it = BlockIteratorBox.next(content, pos)
      if it.is_Err() { break }
      local m = it.as_Ok()
      local obj = BoxHelpers.map_get(m, "obj")
      pos = BoxHelpers.map_get(m, "next_pos")
      // parse id
      local key_id = "\"id\":"
      local p = obj.indexOf(key_id)
      if p < 0 { return Result.Err("block id missing") }
      p = p + key_id.length()
      // skip ws
      loop(p < obj.length()) { local ch = obj.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
      local digs = CommonImports.read_digits(obj, p)
      if digs == "" { return Result.Err("invalid block id") }
      ids.set(StringHelpers.int_to_str(StringHelpers.to_i64(digs)), 1)
      // require terminator
      if obj.indexOf("\"terminator\":{") < 0 { return Result.Err("terminator missing") }
    }
    // validate each block terminator references
    pos = 0
    loop(true) {
      local it = BlockIteratorBox.next(content, pos)
      if it.is_Err() { break }
      local m = it.as_Ok()
      pos = BoxHelpers.map_get(m, "next_pos")
      local obj = BoxHelpers.map_get(m, "obj")
      // extract terminator object
      local ts = obj.indexOf("\"terminator\":{")
      if ts < 0 { return Result.Err("terminator missing") }
      local te = ts + "\"terminator\":".length()
      // naive: use general locator to get terminator via instructions fallback if needed
      local term = me.terminator(obj)
      if term.is_Err() { return Result.Err("terminator parse failed") }
      local tj = term.as_Ok()
      // op tolerant
      local k = "\"op\""
      local p2 = tj.indexOf(k)
      if p2 < 0 { return Result.Err("terminator op not found") }
      p2 = p2 + k.length()
      loop(p2 < tj.length()) { local ch2 = tj.substring(p2,p2+1) if ch2 == ":" { p2 = p2 + 1 break } if ch2 == " " || ch2 == "\n" || ch2 == "\r" || ch2 == "\t" { p2 = p2 + 1 continue } return Result.Err("terminator op colon not found") }
      loop(p2 < tj.length()) { local ch3 = tj.substring(p2,p2+1) if ch3 == " " || ch3 == "\n" || ch3 == "\r" || ch3 == "\t" { p2 = p2 + 1 continue } break }
      if tj.substring(p2,p2+1) != "\"" { return Result.Err("terminator op quote not found") }
      local e2 = JsonCursorBox.index_of_from(tj, "\"", p2+1)
      if e2 < 0 { return Result.Err("terminator op end not found") }
      local op = tj.substring(p2+1, e2)
      if op == "jump" {
        local kt = "\"target\":"
        local pt = tj.indexOf(kt)
        if pt < 0 { return Result.Err("jump: target missing") }
        pt = pt + kt.length()
        loop(pt < tj.length()) {
          local ch = tj.substring(pt,pt+1)
          if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { pt = pt + 1 continue }
          break
        }
        local digs2 = StringHelpers.read_digits(tj, pt)
        if digs2 == "" { return Result.Err("jump: invalid target") }
        local key = StringHelpers.int_to_str(StringHelpers.to_i64(digs2))
        if ids.get(key) == null { return Result.Err("jump: unknown target") }
      } else { if op == "branch" {
        local k1 = "\"then_bb\":"
        local p3 = tj.indexOf(k1)
        if p3 < 0 { return Result.Err("branch: then_bb missing") }
        p3 = p3 + k1.length()
        loop(p3 < tj.length()) { local ch = tj.substring(p3,p3+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p3 = p3 + 1 continue } break }
        local d1 = StringHelpers.read_digits(tj, p3)
        if d1 == "" { return Result.Err("branch: invalid then_bb") }
        local k2 = "\"else_bb\":"
        local p4 = tj.indexOf(k2)
        if p4 < 0 { return Result.Err("branch: else_bb missing") }
        p4 = p4 + k2.length()
        loop(p4 < tj.length()) { local ch = tj.substring(p4,p4+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p4 = p4 + 1 continue } break }
        local d2 = StringHelpers.read_digits(tj, p4)
        if d2 == "" { return Result.Err("branch: invalid else_bb") }
        if ids.get(StringHelpers.int_to_str(StringHelpers.to_i64(d1))) == null { return Result.Err("branch: unknown then_bb") }
        if ids.get(StringHelpers.int_to_str(StringHelpers.to_i64(d2))) == null { return Result.Err("branch: unknown else_bb") }
      } else { if op == "ret" { /* ok */ } else { return Result.Err("terminator: unsupported op "" + op + """) } }
      }
    }
    return Result.Ok(0)
  }
}
