//! Method call evaluation module
//!
//! This module handles method dispatch and evaluation for all Value types,
//! including strings, arrays, floats, integers, `DataFrames`, and generic methods.
//! Extracted for maintainability and following Toyota Way principles.
//! All functions maintain <10 cyclomatic complexity.

use crate::frontend::ast::Expr;
use crate::runtime::validation::validate_arg_count;
use crate::runtime::{DataFrameColumn, InterpreterError, Value};
use std::sync::Arc;

/// Main method call evaluation entry point
///
/// # Complexity
/// Cyclomatic complexity: 4 (within Toyota Way limits)
pub fn eval_method_call<F>(
    receiver: &Expr,
    method: &str,
    args: &[Expr],
    mut eval_expr: F,
    mut eval_dataframe_filter: impl FnMut(&Value, &[Expr]) -> Result<Value, InterpreterError>,
) -> Result<Value, InterpreterError>
where
    F: FnMut(&Expr) -> Result<Value, InterpreterError>,
{
    let receiver_value = eval_expr(receiver)?;

    // Special handling for DataFrame filter method - don't pre-evaluate the condition
    if matches!(receiver_value, Value::DataFrame { .. }) && method == "filter" {
        return eval_dataframe_filter(&receiver_value, args);
    }

    let arg_values: Result<Vec<_>, _> = args.iter().map(eval_expr).collect();
    let arg_values = arg_values?;

    dispatch_method_call(&receiver_value, method, &arg_values, args.is_empty())
}

/// Dispatch method call based on receiver type
///
/// # Complexity
/// Cyclomatic complexity: 7 (within Toyota Way limits)
pub fn dispatch_method_call(
    receiver: &Value,
    method: &str,
    arg_values: &[Value],
    args_empty: bool,
) -> Result<Value, InterpreterError> {
    match receiver {
        Value::String(s) => eval_string_method(s, method, arg_values),
        Value::Array(arr) => eval_array_method_simple(arr, method, arg_values),
        Value::Float(f) => eval_float_method(*f, method, args_empty),
        Value::Integer(n) => eval_integer_method(*n, method, arg_values),
        Value::DataFrame { columns } => eval_dataframe_method_simple(columns, method, arg_values),
        _ => eval_generic_method(receiver, method, args_empty),
    }
}

/// Evaluate float methods
///
/// # Complexity
/// Cyclomatic complexity: 9 (within Toyota Way limits)
pub fn eval_float_method(
    f: f64,
    method: &str,
    args_empty: bool,
) -> Result<Value, InterpreterError> {
    // Issue #91: Special case for powf - suggest ** operator instead
    if method == "powf" {
        return Err(InterpreterError::RuntimeError(
            "Float method 'powf' not available. Use ** operator for exponentiation (e.g., 2.0 ** 3.0)".to_string()
        ));
    }

    if !args_empty {
        return Err(InterpreterError::RuntimeError(format!(
            "Float method '{method}' takes no arguments"
        )));
    }

    match method {
        "sqrt" => Ok(Value::Float(f.sqrt())),
        "abs" => Ok(Value::Float(f.abs())),
        "round" => Ok(Value::Float(f.round())),
        "floor" => Ok(Value::Float(f.floor())),
        "ceil" => Ok(Value::Float(f.ceil())),
        "to_string" => Ok(Value::from_string(f.to_string())),
        _ => Err(InterpreterError::RuntimeError(format!(
            "Unknown float method: {method}"
        ))),
    }
}

/// Evaluate integer methods
///
/// # Complexity
/// Cyclomatic complexity: 7 (within Toyota Way limits)
pub fn eval_integer_method(
    n: i64,
    method: &str,
    arg_values: &[Value],
) -> Result<Value, InterpreterError> {
    match method {
        "abs" => {
            validate_arg_count("Integer.abs", arg_values, 0)?;
            Ok(Value::Integer(n.abs()))
        }
        "to_string" => {
            validate_arg_count("Integer.to_string", arg_values, 0)?;
            Ok(Value::from_string(n.to_string()))
        }
        "pow" => {
            validate_arg_count("Integer.pow", arg_values, 1)?;
            // Extract exponent from argument
            match &arg_values[0] {
                Value::Integer(exp) => {
                    if *exp < 0 {
                        return Err(InterpreterError::RuntimeError(
                            "Integer pow() exponent must be non-negative".to_string(),
                        ));
                    }
                    let result = n.pow(*exp as u32);
                    Ok(Value::Integer(result))
                }
                _ => Err(InterpreterError::TypeError(format!(
                    "Integer pow() requires integer exponent, got {}",
                    arg_values[0].type_name()
                ))),
            }
        }
        _ => Err(InterpreterError::RuntimeError(format!(
            "Unknown integer method: {method}"
        ))),
    }
}

