//! 🎯 箱理論: Call構築専用モジュール
//!
//! 責務: ASTからCall構築のみ
//! - build_function_call: 関数呼び出し構築
//! - build_method_call: メソッド呼び出し構築
//! - build_from_expression: from式構築

use super::super::{Effect, EffectMask, MirBuilder, MirInstruction, MirType, ValueId};
use super::special_handlers;
use super::CallTarget;
use crate::ast::{ASTNode, LiteralValue};
use crate::mir::TypeOpKind;

impl MirBuilder {
    // Build function call: name(args)
    pub fn build_function_call(
        &mut self,
        name: String,
        args: Vec<ASTNode>,
    ) -> Result<ValueId, String> {
        // Dev trace
        if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
            let cur_fun = self
                .scope_ctx
                .current_function
                .as_ref()
                .map(|f| f.signature.name.clone())
                .unwrap_or_else(|| "<none>".to_string());
            eprintln!(
                "[builder] function-call name={} static_ctx={} in_fn={}",
                name,
                self.comp_ctx.current_static_box.as_deref().unwrap_or(""),
                cur_fun
            );
        }

        // 0. Phase 285W-Syntax-0.1: Reject weak(...) function call syntax
        // SSOT: docs/reference/language/lifecycle.md - weak <expr> is the ONLY valid syntax
        if name == "weak" {
            eprintln!("[Phase285W-0.1] Rejecting weak(...) function call");
            return Err(format!(
                "Invalid syntax: weak(...). Use unary operator: weak <expr>\n\
                 Help: Change 'weak(obj)' to 'weak obj' (unary operator, no parentheses)\n\
                 SSOT: docs/reference/language/lifecycle.md"
            ));
        }

        // 1. TypeOp wiring: isType(value, "Type"), asType(value, "Type")
        if let Some(result) = self.try_build_typeop_function(&name, &args)? {
            return Ok(result);
        }

        // 2. Math function handling
        let raw_args = args.clone();
        if let Some(res) = self.try_handle_math_function(&name, raw_args) {
            return res;
        }

        // 3. Build argument values
        let arg_values = self.build_call_args(&args)?;

        // 4. Special-case: global str(x) → x.str() normalization
        if name == "str" && arg_values.len() == 1 {
            return self.build_str_normalization(arg_values[0]);
        }

        // 5. Determine call route (unified vs legacy)
        let use_unified = super::call_unified::is_unified_call_enabled()
            && (super::super::call_resolution::is_builtin_function(&name)
                || super::super::call_resolution::is_extern_function(&name));

