use super::*;
use expect_test::expect;

#[test]
fn inner_item_smoke() {
    check_at(
        r#"
struct inner {}
fn outer() {
    $0
    fn inner() {}
}
"#,
        expect![[r#"
            block scope
            inner: v

            crate
            inner: t
            outer: v
        "#]],
    );
}

#[test]
fn use_from_crate() {
    check_at(
        r#"
struct Struct {}
fn outer() {
    fn Struct() {}
    use Struct as PlainStruct;
    use crate::Struct as CrateStruct;
    use self::Struct as SelfStruct;
    use super::Struct as SuperStruct;
    $0
}
"#,
        expect![[r#"
            block scope
            CrateStruct: ti
            PlainStruct: ti vi
            SelfStruct: ti
            Struct: v
            SuperStruct: _

            crate
            Struct: t
            outer: v
        "#]],
    );
}

#[test]
fn merge_namespaces() {
    check_at(
        r#"
struct name {}
fn outer() {
    fn name() {}

    use name as imported; // should import both `name`s

    $0
}
"#,
        expect![[r#"
            block scope
            imported: ti vi
            name: v

            crate
            name: t
            outer: v
        "#]],
    );
}

#[test]
fn nested_blocks() {
    check_at(
        r#"
fn outer() {
    struct inner1 {}
    fn inner() {
        use inner1;
        use outer;
        fn inner2() {}
        $0
    }
}
"#,
        expect![[r#"
            block scope
            inner1: ti
            inner2: v
            outer: vi

            block scope
            inner: v
            inner1: t

            crate
            outer: v
        "#]],
    );
}

#[test]
fn super_imports() {
    check_at(
        r#"
mod module {
    fn f() {
        use super::Struct;
        $0
    }
}

struct Struct {}
"#,
        expect![[r#"
            block scope
            Struct: ti

            crate
            Struct: t
            module: t

            crate::module
            f: v
        "#]],
    );
}

#[test]
fn super_imports_2() {
    check_at(
        r#"
fn outer() {
    mod m {
        struct ResolveMe {}
        fn middle() {
            mod m2 {
                fn inner() {
                    use super::ResolveMe;
                    $0
                }
            }
        }
    }
}
"#,
        expect![[r#"
            block scope
            ResolveMe: ti

            block scope
            m2: t

            block scope::m2
            inner: v

            block scope
            m: t

            block scope::m
            ResolveMe: t
            middle: v

            crate
            outer: v
        "#]],
    );
}

#[test]
fn nested_module_scoping() {
    check_block_scopes_at(
        r#"
fn f() {
    mod module {
        struct Struct {}
        fn f() {
            use self::Struct;
            $0
        }
    }
}
    "#,
        expect![[r#"
            BlockId(1) in BlockRelativeModuleId { block: Some(BlockId(0)), local_id: Idx::<ModuleData>(1) }
            BlockId(0) in BlockRelativeModuleId { block: None, local_id: Idx::<ModuleData>(0) }
            crate scope
        "#]],
    );
}

#[test]
fn self_imports() {
    check_at(
        r#"
fn f() {
    mod m {
        struct ResolveMe {}
        fn g() {
            fn h() {
                use self::ResolveMe;
                $0
            }
        }
    }
}
"#,
        expect![[r#"
            block scope
            ResolveMe: ti

            block scope
            h: v

            block scope
            m: t

            block scope::m
            ResolveMe: t
            g: v

            crate
            f: v
        "#]],
    );
}

#[test]
fn legacy_macro_items() {
    // Checks that legacy-scoped `macro_rules!` from parent namespaces are resolved and expanded
    // correctly.
    check_at(
        r#"
macro_rules! mark {
    () => {
        struct Hit {}
    }
}

fn f() {
    mark!();
    $0
}
"#,
        expect![[r#"
            block scope
            Hit: t

            crate
            f: v
        "#]],
    );
}

#[test]
fn macro_resolve() {
    check_at(
        r#"
//- /lib.rs crate:lib deps:core
use core::cov_mark;

fn f() {
    fn nested() {
        cov_mark::mark!(Hit);
        $0
    }
}
//- /core.rs crate:core
pub mod cov_mark {
    #[macro_export]
    macro_rules! _mark {
        ($name:ident) => {
            struct $name {}
        }
    }

    pub use crate::_mark as mark;
}
"#,
        expect![[r#"
            block scope
            Hit: t

            block scope
            nested: v

            crate
            cov_mark: ti
            f: v
        "#]],
    );
}

#[test]
fn macro_resolve_legacy() {
    check_at(
        r#"
//- /lib.rs
mod module;

//- /module.rs
macro_rules! m {
    () => {
        struct Def {}
    };
}

fn f() {
    {
        m!();
        $0
    }
}
        "#,
        expect![[r#"
            block scope
            Def: t

            crate
            module: t

            crate::module
            f: v
        "#]],
    )
}

#[test]
fn super_does_not_resolve_to_block_module() {
    check_at(
        r#"
fn main() {
    struct Struct {}
    mod module {
        use super::Struct;

        $0
    }
}
    "#,
        expect![[r#"
        block scope
        Struct: t
        module: t

        block scope::module
        Struct: _

        crate
        main: v
    "#]],
    );
}

#[test]
fn underscore_import() {
    // This used to panic, because the default (private) visibility inside block expressions would
    // point into the containing `DefMap`, which visibilities should never be able to do.
    cov_mark::check!(adjust_vis_in_block_def_map);
    check_at(
        r#"
mod m {
    fn main() {
        use Tr as _;
        trait Tr {}
        $0
    }
}
    "#,
        expect![[r#"
        block scope
        _: t
        Tr: t

        crate
        m: t

        crate::m
        main: v
    "#]],
    );
}

#[test]
fn nested_macro_item_decl() {
    cov_mark::check!(macro_call_in_macro_stmts_is_added_to_item_tree);
    check_at(
        r#"
macro_rules! inner_declare {
    ($ident:ident) => {
        static $ident: u32 = 0;
    };
}
macro_rules! declare {
    ($ident:ident) => {
        inner_declare!($ident);
    };
}

fn foo() {
    declare!(bar);
    bar;
    $0
}
        "#,
        expect![[r#"
            block scope
            bar: v

            crate
            foo: v
        "#]],
    )
}

#[test]
fn is_visible_from_same_def_map() {
    // Regression test for https://github.com/rust-lang/rust-analyzer/issues/9481
    cov_mark::check!(is_visible_from_same_block_def_map);
    check_at(
        r#"
fn outer() {
    mod tests {
        use super::*;
    }
    use crate::name;
    $0
}
        "#,
        expect![[r#"
            block scope
            name: _
            tests: t

            block scope::tests
            name: _
            outer: v

            crate
            outer: v
        "#]],
    );
}

#[test]
fn stmt_macro_expansion_with_trailing_expr() {
    cov_mark::check!(macro_stmt_with_trailing_macro_expr);
    check_at(
        r#"
macro_rules! mac {
    () => { mac!($) };
    ($x:tt) => { fn inner() {} };
}
fn foo() {
    mac!();
    $0
}
        "#,
        expect![[r#"
            block scope
            inner: v

            crate
            foo: v
        "#]],
    )
}

#[test]
fn trailing_expr_macro_expands_stmts() {
    check_at(
        r#"
macro_rules! foo {
    () => { const FOO: u32 = 0;const BAR: u32 = 0; };
}
fn f() {$0
    foo!{}
};
        "#,
        expect![[r#"
            block scope
            BAR: v
            FOO: v

            crate
            f: v
        "#]],
    )
}