/// Evaluate generic methods available on all types
///
/// # Complexity
/// Cyclomatic complexity: 3 (within Toyota Way limits)
pub fn eval_generic_method(
    receiver: &Value,
    method: &str,
    args_empty: bool,
) -> Result<Value, InterpreterError> {
    if method == "to_string" && args_empty {
        Ok(Value::from_string(receiver.to_string()))
    } else {
        Err(InterpreterError::RuntimeError(format!(
            "Method '{}' not found for type {}",
            method,
            receiver.type_name()
        )))
    }
}

/// Evaluate string methods
///
/// # Complexity
/// Cyclomatic complexity: High - needs delegation to `eval_string_methods` module
pub fn eval_string_method(
    s: &Arc<str>,
    method: &str,
    args: &[Value],
) -> Result<Value, InterpreterError> {
    // Delegate to the already extracted eval_string_methods module
    super::eval_string_methods::eval_string_method(s, method, args)
}

/// Simple array method evaluation (for non-higher-order methods)
///
/// # Complexity
/// Cyclomatic complexity: 4 (within Toyota Way limits)
pub fn eval_array_method_simple(
    arr: &Arc<[Value]>,
    method: &str,
    args: &[Value],
) -> Result<Value, InterpreterError> {
    match method {
        "len" | "length" => {
            validate_arg_count("Array.len", args, 0)?;
            Ok(Value::Integer(arr.len() as i64))
        }
        "is_empty" => {
            validate_arg_count("Array.is_empty", args, 0)?;
            Ok(Value::Bool(arr.is_empty()))
        }
        // STDLIB-004: Custom array methods
        "slice" => {
            validate_arg_count("Array.slice", args, 2)?;
            match (&args[0], &args[1]) {
                (Value::Integer(start), Value::Integer(end)) => {
                    let start_idx = (*start).max(0) as usize;
                    let end_idx = (*end).max(0) as usize;
                    let slice: Vec<Value> = arr
                        .iter()
                        .skip(start_idx)
                        .take(end_idx.saturating_sub(start_idx))
                        .cloned()
                        .collect();
                    Ok(Value::from_array(slice))
                }
                _ => Err(InterpreterError::RuntimeError(
                    "Array.slice() expects two integer arguments".to_string(),
                )),
            }
        }
        "join" => {
            validate_arg_count("Array.join", args, 1)?;
            match &args[0] {
                Value::String(separator) => {
                    let strings: Vec<String> = arr
                        .iter()
                        .map(|v| match v {
                            Value::String(s) => s.to_string(),
                            _ => format!("{v}"),
                        })
                        .collect();
                    Ok(Value::from_string(strings.join(separator.as_ref())))
                }
                _ => Err(InterpreterError::RuntimeError(
                    "Array.join() expects a string argument".to_string(),
                )),
            }
        }
        "unique" => {
            validate_arg_count("Array.unique", args, 0)?;
            let mut seen = std::collections::HashSet::new();
            let unique: Vec<Value> = arr
                .iter()
                .filter(|v| {
                    let key = format!("{v:?}");  // Use debug representation as key
                    seen.insert(key)
                })
                .cloned()
                .collect();
            Ok(Value::from_array(unique))
        }
        _ => {
            // For complex array methods, delegate to eval_array module
            // This requires passing a function evaluator which we don't have here
            Err(InterpreterError::RuntimeError(format!(
                "Array method '{method}' requires interpreter context"
            )))
        }
    }
}

