use crate::enums::reduction::Reduction;
use crate::records::never_type::NeverType;
use crate::records::type_function_context::TypeFunctionContext;
use crate::records::type_function_reduction_result::TypeFunctionReductionResult;
use crate::records::type_pack::TypePack;
use crate::type_aliases::error_vec::ErrorVec;
use crate::type_aliases::type_id::TypeId;
use crate::type_aliases::type_pack_id::TypePackId;
use alloc::string::String;
use alloc::vec::Vec;
use luaur_ast::records::location::Location;
use luaur_common::macros::luau_assert::LUAU_ASSERT;

pub fn numeric_binop_type_function(
    instance: TypeId,
    type_params: Vec<TypeId>,
    pack_params: Vec<TypePackId>,
    ctx: *mut TypeFunctionContext,
    metamethod: String,
) -> TypeFunctionReductionResult {
    let ctx_ref = unsafe { &*ctx };
    if type_params.len() != 2 || !pack_params.is_empty() {
        unsafe {
            (*ctx_ref.ice.as_ptr()).ice_string(
                "encountered a type function instance without the required argument structure",
            )
        };
        LUAU_ASSERT!(false);
    }

    let lhs_ty = unsafe { crate::functions::follow_type::follow_type_id(type_params[0]) };
    let rhs_ty = unsafe { crate::functions::follow_type::follow_type_id(type_params[1]) };

    if lhs_ty == instance || rhs_ty == instance {
        return TypeFunctionReductionResult {
            result: Some(unsafe { (*ctx_ref.builtins.as_ptr()).neverType }),
            reduction_status: Reduction::MaybeOk,
            blocked_types: Vec::new(),
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    if unsafe {
        crate::functions::get_type_alt_j::get_type_id::<NeverType>(lhs_ty)
            .as_ref()
            .is_some()
    } || unsafe {
        crate::functions::get_type_alt_j::get_type_id::<NeverType>(rhs_ty)
            .as_ref()
            .is_some()
    } {
        return TypeFunctionReductionResult {
            result: Some(unsafe { (*ctx_ref.builtins.as_ptr()).neverType }),
            reduction_status: Reduction::MaybeOk,
            blocked_types: Vec::new(),
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    let location = if !ctx_ref.constraint.is_null() {
        unsafe { &*ctx_ref.constraint }.location
    } else {
        Location::new(Default::default(), Default::default())
    };

    if crate::functions::is_pending::is_pending(lhs_ty, ctx_ref.solver) {
        return TypeFunctionReductionResult {
            result: None,
            reduction_status: Reduction::MaybeOk,
            blocked_types: vec![lhs_ty],
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    } else if crate::functions::is_pending::is_pending(rhs_ty, ctx_ref.solver) {
        return TypeFunctionReductionResult {
            result: None,
            reduction_status: Reduction::MaybeOk,
            blocked_types: vec![rhs_ty],
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    let norm_lhs_ty = unsafe { (*ctx_ref.normalizer.as_ptr()).try_normalize(lhs_ty) };
    let norm_rhs_ty = unsafe { (*ctx_ref.normalizer.as_ptr()).try_normalize(rhs_ty) };

    let (Some(norm_lhs_ty), Some(norm_rhs_ty)) = (norm_lhs_ty, norm_rhs_ty) else {
        return TypeFunctionReductionResult {
            result: None,
            reduction_status: Reduction::MaybeOk,
            blocked_types: Vec::new(),
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    };

    // if one of the types is error suppressing, we can reduce to `any` since we should
    // suppress errors in the result of the usage.
    if norm_lhs_ty.should_suppress_errors() || norm_rhs_ty.should_suppress_errors() {
        return TypeFunctionReductionResult {
            result: Some(unsafe { (*ctx_ref.builtins.as_ptr()).anyType }),
            reduction_status: Reduction::MaybeOk,
            blocked_types: Vec::new(),
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    // if we're adding two `number` types, the result is `number`.
    if norm_lhs_ty.is_exactly_number() && norm_rhs_ty.is_exactly_number() {
        return TypeFunctionReductionResult {
            result: Some(unsafe { (*ctx_ref.builtins.as_ptr()).numberType }),
            reduction_status: Reduction::MaybeOk,
            blocked_types: Vec::new(),
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    if let Some(result) =
        crate::functions::try_distribute_type_function_app::try_distribute_type_function_app(
            |instance, type_params, pack_params, ctx| {
                numeric_binop_type_function(
                    instance,
                    type_params,
                    pack_params,
                    ctx,
                    metamethod.clone(),
                )
            },
            instance,
            &type_params,
            &pack_params,
            ctx,
        )
    {
        return result;
    }

    let mut dummy: ErrorVec = Vec::new();

    let mm_type = unsafe {
        crate::functions::find_metatable_entry::find_metatable_entry(
            ctx_ref.builtins.as_ptr(),
            &mut dummy,
            lhs_ty,
            &metamethod,
            location,
        )
    };

    let mut reversed = false;
    let mut mm_type_opt = mm_type;

    if mm_type_opt.is_none() {
        mm_type_opt = unsafe {
            crate::functions::find_metatable_entry::find_metatable_entry(
                ctx_ref.builtins.as_ptr(),
                &mut dummy,
                rhs_ty,
                &metamethod,
                location,
            )
        };
        reversed = true;
    }

    if mm_type_opt.is_none() {
        return TypeFunctionReductionResult {
            result: None,
            reduction_status: Reduction::Erroneous,
            blocked_types: Vec::new(),
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    let mm_type = unsafe { crate::functions::follow_type::follow_type_id(mm_type_opt.unwrap()) };

    if crate::functions::is_pending::is_pending(mm_type, ctx_ref.solver) {
        return TypeFunctionReductionResult {
            result: None,
            reduction_status: Reduction::MaybeOk,
            blocked_types: vec![mm_type],
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    let arg_pack = unsafe {
        (*ctx_ref.arena.as_ptr()).add_type_pack_initializer_list_type_id(&[lhs_ty, rhs_ty])
    };

    if reversed {
        let pack_mut = unsafe {
            crate::functions::get_mutable_type_pack::get_mutable_type_pack_id::<TypePack>(arg_pack)
        };
        if !pack_mut.is_null() {
            let pack_ref = unsafe { &mut *pack_mut };
            if pack_ref.head.len() >= 2 {
                pack_ref.head.swap(0, 1);
            }
        }
    }

    let ret_pack = crate::functions::solve_function_call::solve_function_call(
        ctx, location, mm_type, arg_pack,
    );

    if ret_pack.is_none() {
        return TypeFunctionReductionResult {
            result: None,
            reduction_status: Reduction::Erroneous,
            blocked_types: Vec::new(),
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    let ret_pack_val = ret_pack.unwrap();
    let extracted = crate::functions::extend_type_pack::extend_type_pack(
        unsafe { &mut *ctx_ref.arena.as_ptr() },
        ctx_ref.builtins.as_ptr(),
        ret_pack_val,
        1,
        Vec::new(),
    );

    if extracted.head.is_empty() {
        return TypeFunctionReductionResult {
            result: None,
            reduction_status: Reduction::Erroneous,
            blocked_types: Vec::new(),
            blocked_packs: Vec::new(),
            error: None,
            messages: Vec::new(),
        };
    }

    TypeFunctionReductionResult {
        result: Some(extracted.head[0]),
        reduction_status: Reduction::MaybeOk,
        blocked_types: Vec::new(),
        blocked_packs: Vec::new(),
        error: None,
        messages: Vec::new(),
    }
}
