#[cfg(test)]
mod test {
    use internment::ArcIntern;
    use smol_str::SmolStr;

    use crate::{
        DiagnosticCode, GenericTpl, GenericTplId, LuaMergedTableType, LuaType, RenderLevel,
        TypeSubstitutor, VirtualWorkspace, humanize_type, instantiate_type_generic,
    };

    #[test]
    fn test_variadic_func() {
        let mut ws = crate::VirtualWorkspace::new();
        ws.def(
            r#"
        ---@generic T, R
        ---@param call async fun(...: T...): R...
        ---@return async fun(...: T...): R...
        function async_create(call)

        end


        ---@param a number
        ---@param b string
        ---@param c boolean
        ---@return number
        function locaf(a, b, c)

        end
        "#,
        );

        let ty = ws.expr_ty("async_create(locaf)");
        let expected = ws.ty("async fun(a: number, b: string, c:boolean): number...");
        assert_eq!(ty, expected);
    }

    #[test]
    fn test_merged_table_instantiates_generic_components() {
        let ws = crate::VirtualWorkspace::new();
        let mut substitutor = TypeSubstitutor::new();
        substitutor.insert_type(GenericTplId::Type(0), LuaType::String, true);

        let generic_type = LuaType::TplRef(
            GenericTpl::new(
                GenericTplId::Type(0),
                ArcIntern::new(SmolStr::new("T")),
                None,
            )
            .into(),
        );
        let merged_type = LuaMergedTableType::new(vec![generic_type, LuaType::Table]).into();

        let instantiated =
            instantiate_type_generic(ws.analysis.compilation.get_db(), &merged_type, &substitutor);

        let LuaType::MergedTable(merged) = instantiated else {
            panic!("expected merged table after generic instantiation");
        };
        assert_eq!(merged.get_types()[0], LuaType::String);
        assert_eq!(merged.get_types()[1], LuaType::Table);
    }

    #[test]
    fn test_select_type() {
        let mut ws = crate::VirtualWorkspace::new_with_init_std_lib();
        ws.def(
            r#"
        ---@param ... string
        function ffff(...)
            a, b, c = select(2, ...)
        end
        "#,
        );

        let a_ty = ws.expr_ty("a");
        let b_ty = ws.expr_ty("b");
        let c_ty = ws.expr_ty("c");
        let expected = ws.ty("string");
        assert_eq!(a_ty, expected);
        assert_eq!(b_ty, expected);
        assert_eq!(c_ty, expected);

        ws.def(
            r#"
        e, f = select(2, "a", "b", "c")
        "#,
        );

        let e = ws.expr_ty("e");
        let expected = LuaType::String;
        let f = ws.expr_ty("f");
        let expected_f = LuaType::String;
        assert_eq!(e, expected);
        assert_eq!(f, expected_f);

        ws.def(
            r#"
        h = select('#', "a", "b")
        "#,
        );

        let h = ws.expr_ty("h");
        let expected = LuaType::IntegerConst(2);
        assert_eq!(h, expected);

        // select(n, func()) where func() returns multiple values
        ws.def(
            r#"
        ---@return integer, string
        function multi_ret() end
        g = select(2, multi_ret())
        "#,
        );

        let g = ws.expr_ty("g");
        let expected_g = ws.ty("string");
        assert_eq!(g, expected_g);
    }

    #[test]
    fn test_unpack() {
        let mut ws = crate::VirtualWorkspace::new_with_init_std_lib();

        ws.def(
            r#"
        local h ---@type number[]
        a, b, c = table.unpack(h)
        "#,
        );

        let a = ws.expr_ty("a");
        let expected = ws.ty("number?");
        let b = ws.expr_ty("b");
        let expected_b = ws.ty("number?");
        let c = ws.expr_ty("c");
        let expected_c = ws.ty("number?");
        assert_eq!(a, expected);
        assert_eq!(b, expected_b);
        assert_eq!(c, expected_c);
    }

