use crate::parser::types::{FunctionValue, Value, LambdaParam};
use crate::parser::interpreter::Interpreter;

use super::partial::apply_partial_application;
use super::array_helpers::{do_map, do_filter};
use super::curry_utils::{
    handle_composition, handle_composition_partial, handle_curried_prop, handle_curried_assoc,
    handle_rust_closure,
};
use super::lambda::call_inline_lambda_ast;

pub fn is_placeholder(val: &Value) -> bool {
    match val {
        Value::Placeholder => true,
        Value::SingleString(s) if s == "_" => true,
        Value::StrArray(ss) if ss.len() == 1 && ss[0] == "_" => true,
        _ => false,
    }
}

/// Recursively check if a Value or any sub-value is a placeholder.
/// This is needed for MixedArray (variadic).
pub fn contains_placeholder(val: &Value) -> bool {
    match val {
        Value::Placeholder => true,
        Value::SingleString(s) if s == "_" => true,
        Value::StrArray(ss) if ss.len() == 1 && ss[0] == "_" => true,
        Value::MixedArray(items) => items.iter().any(contains_placeholder),
        _ => false,
    }
}

pub fn apply_one_function_value(
    interp: &mut Interpreter,
    func_val: Box<FunctionValue>,
    data: Value,
) -> Result<Value, String> {
    apply_n_ary_function_value(interp, func_val, vec![data])
}

pub fn apply_n_ary_function_value(
    interp: &mut Interpreter,
    func_val: Box<FunctionValue>,
    args: Vec<Value>,
) -> Result<Value, String> {
    use FunctionValue::*;
    match *func_val {
        Named(ref name) => {
            if let Some(bridge) = interp.get_dynamic_function(name) {
                let closure = bridge.lock()
                    .map_err(|_| format!("Dynamic function lock error for '{}'", name))?;
                closure(interp, args)
            } else {
                Err(format!("No built-in bridging for Named('{}')", name))
            }
        }
        InlineLambdaAST { params, body_expr } => {
            // Handle variadic lambdas (including (x, $, y) and ($), etc.)

            let mut bound_args = Vec::new();

            // Multi-parameter with variadic in the middle: (x, $, y)
            let arity = params.len();
            let variadic_idx = params.iter().position(|p| matches!(p, LambdaParam::Variadic));
            if let Some(vidx) = variadic_idx {
                // If ($): old style single variadic parameter
                if arity == 1 {
                    let arg_vec = args;
                    let any_ph = arg_vec.iter().any(contains_placeholder);
                    let ba = vec![Value::MixedArray(arg_vec)];
                    if !any_ph {
                        call_inline_lambda_ast(interp, &params, &body_expr, ba)
                    } else {
                        Ok(Value::Function(Box::new(InlineLambdaASTPartial {
                            params,
                            body_expr,
                            bound_args: ba,
                        })))
                    }
                } else if arity >= 2 {
                    // (before, ..., Variadic, ..., after)
                    let before_count = vidx;
                    let after_count = arity - vidx - 1;
                    let total_args = args.len();

                    // Assign 'before' params
                    for i in 0..before_count {
                        if i < total_args {
                            bound_args.push(args[i].clone());
                        } else {
                            bound_args.push(Value::Placeholder);
                        }
                    }

                    // Assign variadic param
                    let start_variadic = before_count;
                    let end_variadic = total_args.saturating_sub(after_count);
                    let variadic_slice = if end_variadic > start_variadic && total_args >= before_count + after_count {
                        args[start_variadic..end_variadic].to_vec()
                    } else {
                        Vec::new()
                    };
                    bound_args.push(Value::MixedArray(variadic_slice.clone()));

                    // Assign 'after' params
                    for i in 0..after_count {
                        let idx = total_args.checked_sub(after_count).unwrap_or(0) + i;
                        if idx < total_args {
                            bound_args.push(args[idx].clone());
                        } else {
                            bound_args.push(Value::Placeholder);
                        }
                    }

                    // Check for placeholders, even nested in MixedArray
                    let any_ph = bound_args.iter().any(contains_placeholder);
                    if !any_ph {
                        call_inline_lambda_ast(interp, &params, &body_expr, bound_args)
                    } else {
                        Ok(Value::Function(Box::new(InlineLambdaASTPartial {
                            params,
                            body_expr,
                            bound_args,
                        })))
                    }
                } else {
                    // Fallback, shouldn't happen
                    Err("Invalid lambda parameter pattern".to_string())
                }
            }
            // Classic non-variadic
            else {
                let arity = params.len();
                let mut bound_args: Vec<Value> = Vec::with_capacity(arity);
                let mut n_provided = 0;
                for arg in &args {
                    if n_provided >= arity {
                        break; // Ignore extra args in Ramda style
                    }
                    if is_placeholder(arg) {
                        bound_args.push(Value::Placeholder);
                    } else {
                        bound_args.push(arg.clone());
                    }
                    n_provided += 1;
                }
                while bound_args.len() < arity {
                    bound_args.push(Value::Placeholder);
                }
                if bound_args.iter().all(|v| !is_placeholder(v)) {
                    call_inline_lambda_ast(interp, &params, &body_expr, bound_args)
                } else {
                    Ok(Value::Function(Box::new(InlineLambdaASTPartial {
                        params,
                        body_expr,
                        bound_args,
                    })))
                }
            }
        }
        InlineLambdaASTPartial { params, body_expr, bound_args } => {
            apply_partial_application(interp, params, body_expr, bound_args, args)
        }
        CurriedMap(map_fn_box) => {
            if args.is_empty() {
                Ok(Value::Function(Box::new(FunctionValue::CurriedMapPlaceholder {
                    func: Some(map_fn_box),
                    data: None,
                })))
            } else if args.len() == 1 {
                let data_val = args.into_iter().next().unwrap();
                do_map(interp, &*map_fn_box, data_val)
            } else {
                Err(format!("array:map => expected 0 or 1 argument, got {}", args.len()))
            }
        }
        CurriedMapPlaceholder { func, data } => {
            super::array_helpers::apply_curried_map_placeholder(interp, func, data, args)
        }
        CurriedFilter(pred_box) => {
            if args.is_empty() {
                Ok(Value::Function(Box::new(FunctionValue::CurriedFilterPlaceholder {
                    pred: Some(pred_box),
                    data: None,
                })))
            } else if args.len() == 1 {
                let data_val = args.into_iter().next().unwrap();
                do_filter(interp, &*pred_box, data_val)
            } else {
                Err(format!("array:filter => expected 0 or 1 arg, got {}", args.len()))
            }
        }
        CurriedFilterPlaceholder { pred, data } => {
            super::array_helpers::apply_curried_filter_placeholder(interp, pred, data, args)
        }
        CurriedReducePartial(f_box, init_val) => {
            super::array_helpers::apply_curried_reduce_partial(interp, f_box, init_val, args)
        }
        CurriedReducePlaceholder { func, init_val, data } => {
            super::array_helpers::apply_curried_reduce_placeholder(interp, func, init_val, data, args)
        }
        Composition(f_vec, mode) => handle_composition(interp, f_vec, mode, args),
        CompositionPartial { mode, slots } => handle_composition_partial(interp, mode, slots, args),
        CurriedProp(k) => handle_curried_prop(interp, k, args),
        CurriedAssoc(k, v) => handle_curried_assoc(interp, k, v, args),
        RustClosure(desc, closure_arc, arity) => handle_rust_closure(interp, desc, closure_arc, arity, args),
    }
}
