/*!
 * VM BoxCall Dispatch
 *
 * Purpose: Method calls on Box values (dispatch by concrete box type)
 * Responsibilities: call_box_method_impl, BoxCall debug logging
 * Key APIs: call_box_method_impl, debug_log_boxcall
 * Typical Callers: vm_instructions::execute_boxcall / VM::call_unified_method
 */

use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, VoidBox};
use super::vm::{VM, VMError, VMValue};

impl VM {
    /// Call a method on a Box - simplified version of interpreter method dispatch
    pub(super) fn call_box_method_impl(&self, box_value: Box<dyn NyashBox>, method: &str, _args: Vec<Box<dyn NyashBox>>) -> Result<Box<dyn NyashBox>, VMError> {
        // PluginBoxV2: delegate to unified plugin host (BID-FFI v1)
        if let Some(pbox) = box_value.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
            if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") {
                eprintln!("[VM][BoxCall→PluginInvoke] {}.{} inst_id={}", pbox.box_type, method, pbox.inner.instance_id);
            }
            let host = crate::runtime::get_global_plugin_host();
            let host = host.read().unwrap();
            match host.invoke_instance_method(&pbox.box_type, method, pbox.inner.instance_id, &_args) {
                Ok(Some(val)) => { return Ok(val); }
                Ok(None) => { return Ok(Box::new(crate::box_trait::VoidBox::new())); }
                Err(e) => {
                    return Err(VMError::InvalidInstruction(format!("PluginInvoke failed via BoxCall: {}.{} ({})", pbox.box_type, method, e.message())));
                }
            }
        }

        // MathBox methods (minimal set used in 10.9)
        if let Some(math) = box_value.as_any().downcast_ref::<crate::boxes::math_box::MathBox>() {
            match method {
                "min" => {
                    if _args.len() >= 2 { return Ok(math.min(_args[0].clone_or_share(), _args[1].clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: min(a, b) requires 2 args")));
                }
                "max" => {
                    if _args.len() >= 2 { return Ok(math.max(_args[0].clone_or_share(), _args[1].clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: max(a, b) requires 2 args")));
                }
                "abs" => {
                    if let Some(v) = _args.get(0) { return Ok(math.abs(v.clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: abs(x) requires 1 arg")));
                }
                "sin" => {
                    if let Some(v) = _args.get(0) { return Ok(math.sin(v.clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: sin(x) requires 1 arg")));
                }
                "cos" => {
                    if let Some(v) = _args.get(0) { return Ok(math.cos(v.clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: cos(x) requires 1 arg")));
                }
                _ => { return Ok(Box::new(VoidBox::new())); }
            }
        }
        // ResultBox (NyashResultBox - new)
        if let Some(result_box) = box_value.as_any().downcast_ref::<crate::boxes::result::NyashResultBox>() {
            match method {
                "is_ok" | "isOk" => { return Ok(result_box.is_ok()); }
                "get_value" | "getValue" => { return Ok(result_box.get_value()); }
                "get_error" | "getError" => { return Ok(result_box.get_error()); }
                _ => return Ok(Box::new(VoidBox::new())),
            }
        }

        // Legacy box_trait::ResultBox path removed (migration complete)

        // Generic fallback: toString for any Box type
        if method == "toString" {
            return Ok(Box::new(StringBox::new(box_value.to_string_box().value)));
        }

        // JitConfigBox methods
        if let Some(jcb) = box_value.as_any().downcast_ref::<crate::boxes::jit_config_box::JitConfigBox>() {
            match method {
                "get" => {
                    if let Some(k) = _args.get(0) { return Ok(jcb.get_flag(&k.to_string_box().value).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string())))); }
                    return Ok(Box::new(StringBox::new("get(name) requires 1 arg")));
                }
                "set" => {
                    if _args.len() >= 2 {
                        let k = _args[0].to_string_box().value;
                        let v = _args[1].to_string_box().value;
                        let on = matches!(v.as_str(), "1" | "true" | "True" | "on" | "ON");
                        return Ok(jcb.set_flag(&k, on).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string()))));
                    }
                    return Ok(Box::new(StringBox::new("set(name, bool) requires 2 args")));
                }
                "getThreshold" => { return Ok(jcb.get_threshold()); }
                "setThreshold" => {
                    if let Some(v) = _args.get(0) { 
                        let iv = v.to_string_box().value.parse::<i64>().unwrap_or(0);
                        return Ok(jcb.set_threshold(iv).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string()))));
                    }
                    return Ok(Box::new(StringBox::new("setThreshold(n) requires 1 arg")));
                }
                "apply" => { return Ok(jcb.apply()); }
                "toJson" => { return Ok(jcb.to_json()); }
                "fromJson" => {
                    if let Some(s) = _args.get(0) { return Ok(jcb.from_json(&s.to_string_box().value).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string())))); }
                    return Ok(Box::new(StringBox::new("fromJson(json) requires 1 arg")));
                }
                "enable" => {
                    if let Some(k) = _args.get(0) { return Ok(jcb.set_flag(&k.to_string_box().value, true).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string())))); }
                    return Ok(Box::new(StringBox::new("enable(name) requires 1 arg")));
                }
                "disable" => {
                    if let Some(k) = _args.get(0) { return Ok(jcb.set_flag(&k.to_string_box().value, false).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string())))); }
                    return Ok(Box::new(StringBox::new("disable(name) requires 1 arg")));
                }
                "summary" => { return Ok(jcb.summary()); }
                _ => { return Ok(Box::new(VoidBox::new())); }
            }
        }

        // JitPolicyBox methods
        if let Some(jpb) = box_value.as_any().downcast_ref::<crate::boxes::jit_policy_box::JitPolicyBox>() {
            match method {
                "get" => {
                    if let Some(k) = _args.get(0) { return Ok(jpb.get_flag(&k.to_string_box().value)); }
                    return Ok(Box::new(StringBox::new("get(name) requires 1 arg")));
                }
                "set" => {
                    if _args.len() >= 2 {
                        let k = _args[0].to_string_box().value;
                        let v = _args[1].to_string_box().value;
                        let on = matches!(v.as_str(), "1" | "true" | "True" | "on" | "ON");
                        return Ok(jpb.set_flag(&k, on));
                    }
                    return Ok(Box::new(StringBox::new("set(name, bool) requires 2 args")));
                }
                "setWhitelistCsv" | "set_whitelist_csv" => {
                    if let Some(s) = _args.get(0) { return Ok(jpb.set_whitelist_csv(&s.to_string_box().value)); }
                    return Ok(Box::new(StringBox::new("setWhitelistCsv(csv) requires 1 arg")));
                }
                "addWhitelist" | "add_whitelist" => {
                    if let Some(s) = _args.get(0) { return Ok(jpb.add_whitelist(&s.to_string_box().value)); }
                    return Ok(Box::new(StringBox::new("addWhitelist(name) requires 1 arg")));
                }
                "clearWhitelist" | "clear_whitelist" => { return Ok(jpb.clear_whitelist()); }
                "enablePreset" | "enable_preset" => {
                    if let Some(s) = _args.get(0) { return Ok(jpb.enable_preset(&s.to_string_box().value)); }
                    return Ok(Box::new(StringBox::new("enablePreset(name) requires 1 arg")));
                }
                _ => { return Ok(Box::new(VoidBox::new())); }
            }
        }

        // JitStatsBox methods
        if let Some(jsb) = box_value.as_any().downcast_ref::<crate::boxes::jit_stats_box::JitStatsBox>() {
            match method {
                "toJson" => { return Ok(jsb.to_json()); }
                "top5" => {
                    if let Some(jm) = &self.jit_manager {
                        let v = jm.top_hits(5);
                        let arr: Vec<serde_json::Value> = v.into_iter().map(|(name, hits, compiled, handle)| {
                            serde_json::json!({
                                "name": name,
                                "hits": hits,
                                "compiled": compiled,
                                "handle": handle
                            })
                        }).collect();
                        let s = serde_json::to_string(&arr).unwrap_or_else(|_| "[]".to_string());
                        return Ok(Box::new(StringBox::new(s)));
                    }
                    return Ok(Box::new(StringBox::new("[]")));
                }
                "summary" => {
                    let cfg = crate::jit::config::current();
                    let caps = crate::jit::config::probe_capabilities();
                    let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
                    let b1_norm = crate::jit::rt::b1_norm_get();
                    let ret_b1 = crate::jit::rt::ret_bool_hint_get();
                    let mut payload = serde_json::json!({
                        "abi_mode": abi_mode,
                        "abi_b1_enabled": cfg.native_bool_abi,
                        "abi_b1_supported": caps.supports_b1_sig,
                        "b1_norm_count": b1_norm,
                        "ret_bool_hint_count": ret_b1,
                        "top5": [],
                        "perFunction": []
                    });
                    if let Some(jm) = &self.jit_manager {
                        let v = jm.top_hits(5);
                        let top5: Vec<serde_json::Value> = v.into_iter().map(|(name, hits, compiled, handle)| serde_json::json!({
                            "name": name, "hits": hits, "compiled": compiled, "handle": handle
                        })).collect();
                        let perf = jm.per_function_stats();
                        let per_arr: Vec<serde_json::Value> = perf.into_iter().map(|(name, phi_t, phi_b1, rb, hits, compiled, handle)| serde_json::json!({
                            "name": name, "phi_total": phi_t, "phi_b1": phi_b1, "ret_bool_hint": rb, "hits": hits, "compiled": compiled, "handle": handle
                        })).collect();
                        if let Some(obj) = payload.as_object_mut() { obj.insert("top5".to_string(), serde_json::Value::Array(top5)); obj.insert("perFunction".to_string(), serde_json::Value::Array(per_arr)); }
                    }
                    let s = serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string());
                    return Ok(Box::new(StringBox::new(s)));
                }
                "perFunction" | "per_function" => {
                    if let Some(jm) = &self.jit_manager {
                        let v = jm.per_function_stats();
                        let arr: Vec<serde_json::Value> = v.into_iter().map(|(name, phi_t, phi_b1, rb, hits, compiled, handle)| serde_json::json!({
                            "name": name,
                            "phi_total": phi_t,
                            "phi_b1": phi_b1,
                            "ret_bool_hint": rb,
                            "hits": hits,
                            "compiled": compiled,
                            "handle": handle,
                        })).collect();
                        let s = serde_json::to_string_pretty(&arr).unwrap_or_else(|_| "[]".to_string());
                        return Ok(Box::new(StringBox::new(s)));
                    }
                    return Ok(Box::new(StringBox::new("[]")));
                }
                _ => { return Ok(Box::new(VoidBox::new())); }
            }
        }

        // StringBox methods
        if let Some(string_box) = box_value.as_any().downcast_ref::<StringBox>() {
            match method {
                "length" | "len" => { return Ok(Box::new(IntegerBox::new(string_box.value.len() as i64))); }
                "toString" => { return Ok(Box::new(StringBox::new(string_box.value.clone()))); }
                _ => return Ok(Box::new(VoidBox::new())),
            }
        }

        // ArrayBox methods (minimal set)
        if let Some(array_box) = box_value.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
            match method {
                "push" => { if let Some(v) = _args.get(0) { return Ok(array_box.push(v.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: push(value) requires 1 arg"))); }
                "pop" => { return Ok(array_box.pop()); },
                "length" | "len" => { return Ok(array_box.length()); },
                "get" => { if let Some(i) = _args.get(0) { return Ok(array_box.get(i.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: get(index) requires 1 arg"))); }
                "set" => { if _args.len() >= 2 { return Ok(array_box.set(_args[0].clone_or_share(), _args[1].clone_or_share())); } return Ok(Box::new(StringBox::new("Error: set(index, value) requires 2 args"))); }
                "remove" => { if let Some(i) = _args.get(0) { return Ok(array_box.remove(i.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: remove(index) requires 1 arg"))); }
                "contains" => { if let Some(v) = _args.get(0) { return Ok(array_box.contains(v.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: contains(value) requires 1 arg"))); }
                "indexOf" => { if let Some(v) = _args.get(0) { return Ok(array_box.indexOf(v.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: indexOf(value) requires 1 arg"))); }
                "clear" => { return Ok(array_box.clear()); },
                "join" => { if let Some(sep) = _args.get(0) { return Ok(array_box.join(sep.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: join(sep) requires 1 arg"))); }
                "sort" => { return Ok(array_box.sort()); },
                "reverse" => { return Ok(array_box.reverse()); },
                "slice" => { if _args.len() >= 2 { return Ok(array_box.slice(_args[0].clone_or_share(), _args[1].clone_or_share())); } return Ok(Box::new(StringBox::new("Error: slice(start, end) requires 2 args"))); }
                _ => return Ok(Box::new(VoidBox::new())),
            }
        }

        // MapBox methods (minimal set)
        if let Some(map_box) = box_value.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
            match method {
                "set" => { if _args.len() >= 2 { return Ok(map_box.set(_args[0].clone_or_share(), _args[1].clone_or_share())); } return Ok(Box::new(StringBox::new("Error: set(key, value) requires 2 args"))); }
                "get" => { if let Some(k) = _args.get(0) { return Ok(map_box.get(k.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: get(key) requires 1 arg"))); }
                "has" => { if let Some(k) = _args.get(0) { return Ok(map_box.has(k.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: has(key) requires 1 arg"))); }
                "delete" | "remove" => { if let Some(k) = _args.get(0) { return Ok(map_box.delete(k.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: delete(key) requires 1 arg"))); }
                "keys" => { return Ok(map_box.keys()); },
                "values" => { return Ok(map_box.values()); },
                "size" => { return Ok(map_box.size()); },
                "clear" => { return Ok(map_box.clear()); },
                _ => return Ok(Box::new(VoidBox::new())),
            }
        }

        // TaskGroupBox methods (scaffold → instance内の所有Futureに対して実行)
        if box_value.as_any().downcast_ref::<crate::boxes::task_group_box::TaskGroupBox>().is_some() {
            let mut owned = box_value;
            if let Some(tg) = (&mut *owned).as_any_mut().downcast_mut::<crate::boxes::task_group_box::TaskGroupBox>() {
                match method {
                    "cancelAll" | "cancel_all" => { return Ok(tg.cancelAll()); }
                    "joinAll" | "join_all" => {
                        let ms = _args.get(0).map(|a| a.to_string_box().value.parse::<i64>().unwrap_or(2000));
                        return Ok(tg.joinAll(ms));
                    }
                    _ => { return Ok(Box::new(VoidBox::new())); }
                }
            }
            return Ok(Box::new(VoidBox::new()));
        }

        // P2PBox methods (minimal)
        if let Some(p2p) = box_value.as_any().downcast_ref::<crate::boxes::p2p_box::P2PBox>() {
            match method {
                "send" => {
                    if _args.len() >= 2 { return Ok(p2p.send(_args[0].clone_or_share(), _args[1].clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: send(to, intent) requires 2 args")));
                }
                "on" => {
                    if _args.len() >= 2 { return Ok(p2p.on(_args[0].clone_or_share(), _args[1].clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: on(intent, handler) requires 2 args")));
                }
                "onOnce" | "on_once" => {
                    if _args.len() >= 2 { return Ok(p2p.on_once(_args[0].clone_or_share(), _args[1].clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: onOnce(intent, handler) requires 2 args")));
                }
                "off" => {
                    if _args.len() >= 1 { return Ok(p2p.off(_args[0].clone_or_share())); }
                    return Ok(Box::new(StringBox::new("Error: off(intent) requires 1 arg")));
                }
                "getLastFrom" => { return Ok(p2p.get_last_from()); }
                "getLastIntentName" => { return Ok(p2p.get_last_intent_name()); }
                "debug_nodes" | "debugNodes" => { return Ok(p2p.debug_nodes()); }
                "getNodeId" | "getId" => { return Ok(p2p.get_node_id()); }
                "getTransportType" | "transport" => { return Ok(p2p.get_transport_type()); }
                "isReachable" => { if let Some(n) = _args.get(0) { return Ok(p2p.is_reachable(n.clone_or_share())); } return Ok(Box::new(BoolBox::new(false))); }
                _ => return Ok(Box::new(VoidBox::new())),
            }
        }

        // SocketBox methods (minimal + timeouts)
        if let Some(sock) = box_value.as_any().downcast_ref::<crate::boxes::socket_box::SocketBox>() {
            match method {
                "bind" => { if _args.len() >= 2 { return Ok(sock.bind(_args[0].clone_or_share(), _args[1].clone_or_share())); } return Ok(Box::new(StringBox::new("Error: bind(address, port) requires 2 args"))); }
                "listen" => { if let Some(b) = _args.get(0) { return Ok(sock.listen(b.clone_or_share())); } return Ok(Box::new(StringBox::new("Error: listen(backlog) requires 1 arg"))); }
                "accept" => { return Ok(sock.accept()); },
                "acceptTimeout" | "accept_timeout" => { if let Some(ms) = _args.get(0) { return Ok(sock.accept_timeout(ms.clone_or_share())); } return Ok(Box::new(crate::box_trait::VoidBox::new())); }
                "connect" => { if _args.len() >= 2 { return Ok(sock.connect(_args[0].clone_or_share(), _args[1].clone_or_share())); } return Ok(Box::new(StringBox::new("Error: connect(address, port) requires 2 args"))); }
                "read" => { return Ok(sock.read()); },
                "recvTimeout" | "recv_timeout" => { if let Some(ms) = _args.get(0) { return Ok(sock.recv_timeout(ms.clone_or_share())); } return Ok(Box::new(StringBox::new(""))); }
                "write" => { if let Some(d) = _args.get(0) { return Ok(sock.write(d.clone_or_share())); } return Ok(Box::new(crate::box_trait::BoolBox::new(false))); }
                "close" => { return Ok(sock.close()); },
                "isServer" | "is_server" => { return Ok(sock.is_server()); },
                "isConnected" | "is_connected" => { return Ok(sock.is_connected()); },
                _ => return Ok(Box::new(VoidBox::new())),
            }
        }

        // IntegerBox methods
        if let Some(integer_box) = box_value.as_any().downcast_ref::<IntegerBox>() { match method { "toString" => { return Ok(Box::new(StringBox::new(integer_box.value.to_string()))); }, "abs" => { return Ok(Box::new(IntegerBox::new(integer_box.value.abs()))); }, _ => return Ok(Box::new(VoidBox::new())), } }

        // BoolBox methods
        if let Some(bool_box) = box_value.as_any().downcast_ref::<BoolBox>() { match method { "toString" => { return Ok(Box::new(StringBox::new(bool_box.value.to_string()))); }, _ => return Ok(Box::new(VoidBox::new())), } }

        // Default: return void for any unrecognized box type or method
        Ok(Box::new(VoidBox::new()))
    }

    /// Debug helper for BoxCall tracing (enabled via NYASH_VM_DEBUG_BOXCALL=1)
    pub(super) fn debug_log_boxcall(&self, recv: &VMValue, method: &str, args: &[Box<dyn NyashBox>], stage: &str, result: Option<&VMValue>) {
        if std::env::var("NYASH_VM_DEBUG_BOXCALL").ok().as_deref() == Some("1") {
            let recv_ty = match recv {
                VMValue::BoxRef(arc) => arc.type_name().to_string(),
                VMValue::Integer(_) => "Integer".to_string(),
                VMValue::Float(_) => "Float".to_string(),
                VMValue::Bool(_) => "Bool".to_string(),
                VMValue::String(_) => "String".to_string(),
                VMValue::Future(_) => "Future".to_string(),
                VMValue::Void => "Void".to_string(),
            };
            let args_desc: Vec<String> = args.iter().map(|a| a.type_name().to_string()).collect();
            if let Some(res) = result {
                let res_ty = match res {
                    VMValue::BoxRef(arc) => format!("BoxRef({})", arc.type_name()),
                    VMValue::Integer(_) => "Integer".to_string(),
                    VMValue::Float(_) => "Float".to_string(),
                    VMValue::Bool(_) => "Bool".to_string(),
                    VMValue::String(_) => "String".to_string(),
                    VMValue::Future(_) => "Future".to_string(),
                    VMValue::Void => "Void".to_string(),
                };
                eprintln!("[VM-BOXCALL][{}] recv_ty={} method={} argc={} args={:?} => result_ty={}", stage, recv_ty, method, args.len(), args_desc, res_ty);
            } else {
                eprintln!("[VM-BOXCALL][{}] recv_ty={} method={} argc={} args={:?}", stage, recv_ty, method, args.len(), args_desc);
            }
        }
    }
}
