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

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,
    }
}

pub fn apply_partial_application(
    interp: &mut Interpreter,
    params: Vec<LambdaParam>,
    body_expr: Box<crate::parser::ast::Expr>,
    mut bound_args: Vec<Value>,
    args: Vec<Value>,
) -> Result<Value, String> {
    if params.len() == 1 && matches!(params[0], LambdaParam::Variadic) {
        let mut prev_args = match bound_args.pop() {
            Some(Value::MixedArray(vals)) => vals,
            _ => vec![],
        };

        let mut supplied = args.into_iter();
        for v in prev_args.iter_mut() {
            if is_placeholder(v) {
                if let Some(next) = supplied.next() {
                    *v = next;
                }
            }
        }
        prev_args.extend(supplied);

        let any_ph = prev_args.iter().any(|v| is_placeholder(v));
        let final_bound_args = vec![Value::MixedArray(prev_args)];
        if !any_ph {
            call_inline_lambda_ast(interp, &params, &body_expr, final_bound_args)
        } else {
            Ok(Value::Function(Box::new(FunctionValue::InlineLambdaASTPartial {
                params,
                body_expr,
                bound_args: final_bound_args,
            })))
        }
    }
    else {
        let arity = params.len();
        let mut arg_idx = 0;
        for arg in &args {
            while arg_idx < bound_args.len() && !is_placeholder(&bound_args[arg_idx]) {
                arg_idx += 1;
            }
            if arg_idx >= arity {
                break;
            }

            if is_placeholder(arg) {
            } else {
                bound_args[arg_idx] = arg.clone();
            }

            arg_idx += 1;
        }

        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(FunctionValue::InlineLambdaASTPartial {
                params,
                body_expr,
                bound_args,
            })))
        }
    }
}