/// Simple `DataFrame` method evaluation (for non-complex operations)
///
/// # Complexity
/// Cyclomatic complexity: 4 (within Toyota Way limits)
pub fn eval_dataframe_method_simple(
    columns: &[DataFrameColumn],
    method: &str,
    args: &[Value],
) -> Result<Value, InterpreterError> {
    match method {
        "shape" => {
            validate_arg_count("DataFrame.shape", args, 0)?;
            let rows = columns.first().map_or(0, |c| c.values.len());
            let cols = columns.len();
            Ok(Value::Tuple(Arc::from(
                vec![Value::Integer(rows as i64), Value::Integer(cols as i64)].as_slice(),
            )))
        }
        "columns" => {
            validate_arg_count("DataFrame.columns", args, 0)?;
            let col_names = columns
                .iter()
                .map(|c| Value::from_string(c.name.clone()))
                .collect();
            Ok(Value::from_array(col_names))
        }
        _ => {
            // For complex DataFrame operations, delegate to eval_dataframe_ops
            super::eval_dataframe_ops::eval_dataframe_method(columns, method, args)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_eval_float_method() {
        assert_eq!(
            eval_float_method(4.0, "sqrt", true).unwrap(),
            Value::Float(2.0)
        );
        assert_eq!(
            eval_float_method(-3.5, "abs", true).unwrap(),
            Value::Float(3.5)
        );
        assert_eq!(
            eval_float_method(3.7, "round", true).unwrap(),
            Value::Float(4.0)
        );
        assert_eq!(
            eval_float_method(3.7, "floor", true).unwrap(),
            Value::Float(3.0)
        );
        assert_eq!(
            eval_float_method(3.2, "ceil", true).unwrap(),
            Value::Float(4.0)
        );
    }

    #[test]
    fn test_eval_integer_method() {
        // Test abs() - no arguments
        assert_eq!(
            eval_integer_method(-5, "abs", &[]).unwrap(),
            Value::Integer(5)
        );

        // Test to_string() - no arguments
        let result = eval_integer_method(42, "to_string", &[]).unwrap();
        match result {
            Value::String(s) => assert_eq!(s.as_ref(), "42"),
            _ => panic!("Expected string value"),
        }

        // Test pow() - with argument
        assert_eq!(
            eval_integer_method(2, "pow", &[Value::Integer(3)]).unwrap(),
            Value::Integer(8)
        );
        assert_eq!(
            eval_integer_method(5, "pow", &[Value::Integer(2)]).unwrap(),
            Value::Integer(25)
        );
        assert_eq!(
            eval_integer_method(10, "pow", &[Value::Integer(0)]).unwrap(),
            Value::Integer(1)
        );

        // Test pow() error cases
        assert!(eval_integer_method(2, "pow", &[]).is_err()); // Missing argument
        assert!(eval_integer_method(2, "pow", &[Value::Integer(-1)]).is_err()); // Negative exponent
        assert!(eval_integer_method(2, "pow", &[Value::String("3".into())]).is_err());
        // Wrong type
    }

    #[test]
    fn test_eval_generic_method() {
        let val = Value::Integer(42);
        let result = eval_generic_method(&val, "to_string", true).unwrap();
        match result {
            Value::String(s) => assert_eq!(s.as_ref(), "42"),
            _ => panic!("Expected string value"),
        }

        // Test error case
        assert!(eval_generic_method(&val, "unknown", true).is_err());
    }

    #[test]
    fn test_method_argument_validation() {
        assert!(eval_float_method(4.0, "sqrt", false).is_err());
        assert!(eval_integer_method(5, "abs", &[Value::Integer(1)]).is_err());
        assert!(eval_generic_method(&Value::Nil, "type", false).is_err());
    }

    #[test]
    fn test_eval_array_method_simple_match_arms() {
        // Mutation test: Verify array method match arms are tested
        // MISSED: delete match arm "len" | "length" (line 154:9)
        // MISSED: delete match arm "is_empty" (line 162:9)

        let arr = Arc::from(vec![
            Value::Integer(1),
            Value::Integer(2),
            Value::Integer(3),
        ]);

        // Test "len" match arm
        let result = eval_array_method_simple(&arr, "len", &[]).unwrap();
        assert_eq!(result, Value::Integer(3), "len should return array length");

        // Test "length" alias
        let result = eval_array_method_simple(&arr, "length", &[]).unwrap();
        assert_eq!(
            result,
            Value::Integer(3),
            "length should return array length"
        );

        // Test "is_empty" match arm with non-empty array
        let result = eval_array_method_simple(&arr, "is_empty", &[]).unwrap();
        assert_eq!(
            result,
            Value::Bool(false),
            "is_empty should return false for non-empty"
        );

        // Test "is_empty" with empty array
        let empty_arr = Arc::from(vec![]);
        let result = eval_array_method_simple(&empty_arr, "is_empty", &[]).unwrap();
        assert_eq!(
            result,
            Value::Bool(true),
            "is_empty should return true for empty"
        );
    }

    #[test]
    fn test_eval_array_method_simple_negation_operators() {
        // Mutation test: Verify negation operators (!) are tested
        // MISSED: delete ! in eval_array_method_simple (line 155:16)
        // MISSED: delete ! in eval_array_method_simple (line 163:16)

        let arr = Arc::from(vec![Value::Integer(1)]);

        // Test that len REJECTS arguments (! operator line 155)
        let result = eval_array_method_simple(&arr, "len", &[Value::Integer(1)]);
        assert!(result.is_err(), "len should reject arguments");

        // Test that is_empty REJECTS arguments (! operator line 163)
        let result = eval_array_method_simple(&arr, "is_empty", &[Value::Integer(1)]);
        assert!(result.is_err(), "is_empty should reject arguments");
    }

    #[test]
    fn test_eval_dataframe_method_simple_match_arms() {
        // Mutation test: Verify DataFrame method match arms are tested
        // MISSED: delete match arm "columns" (line 202:9)

        let columns = vec![
            DataFrameColumn {
                name: "col1".to_string(),
                values: vec![Value::Integer(1), Value::Integer(2)],
            },
            DataFrameColumn {
                name: "col2".to_string(),
                values: vec![Value::Integer(3), Value::Integer(4)],
            },
        ];

        // Test "columns" match arm
        let result = eval_dataframe_method_simple(&columns, "columns", &[]).unwrap();
        match result {
            Value::Array(arr) => {
                assert_eq!(arr.len(), 2, "Should return 2 column names");
                assert_eq!(arr[0], Value::from_string("col1".to_string()));
                assert_eq!(arr[1], Value::from_string("col2".to_string()));
            }
            _ => panic!("Expected array of column names"),
        }
    }

    #[test]
    fn test_eval_dataframe_method_simple_negation_operator() {
        // Mutation test: Verify negation operator (!) is tested
        // MISSED: delete ! in eval_dataframe_method_simple (line 203:16)

        let columns = vec![DataFrameColumn {
            name: "col1".to_string(),
            values: vec![Value::Integer(1)],
        }];

        // Test that columns REJECTS arguments (! operator line 203)
        let result = eval_dataframe_method_simple(&columns, "columns", &[Value::Integer(1)]);
        assert!(result.is_err(), "columns method should reject arguments");
    }

    #[test]
    fn test_dispatch_method_call_match_arms() {
        // Mutation test: Verify dispatch_method_call match arms are tested
        // MISSED: delete match arm Value::Array(arr) (line 51:9)
        // MISSED: delete match arm Value::DataFrame{columns} (line 54:9)

        // Test Array dispatch
        let arr_val = Value::Array(Arc::from(vec![Value::Integer(1), Value::Integer(2)]));
        let result = dispatch_method_call(&arr_val, "len", &[], true).unwrap();
        assert_eq!(result, Value::Integer(2), "Array should dispatch to len");

        // Test DataFrame dispatch
        let df_val = Value::DataFrame {
            columns: vec![DataFrameColumn {
                name: "test".to_string(),
                values: vec![Value::Integer(1)],
            }],
        };
        let result = dispatch_method_call(&df_val, "columns", &[], true).unwrap();
        match result {
            Value::Array(_) => {} // Success
            _ => panic!("DataFrame should dispatch to columns"),
        }
    }
}

#[cfg(test)]
mod mutation_tests {
    use super::*;

    #[test]
    fn test_dispatch_method_call_float_match_arm() {
        // MISSED: delete match arm Value::Float(f) in dispatch_method_call (line 52)

        let float_val = Value::Float(4.0);
        let result = dispatch_method_call(&float_val, "sqrt", &[], true);

        assert!(result.is_ok(), "Float should dispatch to eval_float_method");
        assert_eq!(
            result.unwrap(),
            Value::Float(2.0),
            "sqrt(4.0) should be 2.0"
        );
    }

    #[test]
    fn test_eval_method_call_logical_operator() {
        // MISSED: replace && with || in eval_method_call (line 29)
        // The && operator ensures BOTH conditions must be true:
        // 1. receiver must be DataFrame
        // 2. method must be "filter"
        //
        // If mutation changes to ||, then:
        // - Any DataFrame (even without filter) would trigger special handling
        // - Any type with filter method would trigger DataFrame path
        //
        // This test verifies the && logic by testing a DataFrame with non-filter method

        let df_val = Value::DataFrame {
            columns: vec![DataFrameColumn {
                name: "test".to_string(),
                values: vec![Value::Integer(1), Value::Integer(2)],
            }],
        };

        // Test DataFrame with "columns" method (not "filter")
        // With &&: should NOT take special DataFrame filter path
        // With ||: would incorrectly take special path (because it's a DataFrame)
        let result = dispatch_method_call(&df_val, "columns", &[], true);
        assert!(
            result.is_ok(),
            "DataFrame with non-filter method should use normal dispatch"
        );

        // Verify it returns an array (normal dispatch working)
        match result.unwrap() {
            Value::Array(_) => {} // Success - normal dispatch occurred
            _ => panic!("DataFrame.columns should return array via normal dispatch"),
        }
    }
}