    #[test]
    fn test_return() {
        let mut ws = crate::VirtualWorkspace::new();
        ws.def(
            r#"
                ---@class ab
                ---@field a number
                local A

                ---@generic T
                ---@param a T
                ---@return T
                local function name(a)
                    return a
                end

                local a = name(A)
                a.b = 1
                R = A.b
        "#,
        );

        let a = ws.expr_ty("R");
        let expected = ws.ty("nil");
        assert_eq!(a, expected);
    }

    #[test]
    fn test_issue_797() {
        let mut ws = VirtualWorkspace::new();
        ws.def(
            r#"
---@class Holder<T>

---@class C_StringHolder : Holder<string>

---@class C_StringHolderExt : C_StringHolder

---@class C_StringHolderWith<T> : Holder<string>

---@class C_StringHolderWithExt<T> : C_StringHolderWith<T>

---@alias A_StringHolder Holder<string>

---@alias A_StringHolderExt A_StringHolder

---@alias A_StringHolderWith<T> Holder<string>

---@alias A_StringHolderWithExt<T> A_StringHolderWith<T>

---@generic T
---@param v Holder<T>
---@return T
local function extract_holder(v) return v end

local direct ---@type Holder<string>

local class_a ---@type C_StringHolder
local class_b ---@type C_StringHolderExt
local class_c ---@type C_StringHolderWith<table>
local class_d ---@type C_StringHolderWithExt<table>

local alias_a ---@type A_StringHolder
local alias_b ---@type A_StringHolderExt
local alias_c ---@type A_StringHolderWith<table>
local alias_d ---@type A_StringHolderWithExt<table>

result = {
    direct = extract_holder(direct),

    class_a = extract_holder(class_a),
    class_b = extract_holder(class_b),
    class_c = extract_holder(class_c),
    class_d = extract_holder(class_d),

    alias_a = extract_holder(alias_a),
    alias_b = extract_holder(alias_b),
    alias_c = extract_holder(alias_c),
    alias_d = extract_holder(alias_d),
}
        "#,
        );

        let a = ws.expr_ty("result");
        let a_desc = humanize_type(
            ws.analysis.compilation.get_db(),
            &a,
            RenderLevel::DetailedCount(12),
        );
        let expected = r#"{
    direct: string,
    class_a: string,
    class_b: string,
    class_c: string,
    class_d: string,
    alias_a: string,
    alias_b: string,
    alias_c: string,
    alias_d: string,
}"#;
        assert_eq!(a_desc, expected);
    }

    #[test]
    fn test_call_generic() {
        let mut ws = crate::VirtualWorkspace::new();
        ws.def(
            r#"
            ---@alias Warp<T> T

            ---@generic T
            ---@param ... Warp<T>
            function test(...)
            end
        "#,
        );

        assert!(!ws.check_code_for(
            DiagnosticCode::ParamTypeMismatch,
            r#"
            ---@type Warp<number>, Warp<string>
            local a, b
            test(a, b)
        "#,
        ));

        assert!(ws.check_code_for(
            DiagnosticCode::ParamTypeMismatch,
            r#"
            ---@type Warp<number>, Warp<string>
            local a, b
            test--[[@<number | string>]](a, b)
        "#,
        ));
    }

    #[test]
    fn test_generic_alias_instantiation() {
        let mut ws = crate::VirtualWorkspace::new();
        ws.def(
            r#"
            ---@alias Arrayable<T> T | T[]

            ---@class Suite

            ---@generic T
            ---@param value Arrayable<T>
            ---@return T[]
            function toArray(value)
            end
        "#,
        );

        ws.def(
            r#"
            ---@type Arrayable<Suite>
            local suite

            arraySuites = toArray(suite)
        "#,
        );

        let a = ws.expr_ty("arraySuites");
        let expected = ws.ty("Suite[]");
        assert_eq!(a, expected);
    }

    #[test]
    fn test_generic_alias_instantiation2() {
        let mut ws = crate::VirtualWorkspace::new();
        ws.def(
            r#"
            ---@alias Arrayable<T> T | T[]

            ---@class Suite

            ---@param value Arrayable<Suite>
            function toArray(value)

            end
        "#,
        );
        assert!(ws.check_code_for(
            DiagnosticCode::ParamTypeMismatch,
            r#"

            ---@type Suite
            local suite

            local arraySuites = toArray(suite)
            "#
        ));
    }
}
