"""
Box method call lowering for MIR Call instruction.

Handles lowering of box method calls (BoxCall) to LLVM IR.
Implements the "Everything is Box" philosophy with unified method dispatch.
"""

from typing import Dict, Any, Optional
from llvmlite import ir
import os


def lower_method_call(builder, module, box_name, method, receiver, args, dst_vid, vmap, resolver, owner):
    """
    Lower box method call - TRUE UNIFIED IMPLEMENTATION

    Args:
        builder: LLVM IR builder
        module: LLVM Module
        box_name: Name of the box type (e.g., "ConsoleBox", "StringBox")
        method: Method name to call
        receiver: Value ID of the receiver object
        args: List of argument value IDs
        dst_vid: Destination value ID (register)
        vmap: Value mapping dict
        resolver: Value resolver instance
        owner: NyashLLVMBuilder instance

    Effects:
        - Inserts automatic safepoint
        - Emits specialized LLVM calls for known methods
        - Falls back to generic plugin invocation for unknown methods
        - Stores result in vmap[dst_vid]
        - Marks string-producing methods for type tracking
    """
    from instructions.safepoint import insert_automatic_safepoint

    i64 = ir.IntType(64)
    i8 = ir.IntType(8)
    i8p = i8.as_pointer()

    # Insert automatic safepoint
    if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
        insert_automatic_safepoint(builder, module, "boxcall")

    # Helper to declare function
    def _declare(name: str, ret, args_types):
        for f in module.functions:
            if f.name == name:
                return f
        fnty = ir.FunctionType(ret, args_types)
        return ir.Function(module, fnty, name=name)

    # Helper to ensure i64 handle
    def _ensure_handle(v):
        if isinstance(v.type, ir.IntType) and v.type.width == 64:
            return v
        if v.type.is_pointer:
            callee = _declare("nyash.box.from_i8_string", i64, [i8p])
            return builder.call(callee, [v], name="unified_str_ptr2h")
        if isinstance(v.type, ir.IntType):
            return builder.zext(v, i64) if v.type.width < 64 else builder.trunc(v, i64)
        return v

    # Resolve receiver and arguments
    def _resolve_arg(vid: int):
        # Prefer same-block SSA (including function params) from vmap.
        try:
            v = vmap.get(vid)
            if v is not None:
                return v
        except Exception:
            pass
        if resolver and hasattr(resolver, 'resolve_i64'):
            try:
                return resolver.resolve_i64(vid, builder.block, owner.preds,
                                          owner.block_end_values, vmap, owner.bb_map)
            except:
                pass
        return None

    recv_val = _resolve_arg(receiver)
    if recv_val is None:
        recv_val = ir.Constant(i64, 0)
    recv_h = _ensure_handle(recv_val)

    # TRUE UNIFIED METHOD DISPATCH - Everything is Box philosophy
    if method in ["length", "len"]:
        callee = _declare("nyash.any.length_h", i64, [i64])
        result = builder.call(callee, [recv_h], name="unified_length")

    elif method == "size":
        callee = _declare("nyash.any.length_h", i64, [i64])
        result = builder.call(callee, [recv_h], name="unified_size")

    elif method == "substring":
        if len(args) >= 2:
            s = _resolve_arg(args[0]) or ir.Constant(i64, 0)
            e = _resolve_arg(args[1]) or ir.Constant(i64, 0)
            callee = _declare("nyash.string.substring_hii", i64, [i64, i64, i64])
            result = builder.call(callee, [recv_h, s, e], name="unified_substring")
        else:
            result = recv_h

    elif method == "lastIndexOf":
        if args:
            needle = _resolve_arg(args[0]) or ir.Constant(i64, 0)
            needle_h = _ensure_handle(needle)
            callee = _declare("nyash.string.lastIndexOf_hh", i64, [i64, i64])
            result = builder.call(callee, [recv_h, needle_h], name="unified_lastIndexOf")
        else:
            result = ir.Constant(i64, -1)

    elif method == "get":
        if args:
            k = _resolve_arg(args[0]) or ir.Constant(i64, 0)
            callee = _declare("nyash.map.get_hh", i64, [i64, i64])
            result = builder.call(callee, [recv_h, k], name="unified_map_get")
        else:
            result = ir.Constant(i64, 0)

    elif method == "push":
        if args:
            v = _resolve_arg(args[0]) or ir.Constant(i64, 0)
            callee = _declare("nyash.array.push_h", i64, [i64, i64])
            result = builder.call(callee, [recv_h, v], name="unified_array_push")
        else:
            result = recv_h

    elif method == "set":
        if len(args) >= 2:
            k = _resolve_arg(args[0]) or ir.Constant(i64, 0)
            v = _resolve_arg(args[1]) or ir.Constant(i64, 0)
            callee = _declare("nyash.map.set_hh", i64, [i64, i64, i64])
            result = builder.call(callee, [recv_h, k, v], name="unified_map_set")
        else:
            result = recv_h

    elif method == "has":
        if args:
            k = _resolve_arg(args[0]) or ir.Constant(i64, 0)
            callee = _declare("nyash.map.has_hh", i64, [i64, i64])
            result = builder.call(callee, [recv_h, k], name="unified_map_has")
        else:
            result = ir.Constant(i64, 0)

    elif method == "log":
        if args:
            arg0 = _resolve_arg(args[0]) or ir.Constant(i64, 0)
            if isinstance(arg0.type, ir.IntType) and arg0.type.width == 64:
                bridge = _declare("nyash.string.to_i8p_h", i8p, [i64])
                p = builder.call(bridge, [arg0], name="unified_str_h2p")
                callee = _declare("nyash.console.log", i64, [i8p])
                result = builder.call(callee, [p], name="unified_console_log")
            else:
                callee = _declare("nyash.console.log", i64, [i8p])
                result = builder.call(callee, [arg0], name="unified_console_log")
        else:
            result = ir.Constant(i64, 0)

    else:
        # Generic plugin method invocation
        method_str = method.encode('utf-8') + b'\0'
        method_gname = f"unified_method_{method}"
        if method_gname in module.globals:
            method_global = module.get_global(method_gname)
        else:
            method_global = ir.GlobalVariable(
                module, ir.ArrayType(i8, len(method_str)), name=method_gname
            )
            method_global.initializer = ir.Constant(
                ir.ArrayType(i8, len(method_str)), bytearray(method_str)
            )
            method_global.global_constant = True
        mptr = builder.gep(method_global, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])

        argc = ir.Constant(i64, len(args))
        a1 = _resolve_arg(args[0]) if args else ir.Constant(i64, 0)
        a2 = _resolve_arg(args[1]) if len(args) > 1 else ir.Constant(i64, 0)

        callee = _declare("nyash.plugin.invoke_by_name_i64", i64, [i64, i8p, i64, i64, i64])
        result = builder.call(callee, [recv_h, mptr, argc, a1, a2], name="unified_plugin_invoke")

    # Store result
    if dst_vid is not None:
        vmap[dst_vid] = result
        # Mark string-producing methods
        if resolver and hasattr(resolver, 'mark_string'):
            if method in ['substring', 'esc_json', 'node_json', 'dirname', 'join', 'read_all', 'toJson']:
                resolver.mark_string(dst_vid)
