use serde_json::json;

use crate::mir::definitions::Callee;
use crate::mir::{EffectMask, ValueId};

use super::super::helpers::emit_unified_mir_call;

pub(crate) fn emit_call(
    dst: &Option<ValueId>,
    func: &ValueId,
    callee: Option<&Callee>,
    args: &[ValueId],
    effects: &EffectMask,
) -> Option<serde_json::Value> {
    // Phase 15.5: Unified Call support with environment variable control
    let use_unified = match std::env::var("NYASH_MIR_UNIFIED_CALL")
        .ok()
        .as_deref()
        .map(|s| s.to_ascii_lowercase())
    {
        Some(s) if s == "0" || s == "false" || s == "off" => false,
        _ => true,
    };

    if use_unified && callee.is_some() {
        // v1: Unified mir_call format
        let effects_str: Vec<&str> = if effects.is_io() { vec!["IO"] } else { vec![] };
        let args_u32: Vec<u32> = args.iter().map(|v| v.as_u32()).collect();
        let unified_call = emit_unified_mir_call(
            dst.map(|v| v.as_u32()),
            callee.unwrap(),
            &args_u32,
            &effects_str,
        );
        Some(unified_call)
    } else if !use_unified && callee.is_some() {
        // v0: When unified is OFF but callee exists, emit proper v0 format
        match callee.unwrap() {
            Callee::Method {
                method, receiver, ..
            } => {
                // Emit as boxcall for compatibility
                let box_val = receiver.unwrap_or(*func);
                let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
                let mut obj = json!({
                    "op":"boxcall",
                    "box": box_val.as_u32(),
                    "method": method,
                    "args": args_a,
                    "dst": dst.map(|d| d.as_u32())
                });
                // Add dst_type hints for known methods
                let m = method.as_str();
                let dst_ty = if m == "substring"
                    || m == "dirname"
                    || m == "join"
                    || m == "read_all"
                    || m == "read"
                {
                    Some(json!({"kind":"handle","box_type":"StringBox"}))
                } else if m == "length" || m == "lastIndexOf" {
                    Some(json!("i64"))
                } else {
                    None
                };
                if let Some(t) = dst_ty {
                    obj["dst_type"] = t;
                }
                Some(obj)
            }
            _ => {
                // Other callee types: emit generic call
                let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
                Some(
                    json!({"op":"call","func": func.as_u32(), "args": args_a, "dst": dst.map(|d| d.as_u32())}),
                )
            }
        }
    } else {
        // v0: Legacy call format (no callee info)
        let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
        Some(json!({"op":"call","func": func.as_u32(), "args": args_a, "dst": dst.map(|d| d.as_u32())}))
    }
}

pub(crate) fn emit_extern_call(
    dst: &Option<ValueId>,
    iface_name: &str,
    method_name: &str,
    args: &[ValueId],
) -> serde_json::Value {
    let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
    let func_name = if iface_name == "env.console" {
        format!("nyash.console.{}", method_name)
    } else {
        format!("{}.{}", iface_name, method_name)
    };
    let mut obj = json!({
        "op": "externcall",
        "func": func_name,
        "args": args_a,
        "dst": dst.map(|d| d.as_u32()),
    });
    // Minimal dst_type hints for known externs
    if iface_name == "env.console" {
        // console.* returns i64 status (ignored by user code)
        if dst.is_some() {
            obj["dst_type"] = json!("i64");
        }
    }
    obj
}

pub(crate) fn emit_box_call(
    dst: &Option<ValueId>,
    box_val: &ValueId,
    method: &str,
    method_id: Option<&u16>,
    args: &[ValueId],
) -> serde_json::Value {
    let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
    // Minimal dst_type hints
    let mut obj = json!({
        "op":"boxcall","box": box_val.as_u32(), "method": method, "args": args_a, "dst": dst.map(|d| d.as_u32())
    });
    // Phase 287 P4: Include method_id for universal slot tracking (toString[#0])
    if let Some(mid) = method_id {
        obj["method_id"] = json!(mid);
    }
    let m = method;
    let dst_ty = if m == "substring"
        || m == "dirname"
        || m == "join"
        || m == "read_all"
        || m == "read"
    {
        Some(json!({"kind":"handle","box_type":"StringBox"}))
    } else if m == "length" || m == "lastIndexOf" {
        Some(json!("i64"))
    } else {
        None
    };
    if let Some(t) = dst_ty {
        obj["dst_type"] = t;
    }
    obj
}

pub(crate) fn emit_new_box(
    dst: &ValueId,
    box_type: &str,
    args: &[ValueId],
) -> serde_json::Value {
    let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
    json!({"op":"newbox","type": box_type, "args": args_a, "dst": dst.as_u32()})
}