        if !use_unified {
            self.build_legacy_function_call(name, arg_values)
        } else {
            self.build_unified_function_call(name, arg_values)
        }
    }

    // Build method call: object.method(arguments)
    pub fn build_method_call(
        &mut self,
        object: ASTNode,
        method: String,
        arguments: Vec<ASTNode>,
    ) -> Result<ValueId, String> {
        // Debug: Check recursion depth
        const MAX_METHOD_DEPTH: usize = 100;
        self.recursion_depth += 1;
        if self.recursion_depth > MAX_METHOD_DEPTH {
            eprintln!(
                "[FATAL] build_method_call recursion depth exceeded {}",
                MAX_METHOD_DEPTH
            );
            eprintln!("[FATAL] Current depth: {}", self.recursion_depth);
            eprintln!("[FATAL] Method: {}", method);
            return Err(format!(
                "build_method_call recursion depth exceeded: {}",
                self.recursion_depth
            ));
        }

        let result = self.build_method_call_impl(object, method, arguments);
        self.recursion_depth -= 1;
        result
    }

    fn build_method_call_impl(
        &mut self,
        object: ASTNode,
        method: String,
        arguments: Vec<ASTNode>,
    ) -> Result<ValueId, String> {
        self.trace_method_call_if_enabled(&object, &method);

        // 0. Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog へ直接 lowering
        if let Some(result) = self.try_build_mir_debug_method_call(&object, &method, &arguments)? {
            return Ok(result);
        }

        // Phase 288.1: REPL session variable bridge: __repl.get/set → ExternCall
        if let Some(result) = self.try_build_repl_method_call(&object, &method, &arguments)? {
            return Ok(result);
        }

        // 1. Static box method call: BoxName.method(args)
        if let Some(result) = self.try_build_static_receiver_method_call(&object, &method, &arguments)? {
            return Ok(result);
        }

        // 2. Handle env.* methods
        if let Some(res) = self.try_handle_env_method(&object, &method, &arguments) {
            return res;
        }

        // 3. Phase 269 P1.2: ReceiverNormalizeBox - MethodCall 共通入口 SSOT
        if let Some(result) = self.try_normalize_this_me_method_call(&object, &method, &arguments)? {
            return Ok(result);
        }

        // 4. Build object value
        let object_value = self.build_expression(object.clone())?;

        // Phase 287 P4: Debug object value after build_expression
        if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
            eprintln!("[P287-DEBUG] After build_expression: object_value={:?}", object_value);
        }

        // Debug trace for receiver
        self.trace_receiver_if_enabled(&object, object_value);

        // 5. Handle TypeOp methods: value.is("Type") / value.as("Type")
        if let Some(type_name) = special_handlers::is_typeop_method(&method, &arguments) {
            return self.handle_typeop_method(object_value, &method, &type_name);
        }

        // 6. Fallback: standard Box/Plugin method call
        self.handle_standard_method_call(object_value, method, &arguments)
    }

    fn trace_method_call_if_enabled(&self, object: &ASTNode, method: &str) {
        if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() != Some("1") {
            return;
        }
        let kind = match object {
            ASTNode::Variable { .. } => "Variable",
            ASTNode::FieldAccess { .. } => "FieldAccess",
            ASTNode::This { .. } => "This",
            ASTNode::Me { .. } => "Me",
            _ => "Other",
        };
        eprintln!("[builder] method-call object kind={} method={}", kind, method);
    }

    fn try_build_mir_debug_method_call(
        &mut self,
        object: &ASTNode,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        let ASTNode::Variable { name: obj_name, .. } = object else {
            return Ok(None);
        };
        if obj_name != "__mir__" {
            return Ok(None);
        }
        self.try_build_mir_debug_call(method, arguments)
    }

    /// Phase 288.1: REPL session variable bridge
    /// Transform __repl.get/set → ExternCall("__repl", "get/set", args)
    fn try_build_repl_method_call(
        &mut self,
        object: &ASTNode,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        let ASTNode::Variable { name: obj_name, .. } = object else {
            return Ok(None);
        };
        if obj_name != "__repl" {
            return Ok(None);
        }

        // Only handle get/set methods
        if method != "get" && method != "set" {
            return Err(format!("__repl.{} is not supported. Only __repl.get and __repl.set are allowed.", method));
        }

        // Build argument values
        let arg_values = self.build_call_args(arguments)?;

        // Emit ExternCall instruction
        let dst = self.next_value_id();
        self.emit_instruction(MirInstruction::ExternCall {
            dst: Some(dst),
            iface_name: "__repl".to_string(),
            method_name: method.to_string(),
            args: arg_values,
            effects: EffectMask::PURE, // get/set are pure from MIR perspective
        })?;

        Ok(Some(dst))
    }

    fn try_build_static_receiver_method_call(
        &mut self,
        object: &ASTNode,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        let ASTNode::Variable { name: obj_name, .. } = object else {
            return Ok(None);
        };

        // Phase 287 P4: Fix toString() method resolution bug
        // Guard: If this is a local variable, don't treat as static box name
        let is_local_var = self.variable_ctx.variable_map.contains_key(obj_name);

        if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
            eprintln!("[P287-DEBUG] try_build_static_receiver_method_call: obj_name={}, method={}, is_local_var={}", obj_name, method, is_local_var);
            eprintln!("[P287-DEBUG] variable_map keys: {:?}", self.variable_ctx.variable_map.keys().collect::<Vec<_>>());
        }

        if is_local_var {
            // This is a variable reference (primitive or box instance), not a static box name
            // Let it flow through to handle_standard_method_call (line 147 in build_method_call_impl)
            if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
                eprintln!("[P287-DEBUG] -> Returning None (local var, will use method call)");
            }
            return Ok(None);
        }

        // Only treat as static box method call if obj_name is NOT a local variable
        if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
            eprintln!("[P287-DEBUG] -> Calling try_build_static_method_call (not a local var)");
        }
        self.try_build_static_method_call(obj_name, method, arguments)
    }

    fn try_normalize_this_me_method_call(
        &mut self,
        object: &ASTNode,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        if !matches!(object, ASTNode::This { .. } | ASTNode::Me { .. }) {
            return Ok(None);
        }

        // Priority 1: static box → compile-time static call normalization
        if let Some(box_name) = self.comp_ctx.current_static_box.clone() {
            if std::env::var("NYASH_TRACE_NORMALIZE").is_ok() {
                eprintln!(
                    "[trace:normalize] this.{}() → {}.{}() (static call)",
                    method, box_name, method
                );
            }
            // this.method(args) → current_static_box.method/arity(args)
            return Ok(Some(self.handle_static_method_call(
                &box_name, method, arguments,
            )?));
        }

        // Instance method fallback (requires variable_map["me"])
        self.handle_me_method_call(method, arguments)
    }

    // Build from expression: from Parent.method(arguments)
    pub fn build_from_expression(
        &mut self,
        parent: String,
        method: String,
        arguments: Vec<ASTNode>,
    ) -> Result<ValueId, String> {
        let mut arg_values = Vec::new();
        for arg in arguments {
            arg_values.push(self.build_expression(arg)?);
        }
        let parent_value = crate::mir::builder::emission::constant::emit_string(self, parent);
        let result_id = self.next_value_id();
        self.emit_box_or_plugin_call(
            Some(result_id),
            parent_value,
            method,
            None,
            arg_values,
            EffectMask::READ.add(Effect::ReadHeap),
        )?;
        Ok(result_id)
    }

    // ========================================
    // Private helper methods (small functions)
    // ========================================

    /// Try build TypeOp function calls (isType, asType)
    fn try_build_typeop_function(
        &mut self,
        name: &str,
        args: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        if (name == "isType" || name == "asType") && args.len() == 2 {
            if let Some(type_name) = special_handlers::extract_string_literal(&args[1]) {
                let val = self.build_expression(args[0].clone())?;
                let ty = special_handlers::parse_type_name_to_mir(&type_name);
                let dst = self.next_value_id();
                let op = if name == "isType" {
                    TypeOpKind::Check
                } else {
                    TypeOpKind::Cast
                };
                self.emit_instruction(MirInstruction::TypeOp {
                    dst,
                    op,
                    value: val,
                    ty,
                })?;
                return Ok(Some(dst));
            }
        }
        Ok(None)
    }

    /// Try handle math.* function in function-style (sin/cos/abs/min/max)
    fn try_handle_math_function(
        &mut self,
        name: &str,
        raw_args: Vec<ASTNode>,
    ) -> Option<Result<ValueId, String>> {
        if !special_handlers::is_math_function(name) {
            return None;
        }
        // Build numeric args directly for math.* to preserve f64 typing
        let mut math_args: Vec<ValueId> = Vec::new();
        for a in raw_args.into_iter() {
            match a {
                ASTNode::New {
                    class, arguments, ..
                } if class == "FloatBox" && arguments.len() == 1 => {
                    match self.build_expression(arguments[0].clone()) {
                        v @ Ok(_) => math_args.push(v.unwrap()),
                        err @ Err(_) => return Some(err),
                    }
                }
                ASTNode::New {
                    class, arguments, ..
                } if class == "IntegerBox" && arguments.len() == 1 => {
                    let iv = match self.build_expression(arguments[0].clone()) {
                        Ok(v) => v,
                        Err(e) => return Some(Err(e)),
                    };
                    let fv = self.next_value_id();
                    if let Err(e) = self.emit_instruction(MirInstruction::TypeOp {
                        dst: fv,
                        op: TypeOpKind::Cast,
                        value: iv,
                        ty: MirType::Float,
                    }) {
                        return Some(Err(e));
                    }
                    math_args.push(fv);
                }
                ASTNode::Literal {
                    value: LiteralValue::Float(_),
                    ..
                } => match self.build_expression(a) {
                    v @ Ok(_) => math_args.push(v.unwrap()),
                    err @ Err(_) => return Some(err),
                },
                other => match self.build_expression(other) {
                    v @ Ok(_) => math_args.push(v.unwrap()),
                    err @ Err(_) => return Some(err),
                },
            }
        }
        // new MathBox()
        let math_recv = self.next_value_id();
        if let Err(e) = self.emit_constructor_call(math_recv, "MathBox".to_string(), vec![]) {
            return Some(Err(e));
        }
        self.type_ctx
            .value_origin_newbox
            .insert(math_recv, "MathBox".to_string());
        // birth()
        if let Err(e) = self.emit_method_call(None, math_recv, "birth".to_string(), vec![]) {
            return Some(Err(e));
        }
        // call method
        let dst = self.next_value_id();
        if let Err(e) = self.emit_method_call(Some(dst), math_recv, name.to_string(), math_args) {
            return Some(Err(e));
        }
        Some(Ok(dst))
    }

    /// Try handle env.* extern methods
    fn try_handle_env_method(
        &mut self,
        object: &ASTNode,
        method: &str,
        arguments: &Vec<ASTNode>,
    ) -> Option<Result<ValueId, String>> {
        let ASTNode::FieldAccess {
            object: env_obj,
            field: env_field,
            ..
        } = object
        else {
            return None;
        };
        if let ASTNode::Variable { name: env_name, .. } = env_obj.as_ref() {
            if env_name != "env" {
                return None;
            }
            // Build arguments once
            let mut arg_values = Vec::new();
            for arg in arguments {
                match self.build_expression(arg.clone()) {
                    Ok(v) => arg_values.push(v),
                    Err(e) => return Some(Err(e)),
                }
            }
            let iface = env_field.as_str();
            let m = method;
            let mut extern_call = |iface_name: &str,
                                   method_name: &str,
                                   effects: EffectMask,
                                   returns: bool|
             -> Result<ValueId, String> {
                let result_id = self.next_value_id();
                self.emit_instruction(MirInstruction::ExternCall {
                    dst: if returns { Some(result_id) } else { None },
                    iface_name: iface_name.to_string(),
                    method_name: method_name.to_string(),
                    args: arg_values.clone(),
                    effects,
                })?;
                if returns {
                    Ok(result_id)
                } else {
                    let void_id = crate::mir::builder::emission::constant::emit_void(self);
                    Ok(void_id)
                }
            };
            // Use the new module for env method spec
            if let Some((iface_name, method_name, effects, returns)) =
                super::extern_calls::get_env_method_spec(iface, m)
            {
                return Some(extern_call(&iface_name, &method_name, effects, returns));
            }
            return None;
        }
        None
    }

    /// Build call arguments from AST
    fn build_call_args(&mut self, args: &[ASTNode]) -> Result<Vec<ValueId>, String> {
        let mut arg_values = Vec::new();
        for a in args {
            arg_values.push(self.build_expression(a.clone())?);
        }
        Ok(arg_values)
    }

    /// Build str(x) normalization to x.str()
    fn build_str_normalization(&mut self, arg: ValueId) -> Result<ValueId, String> {
        let dst = self.next_value_id();
        // Use unified method emission; downstream rewrite will functionize as needed
        self.emit_method_call(Some(dst), arg, "str".to_string(), vec![])?;
        Ok(dst)
    }

    /// Build legacy function call
    fn build_legacy_function_call(
        &mut self,
        name: String,
        arg_values: Vec<ValueId>,
    ) -> Result<ValueId, String> {
        let dst = self.next_value_id();

        // === ChatGPT5 Pro Design: Type-safe function call resolution ===
        let callee = match self.resolve_call_target(&name) {
            Ok(c) => c,
            Err(_e) => {
                // Fallback: unique static method
                if let Some(result) = self.try_static_method_fallback(&name, &arg_values)? {
                    return Ok(result);
                }
                // Tail-based fallback (disabled by default)
                if let Some(result) = self.try_tail_based_fallback(&name, &arg_values)? {
                    return Ok(result);
                }
                return Err(format!(
                    "Unresolved function: '{}'. {}",
                    name,
                    super::super::call_resolution::suggest_resolution(&name)
                ));
            }
        };

        // Legacy compatibility: Create dummy func value for old systems
        let fun_val = crate::mir::builder::name_const::make_name_const_result(self, &name)?;

        // Emit new-style Call with type-safe callee
        self.emit_instruction(MirInstruction::Call {
            dst: Some(dst),
            func: fun_val,
            callee: Some(callee),
            args: arg_values,
            effects: EffectMask::READ.add(Effect::ReadHeap),
        })?;
        Ok(dst)
    }

    /// Build unified function call
    fn build_unified_function_call(
        &mut self,
        name: String,
        arg_values: Vec<ValueId>,
    ) -> Result<ValueId, String> {
        let dst = self.next_value_id();
        self.emit_unified_call(Some(dst), CallTarget::Global(name), arg_values)?;
        Ok(dst)
    }

    /// Try static method call: BoxName.method(args)
    fn try_build_static_method_call(
        &mut self,
        obj_name: &str,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        let is_local_var = self.variable_ctx.variable_map.contains_key(obj_name);

        // Debug trace
        if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
            let trace = crate::mir::builder::control_flow::joinir::trace::trace();
            trace.stderr_if(
                &format!(
                    "[DEBUG] try_build_static_method_call: obj_name={}, method={}",
                    obj_name, method
                ),
                true,
            );
            trace.stderr_if(&format!("[DEBUG]   is_local_var={}", is_local_var), true);
            if is_local_var {
                trace.stderr_if(
                    &format!(
                        "[DEBUG]   variable_map contains '{}' - treating as local variable, will use method call",
                        obj_name
                    ),
                    true,
                );
                trace.stderr_if(
                    &format!(
                        "[DEBUG]   variable_map keys: {:?}",
                        self.variable_ctx.variable_map.keys().collect::<Vec<_>>()
                    ),
                    true,
                );
            } else {
                trace.stderr_if(
                    &format!(
                        "[DEBUG]   '{}' not in variable_map - treating as static box, will use global call",
                        obj_name
                    ),
                    true,
                );
            }
        }

        // Phase 15.5: Treat unknown identifiers in receiver position as static type names
        if !is_local_var {
            let result = self.handle_static_method_call(obj_name, method, arguments)?;
            return Ok(Some(result));
        }
        Ok(None)
    }

    /// Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog への変換
    ///
    /// 構文:
    ///   __mir__.log("label", v1, v2, ...)
    ///   __mir__.mark("label")
    ///
    /// - 第一引数は String リテラル想定（それ以外はこのハンドラをスキップして通常の解決に回す）。
    /// - 戻り値は Void 定数の ValueId（式コンテキストでも型破綻しないようにするため）。
    fn try_build_mir_debug_call(
        &mut self,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        if method != "log" && method != "mark" {
            return Ok(None);
        }

        if arguments.is_empty() {
            return Err("__mir__.log/__mir__.mark requires at least a label argument".to_string());
        }

        // 第一引数は String リテラルのみ対応（それ以外は通常経路にフォールバック）
        let label = match &arguments[0] {
            ASTNode::Literal {
                value: LiteralValue::String(s),
                ..
            } => s.clone(),
            _ => {
                // ラベルがリテラルでない場合はこのハンドラをスキップし、通常の static box 解決に任せる
                return Ok(None);
            }
        };

        // 残りの引数を評価して ValueId を集める
        let mut values: Vec<ValueId> = Vec::new();
        if method == "log" {
            for arg in &arguments[1..] {
                values.push(self.build_expression(arg.clone())?);
            }
        }

        // MIR に DebugLog 命令を 1 つ挿入（意味論は NYASH_MIR_DEBUG_LOG=1 のときにだけ効く）
        self.emit_instruction(MirInstruction::DebugLog {
            message: label,
            values,
        })?;

        // 式コンテキスト用に Void 定数を返す（呼び出し元では通常使われない）
        let void_id = crate::mir::builder::emission::constant::emit_void(self);
        Ok(Some(void_id))
    }

    /// Try static method fallback (name+arity)
    fn try_static_method_fallback(
        &mut self,
        name: &str,
        arg_values: &[ValueId],
    ) -> Result<Option<ValueId>, String> {
        if let Some(cands) = self.comp_ctx.static_method_index.get(name) {
            let mut matches: Vec<(String, usize)> = cands
                .iter()
                .cloned()
                .filter(|(_, ar)| *ar == arg_values.len())
                .collect();
            if matches.len() == 1 {
                let (bx, _arity) = matches.remove(0);
                let dst = self.next_value_id();
                let func_name = format!("{}.{}{}", bx, name, format!("/{}", arg_values.len()));
                // Emit unified global call to the lowered static method function
                self.emit_unified_call(
                    Some(dst),
                    CallTarget::Global(func_name),
                    arg_values.to_vec(),
                )?;
                return Ok(Some(dst));
            }
        }
        Ok(None)
    }

    /// Try tail-based fallback (disabled by default)
    fn try_tail_based_fallback(
        &mut self,
        name: &str,
        arg_values: &[ValueId],
    ) -> Result<Option<ValueId>, String> {
        if std::env::var("NYASH_BUILDER_TAIL_RESOLVE").ok().as_deref() == Some("1") {
            if let Some(ref module) = self.current_module {
                let tail = format!(".{}{}", name, format!("/{}", arg_values.len()));
                let mut cands: Vec<String> = module
                    .functions
                    .keys()
                    .filter(|k| k.ends_with(&tail))
                    .cloned()
                    .collect();
                if cands.len() == 1 {
                    let func_name = cands.remove(0);
                    let dst = self.next_value_id();
                    self.emit_legacy_call(
                        Some(dst),
                        CallTarget::Global(func_name),
                        arg_values.to_vec(),
                    )?;
                    return Ok(Some(dst));
                }
            }
        }
        Ok(None)
    }

    /// Debug trace for receiver (if enabled)
    fn trace_receiver_if_enabled(&self, object: &ASTNode, object_value: ValueId) {
        if std::env::var("NYASH_DEBUG_PARAM_RECEIVER").ok().as_deref() == Some("1") {
            if let ASTNode::Variable { name, .. } = object {
                let trace = crate::mir::builder::control_flow::joinir::trace::trace();
                trace.stderr_if(
                    &format!(
                        "[DEBUG/param-recv] build_method_call receiver '{}' → ValueId({})",
                        name, object_value.0
                    ),
                    true,
                );
                if let Some(origin) = self.type_ctx.value_origin_newbox.get(&object_value) {
                    trace.stderr_if(&format!("[DEBUG/param-recv]   origin: {}", origin), true);
                }
                if let Some(&mapped_id) = self.variable_ctx.variable_map.get(name) {
                    trace.stderr_if(
                        &format!(
                            "[DEBUG/param-recv]   variable_map['{}'] = ValueId({})",
                            name, mapped_id.0
                        ),
                        true,
                    );
                    if mapped_id != object_value {
                        trace.stderr_if(
                            "[DEBUG/param-recv]   ⚠️ MISMATCH! build_expression returned different ValueId!",
                            true,
                        );
                    }
                } else {
                    trace.stderr_if(
                        &format!(
                            "[DEBUG/param-recv]   ⚠️ '{}' NOT FOUND in variable_map!",
                            name
                        ),
                        true,
                    );
                }
                trace.stderr_if(
                    &format!("[DEBUG/param-recv]   current_block: {:?}", self.current_block),
                    true,
                );
            }
        }
    }
}
