//! Method call handlers for MIR builder
//!
//! This module contains specialized handlers for different types of method calls,
//! following the Single Responsibility Principle.

use crate::ast::ASTNode;
use crate::mir::builder::builder_calls::CallTarget;
use crate::mir::builder::calls::function_lowering;
use crate::mir::builder::{MirBuilder, ValueId};
use crate::mir::{MirInstruction, MirType, TypeOpKind};

/// Me-call 専用のポリシー箱。
///
/// - 責務:
///   - me.method(...) を「インスタンス呼び出し」か「static メソッド呼び出し」か判定する。
///   - static box 文脈で実体のない receiver を生まないように、静的メソッド降下にフォールバックする。
struct MeCallPolicyBox;

impl MeCallPolicyBox {
    fn resolve_me_call(
        builder: &mut MirBuilder,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        // Instance box: prefer enclosing box method (lowered function) if存在
        let enclosing_cls: Option<String> = builder
            .scope_ctx
            .current_function
            .as_ref()
            .and_then(|f| f.signature.name.split('.').next().map(|s| s.to_string()));

        if let Some(cls) = enclosing_cls.as_ref() {
            let mut arg_values = Vec::with_capacity(arguments.len());
            for a in arguments {
                arg_values.push(builder.build_expression(a.clone())?);
            }
            let arity = arg_values.len();
            let fname = function_lowering::generate_method_function_name(cls, method, arity);
            if let Some(ref module) = builder.current_module {
                if let Some(func) = module.functions.get(&fname) {
                    // Decide whether this lowered function expects an implicit receiver.
                    // Instance methods: params[0] is Box(box_name)
                    // Static methods:   params[0] is non-Box or params.is_empty()
                    let params = &func.signature.params;
                    let is_instance_method =
                        !params.is_empty() && matches!(params[0], MirType::Box(_));

                    // Expected argument count from signature (including receiver for instance)
                    let expected_params = params.len();
                    let provided_static = arg_values.len();
                    let provided_instance = arg_values.len() + 1;

                    // Build call_args based on method kind
                    let call_args: Vec<ValueId> = if is_instance_method {
                        // Instance method: prepend 'me' receiver
                        if expected_params != provided_instance {
                            if std::env::var("NYASH_ME_CALL_ARITY_STRICT").ok().as_deref()
                                == Some("1")
                            {
                                return Err(format!(
                                    "[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)",
                                    fname, expected_params, provided_instance
                                ));
                            } else if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref()
                                == Some("1")
                            {
                                eprintln!(
                                    "[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)",
                                    fname, expected_params, provided_instance
                                );
                            }
                        }
                        let me_id = builder.build_me_expression()?;
                        let mut v = Vec::with_capacity(provided_instance);
                        v.push(me_id);
                        v.extend(arg_values.into_iter());
                        v
                    } else {
                        // Static method: no receiver
                        if expected_params != provided_static {
                            if std::env::var("NYASH_ME_CALL_ARITY_STRICT").ok().as_deref()
                                == Some("1")
                            {
                                return Err(format!(
                                    "[me-call] arity mismatch (static): {}: declared {} params, got {} args",
                                    fname, expected_params, provided_static
                                ));
                            } else if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref()
                                == Some("1")
                            {
                                eprintln!(
                                    "[me-call] arity mismatch (static): {}: declared {} params, got {} args",
                                    fname, expected_params, provided_static
                                );
                            }
                        }
                        arg_values
                    };

                    let dst = builder.next_value_id();
                    // Emit as unified global call to lowered function
                    builder.emit_unified_call(
                        Some(dst),
                        CallTarget::Global(fname.clone()),
                        call_args,
                    )?;
                    builder.annotate_call_result_from_func_name(dst, &fname);
                    return Ok(Some(dst));
                }
            }

            // Fallback: treat me.method(...) as a static method on the enclosing box.
            //  - 旧挙動では me を Box 値として Method 呼び出ししようとしていたが、
            //    static box 文脈では実体インスタンスが存在しないため UndefinedValue を生みやすい。
            //  - ここでは receiver を持たない Global call に揃え、FuncScannerBox などの
            //    static ヘルパー呼び出しを安全に扱う。
            let static_dst = builder.handle_static_method_call(cls, method, arguments)?;
            return Ok(Some(static_dst));
        }

        Ok(None)
    }
}

impl MirBuilder {
    /// Handle static method calls: BoxName.method(args)
    pub(super) fn handle_static_method_call(
        &mut self,
        box_name: &str,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<ValueId, String> {
        // Build argument values
        let mut arg_values = Vec::new();
        for arg in arguments {
            arg_values.push(self.build_expression(arg.clone())?);
        }

        // Compose lowered function name: BoxName.method/N
        let func_name = format!("{}.{}/{}", box_name, method, arg_values.len());
        let dst = self.next_value_id();

        if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
            eprintln!("[builder] static-call {}", func_name);
        }

        // Emit unified global call to the static-lowered function (module-local)
        self.emit_unified_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
        Ok(dst)
    }

    /// Handle TypeOp method calls: value.is("Type") and value.as("Type")
    pub(super) fn handle_typeop_method(
        &mut self,
        object_value: ValueId,
        method: &str,
        type_name: &str,
    ) -> Result<ValueId, String> {
        let mir_ty = Self::parse_type_name_to_mir(type_name);
        let dst = self.next_value_id();
        let op = if method == "is" {
            TypeOpKind::Check
        } else {
            TypeOpKind::Cast
        };

        self.emit_instruction(MirInstruction::TypeOp {
            dst,
            op,
            value: object_value,
            ty: mir_ty,
        })?;

        Ok(dst)
    }

    /// Check if this is a TypeOp method call
    #[allow(dead_code)]
    pub(super) fn is_typeop_method(method: &str, arguments: &[ASTNode]) -> Option<String> {
        if (method == "is" || method == "as") && arguments.len() == 1 {
            Self::extract_string_literal(&arguments[0])
        } else {
            None
        }
    }

    /// Handle me.method() calls within static box context
    pub(super) fn handle_me_method_call(
        &mut self,
        method: &str,
        arguments: &[ASTNode],
    ) -> Result<Option<ValueId>, String> {
        MeCallPolicyBox::resolve_me_call(self, method, arguments)
    }

    /// Handle standard Box/Plugin method calls (fallback)
    pub(super) fn handle_standard_method_call(
        &mut self,
        object_value: ValueId,
        method: String,
        arguments: &[ASTNode],
    ) -> Result<ValueId, String> {
        // Phase 285A0.1: WeakRef.weak_to_strong() → WeakLoad
        // SSOT: docs/reference/language/lifecycle.md:179 - weak_to_strong() returns Box | null
        if method == "weak_to_strong" && arguments.is_empty() {
            return self.emit_weak_load(object_value);
        }

        // Phase 285A0.1: upgrade() is deprecated - Fail-Fast
        if method == "upgrade" && arguments.is_empty() {
            return Err("WeakRef uses weak_to_strong(), not upgrade()".to_string());
        }

        // Build argument values
        let mut arg_values = Vec::new();
        for arg in arguments {
            arg_values.push(self.build_expression(arg.clone())?);
        }

        // Receiver class hintは emit_unified_call 側で起源/型から判断する（重複回避）
        // 統一経路: emit_unified_call に委譲（RouterPolicy と rewrite::* で安定化）
        let dst = self.next_value_id();
        self.emit_unified_call(
            Some(dst),
            CallTarget::Method {
                box_type: None,
                method,
                receiver: object_value,
            },
            arg_values,
        )?;
        Ok(dst)
    }
}
