mod resolve_signature_by_args;

use std::sync::Arc;

use emmylua_parser::{LuaAstNode, LuaCallExpr};

use crate::db_index::{DbIndex, LuaFunctionType, LuaType};

use super::{
    LuaInferCache,
    generic::instantiate_func_generic,
    infer::{InferCallFuncResult, InferFailReason, infer_expr_list_types, try_infer_expr_no_flow},
};

pub(crate) use resolve_signature_by_args::{callable_accepts_args, resolve_signature_by_args};

pub fn resolve_signature(
    db: &DbIndex,
    cache: &mut LuaInferCache,
    overloads: Vec<Arc<LuaFunctionType>>,
    call_expr: LuaCallExpr,
    is_generic: bool,
    arg_count: Option<usize>,
) -> InferCallFuncResult {
    let args = call_expr.get_args_list().ok_or(InferFailReason::None)?;
    let mut declined_no_flow_arg_ranges = Vec::new();
    let expr_types_with_ranges = infer_expr_list_types(
        db,
        cache,
        args.get_args().collect::<Vec<_>>().as_slice(),
        arg_count,
        |db, cache, expr| {
            if cache.is_no_flow() {
                let expr_range = expr.get_range();
                let Some(expr_type) = try_infer_expr_no_flow(db, cache, expr)? else {
                    if !is_generic {
                        declined_no_flow_arg_ranges.push(expr_range);
                        return Ok(LuaType::Unknown);
                    }
                    return Err(InferFailReason::None);
                };
                Ok(expr_type)
            } else {
                Ok(crate::infer_expr(db, cache, expr).unwrap_or(LuaType::Unknown))
            }
        },
    )?;
    let declined_no_flow_args = expr_types_with_ranges
        .iter()
        .map(|(_, range)| declined_no_flow_arg_ranges.contains(range))
        .collect::<Vec<_>>();
    let expr_types: Vec<_> = expr_types_with_ranges
        .into_iter()
        .map(|(ty, _)| ty)
        .collect();

    if is_generic {
        resolve_signature_by_generic(db, cache, overloads, call_expr, expr_types, arg_count)
    } else {
        resolve_signature_by_args(
            db,
            &overloads,
            &expr_types,
            call_expr.is_colon_call(),
            arg_count,
            &declined_no_flow_args,
        )
    }
}

fn resolve_signature_by_generic(
    db: &DbIndex,
    cache: &mut LuaInferCache,
    overloads: Vec<Arc<LuaFunctionType>>,
    call_expr: LuaCallExpr,
    expr_types: Vec<LuaType>,
    arg_count: Option<usize>,
) -> InferCallFuncResult {
    let mut instantiate_funcs = Vec::new();
    for func in overloads {
        let instantiate_func = instantiate_func_generic(db, cache, &func, call_expr.clone())?;
        instantiate_funcs.push(Arc::new(instantiate_func));
    }
    resolve_signature_by_args(
        db,
        &instantiate_funcs,
        &expr_types,
        call_expr.is_colon_call(),
        arg_count,
        &[],
    )
}
