// BoxCallHandlerBox - Handle boxcall instruction (Box method calls)
// Single Responsibility: Dispatch dynamic Box method calls

using selfhost.vm.boxes.result_box as Result
using selfhost.shared.common.string_ops as StringOps
using selfhost.shared.common.string_helpers as StringHelpers
using selfhost.vm.hakorune-vm.json_field_extractor as JsonFieldExtractor
using selfhost.vm.hakorune-vm.inst_field_extractor as InstFieldExtractor
using selfhost.vm.hakorune-vm.args_extractor as ArgsExtractorBox
using selfhost.vm.hakorune-vm.args_guard as ArgsGuardBox
using selfhost.vm.hakorune-vm.value_manager as ValueManagerBox
using selfhost.vm.hakorune-vm.receiver_guard as ReceiverGuardBox

static box BoxCallHandlerBox {
  // Handle boxcall instruction
  // inst_json: {"op":"boxcall","box":X,"method":"name","args":[...],"dst":Y}
  // regs: register MapBox
  // Returns: Result.Ok(0) or Result.Err(message)
  handle(inst_json, regs) {
    // Extract box (receiver) ValueId
    local box_id = JsonFieldExtractor.extract_int(inst_json, "box")
    if box_id == null {
      return Result.Err("boxcall: box field not found")
    }

    // Extract method name
    local method_name = JsonFieldExtractor.extract_string(inst_json, "method")
    if method_name == null {
      return Result.Err("boxcall: method field not found")
    }

    // Extract arguments (construct fake mir_call JSON for ArgsExtractorBox)
    // We need to extract args array from inst_json
    local args_array = me._extract_args(inst_json, regs)

    // Special-case: methodRef — tolerate missing/empty arity by defaulting to 0 and return early
    if method_name == "methodRef" {
      local name = args_array.get(0)
      local arity = args_array.get(1)
      if arity == null { arity = 0 }
      local dst_reg = JsonFieldExtractor.extract_int(inst_json, "dst")
      // Load receiver early for direct dispatch
      local receiver = ValueManagerBox.get(regs, box_id)
      if receiver == null {
        return Result.Err("boxcall: receiver v%" + StringHelpers.int_to_str(box_id) + " is unset (method=methodRef)")
      }
      local result_val = receiver.methodRef(name, arity)
      if dst_reg != null { ValueManagerBox.set(regs, dst_reg, result_val) }
      return Result.Ok(0)
    }


    // Guard arguments (no nulls)
    local _g = ArgsGuardBox.ensure_no_nulls(args_array, method_name + "/" + args_array.length())
    if _g.is_Err() { return _g }

    // Extract destination register
    local dst_reg = JsonFieldExtractor.extract_int(inst_json, "dst")

    // Load receiver object
    local receiver = ValueManagerBox.get(regs, box_id)
    if receiver == null {
      return Result.Err("boxcall: receiver v%" + StringHelpers.int_to_str(box_id) + " is unset (method=" + method_name + ")")
    }

    // Prepare method signature for dispatch
    local arg_count = args_array.length()
    local method_sig = method_name + "/" + arg_count

    // Known methods dispatch table
    local result_val = null

    // StringBox methods (using Rust VM available methods)
    if method_sig == "upper/0" {
      result_val = receiver.to_upper()
    } else if method_sig == "lower/0" {
      result_val = receiver.to_lower()
    } else if method_sig == "size/0" {
      result_val = receiver.length()
    } else if method_sig == "length/0" {
      result_val = receiver.length()
    } else if method_sig == "isEmpty/0" {
      result_val = receiver.isEmpty()
    } else if method_sig == "substring/1" {
      result_val = receiver.substring(args_array.get(0))
    } else if method_sig == "substring/2" {
      result_val = receiver.substring(args_array.get(0), args_array.get(1))
    } else if method_sig == "charAt/1" {
      result_val = receiver.charAt(args_array.get(0))
    } else if method_sig == "indexOf/1" {
      result_val = receiver.indexOf(args_array.get(0))
    } else if method_sig == "indexOf/2" {
      result_val = receiver.indexOf(args_array.get(0), args_array.get(1))
    }

    // ArrayBox methods
    else if method_sig == "push/1" {
      result_val = receiver.push(args_array.get(0))
    } else if method_sig == "get/1" {
      result_val = receiver.get(args_array.get(0))
    } else if method_sig == "set/2" {
      result_val = receiver.set(args_array.get(0), args_array.get(1))
    } else if method_sig == "length/0" {
      result_val = receiver.length()
    } else if method_sig == "size/0" {
      result_val = receiver.length()
    } else if method_sig == "isEmpty/0" {
      result_val = receiver.isEmpty()
    }

    // MapBox methods
    else if method_sig == "get/1" {
      result_val = receiver.get(args_array.get(0))
    } else if method_sig == "set/2" {
      result_val = receiver.set(args_array.get(0), args_array.get(1))
    } else if method_sig == "has/1" {
      result_val = receiver.has(args_array.get(0))
    } else if method_sig == "size/0" {
      result_val = receiver.length()
    } else if method_sig == "isEmpty/0" {
      result_val = receiver.isEmpty()
    } else if method_sig == "delete/1" {
      result_val = receiver.delete(args_array.get(0))
    } else if method_sig == "keys/0" {
      result_val = receiver.keys()
    } else if method_sig == "values/0" {
      result_val = receiver.values()
    } else if method_sig == "methodRef/2" {
      // slot 113: Array.methodRef(method_name, arity) -> CallableBox
      result_val = receiver.methodRef(args_array.get(0), args_array.get(1))
    } else if method_sig == "call/1" {
      // slot 501: CallableBox.call(args_array) -> any
      // Flatten: pass args_array.get(0) (the actual args ArrayBox) to CallableBox.call
      // CallableBox.call will flatten the ArrayBox into individual arguments
      result_val = receiver.call(args_array.get(0))
    } else if method_sig == "arity/0" {
      // slot 500: CallableBox.arity() -> Integer
      result_val = receiver.arity()
    } else if method_sig == "call/2" {
      // slot 210: Map.call(key, args_array) -> any
      result_val = receiver.call(args_array.get(0), args_array.get(1))
    } else if method_sig == "callAsync/2" {
      // slot 211: Map.callAsync(key, args_array) -> FutureBox
      result_val = receiver.callAsync(args_array.get(0), args_array.get(1))
    } else if method_sig == "callAsync/1" {
      // slot 502: CallableBox.callAsync(args_array) -> FutureBox
      // Flatten: pass args_array.get(0) (the actual args ArrayBox) to CallableBox.callAsync
      // CallableBox.callAsync will flatten the ArrayBox into individual arguments
      result_val = receiver.callAsync(args_array.get(0))
    }

    // Fallback: unknown method
    // Note: Method resolution depends on Rust VM's plugin switching mechanism
    // (NYASH_VM_DISABLE_STRING_HANDLERS, NYASH_USE_PLUGIN_BUILTINS, etc.)
    // Future: Add Selfhost VM's own plugin resolution here
    else {
      return Result.Err("boxcall: unknown method: " + method_sig)
    }

    // Store result in destination register
    if dst_reg != null {
      ValueManagerBox.set(regs, dst_reg, result_val)
    }

    return Result.Ok(0)
  }

  // Extract args array from boxcall inst_json
  // inst_json: {"op":"boxcall","args":[...],...}
  // Returns: ArrayBox of argument values
  _extract_args(inst_json, regs) {
    // Find "args" field
    local args_key = "\"args\":["
    local args_start = inst_json.indexOf(args_key)
    if args_start < 0 {
      // No args field, return empty array
      return new ArrayBox()
    }
    args_start = args_start + args_key.length()

    // Find array end
    local args_end = StringOps.index_of_from(inst_json, "]", args_start)
    if args_end < 0 {
      return new ArrayBox()
    }

    // Extract args array content
    local args_content = inst_json.substring(args_start, args_end)

    // Create fake mir_call JSON for ArgsExtractorBox
    local fake_mir_call = "{\"args\":[" + args_content + "]}"

    // Use ArgsExtractorBox to parse and load args
    return ArgsExtractorBox.extract_and_load(fake_mir_call, regs)
  }
}
