mod globs;
mod incremental;
mod macros;
mod mod_resolution;
mod primitives;

use std::sync::Arc;

use insta::assert_snapshot;
use ra_db::{fixture::WithFixture, SourceDatabase};
use test_utils::mark;

use crate::{db::DefDatabase, nameres::*, test_db::TestDB};

fn def_map(ra_fixture: &str) -> String {
    compute_crate_def_map(ra_fixture).dump()
}

fn compute_crate_def_map(fixture: &str) -> Arc<CrateDefMap> {
    let db = TestDB::with_files(fixture);
    let krate = db.crate_graph().iter().next().unwrap();
    db.crate_def_map(krate)
}

#[test]
fn crate_def_map_smoke_test() {
    let map = def_map(
        r"
        //- /lib.rs
        mod foo;
        struct S;
        use crate::foo::bar::E;
        use self::E::V;

        //- /foo/mod.rs
        pub mod bar;
        fn f() {}

        //- /foo/bar.rs
        pub struct Baz;

        union U {
            to_be: bool,
            not_to_be: u8,
        }

        enum E { V }

        extern {
            static EXT: u8;
            fn ext();
        }
        ",
    );
    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮E: t
        ⋮S: t v
        ⋮V: t v
        ⋮foo: t
        ⋮
        ⋮crate::foo
        ⋮bar: t
        ⋮f: v
        ⋮
        ⋮crate::foo::bar
        ⋮Baz: t v
        ⋮E: t
        ⋮EXT: v
        ⋮U: t
        ⋮ext: v
    "###)
}

#[test]
fn crate_def_map_super_super() {
    let map = def_map(
        "
        //- /lib.rs
        mod a {
            const A: usize = 0;

            mod b {
                const B: usize = 0;

                mod c {
                    use super::super::*;
                }
            }
        }
        ",
    );
    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮a: t
        ⋮
        ⋮crate::a
        ⋮A: v
        ⋮b: t
        ⋮
        ⋮crate::a::b
        ⋮B: v
        ⋮c: t
        ⋮
        ⋮crate::a::b::c
        ⋮A: v
        ⋮b: t
    "###)
}

#[test]
fn crate_def_map_fn_mod_same_name() {
    let map = def_map(
        "
        //- /lib.rs
        mod m {
            pub mod z {}
            pub fn z() {}
        }
        ",
    );
    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮m: t
        ⋮
        ⋮crate::m
        ⋮z: t v
        ⋮
        ⋮crate::m::z
    "###)
}

#[test]
fn bogus_paths() {
    mark::check!(bogus_paths);
    let map = def_map(
        "
        //- /lib.rs
        mod foo;
        struct S;
        use self;

        //- /foo/mod.rs
        use super;
        use crate;

        ",
    );
    assert_snapshot!(map, @r###"
   ⋮crate
   ⋮S: t v
   ⋮foo: t
   ⋮
   ⋮crate::foo
    "###
    )
}

#[test]
fn use_as() {
    let map = def_map(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::Baz as Foo;

        //- /foo/mod.rs
        pub struct Baz;
        ",
    );
    assert_snapshot!(map,
        @r###"
   ⋮crate
   ⋮Foo: t v
   ⋮foo: t
   ⋮
   ⋮crate::foo
   ⋮Baz: t v
    "###
    );
}

#[test]
fn use_trees() {
    let map = def_map(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::{Baz, Quux};

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        pub enum Quux {};
        ",
    );
    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Baz: t v
        ⋮Quux: t
        ⋮foo: t
        ⋮
        ⋮crate::foo
        ⋮bar: t
        ⋮
        ⋮crate::foo::bar
        ⋮Baz: t v
        ⋮Quux: t
    "###);
}

#[test]
fn re_exports() {
    let map = def_map(
        "
        //- /lib.rs
        mod foo;

        use self::foo::Baz;

        //- /foo/mod.rs
        pub mod bar;

        pub use self::bar::Baz;

        //- /foo/bar.rs
        pub struct Baz;
        ",
    );
    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Baz: t v
        ⋮foo: t
        ⋮
        ⋮crate::foo
        ⋮Baz: t v
        ⋮bar: t
        ⋮
        ⋮crate::foo::bar
        ⋮Baz: t v
    "###);
}

#[test]
fn std_prelude() {
    mark::check!(std_prelude);
    let map = def_map(
        "
        //- /main.rs crate:main deps:test_crate
        use Foo::*;

        //- /lib.rs crate:test_crate
        mod prelude;
        #[prelude_import]
        use prelude::*;

        //- /prelude.rs
        pub enum Foo { Bar, Baz };
        ",
    );
    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Bar: t v
        ⋮Baz: t v
    "###);
}

#[test]
fn can_import_enum_variant() {
    mark::check!(can_import_enum_variant);
    let map = def_map(
        "
        //- /lib.rs
        enum E { V }
        use self::E::V;
        ",
    );
    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮E: t
        ⋮V: t v
    "###
    );
}

#[test]
fn edition_2015_imports() {
    let map = def_map(
        "
        //- /main.rs crate:main deps:other_crate edition:2015
        mod foo;
        mod bar;

        //- /bar.rs
        struct Bar;

        //- /foo.rs
        use bar::Bar;
        use other_crate::FromLib;

        //- /lib.rs crate:other_crate edition:2018
        struct FromLib;
        ",
    );

    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮bar: t
        ⋮foo: t
        ⋮
        ⋮crate::bar
        ⋮Bar: t v
        ⋮
        ⋮crate::foo
        ⋮Bar: t v
        ⋮FromLib: t v
    "###);
}

#[test]
fn item_map_using_self() {
    let map = def_map(
        "
        //- /lib.rs
        mod foo;
        use crate::foo::bar::Baz::{self};
        //- /foo/mod.rs
        pub mod bar;
        //- /foo/bar.rs
        pub struct Baz;
        ",
    );
    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Baz: t v
        ⋮foo: t
        ⋮
        ⋮crate::foo
        ⋮bar: t
        ⋮
        ⋮crate::foo::bar
        ⋮Baz: t v
    "###);
}

#[test]
fn item_map_across_crates() {
    let map = def_map(
        "
        //- /main.rs crate:main deps:test_crate
        use test_crate::Baz;

        //- /lib.rs crate:test_crate
        pub struct Baz;
        ",
    );

    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Baz: t v
    "###);
}

#[test]
fn extern_crate_rename() {
    let map = def_map(
        "
        //- /main.rs crate:main deps:alloc
        extern crate alloc as alloc_crate;

        mod alloc;
        mod sync;

        //- /sync.rs
        use alloc_crate::Arc;

        //- /lib.rs crate:alloc
        struct Arc;
        ",
    );

    assert_snapshot!(map, @r###"
   ⋮crate
   ⋮alloc_crate: t
   ⋮sync: t
   ⋮
   ⋮crate::sync
   ⋮Arc: t v
    "###);
}

#[test]
fn extern_crate_rename_2015_edition() {
    let map = def_map(
        "
        //- /main.rs crate:main deps:alloc edition:2015
        extern crate alloc as alloc_crate;

        mod alloc;
        mod sync;

        //- /sync.rs
        use alloc_crate::Arc;

        //- /lib.rs crate:alloc
        struct Arc;
        ",
    );

    assert_snapshot!(map,
        @r###"
   ⋮crate
   ⋮alloc_crate: t
   ⋮sync: t
   ⋮
   ⋮crate::sync
   ⋮Arc: t v
    "###
    );
}

#[test]
fn reexport_across_crates() {
    let map = def_map(
        "
        //- /main.rs crate:main deps:test_crate
        use test_crate::Baz;

        //- /lib.rs crate:test_crate
        pub use foo::Baz;

        mod foo;

        //- /foo.rs
        pub struct Baz;
        ",
    );

    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Baz: t v
    "###);
}

#[test]
fn values_dont_shadow_extern_crates() {
    let map = def_map(
        "
        //- /main.rs crate:main deps:foo
        fn foo() {}
        use foo::Bar;

        //- /foo/lib.rs crate:foo
        pub struct Bar;
        ",
    );

    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Bar: t v
        ⋮foo: v
    "###);
}

#[test]
fn std_prelude_takes_precedence_above_core_prelude() {
    let map = def_map(
        r#"
        //- /main.rs crate:main deps:core,std
        use {Foo, Bar};

        //- /std.rs crate:std deps:core
        #[prelude_import]
        pub use self::prelude::*;
        mod prelude {
            pub struct Foo;
            pub use core::prelude::Bar;
        }

        //- /core.rs crate:core
        #[prelude_import]
        pub use self::prelude::*;
        mod prelude {
            pub struct Bar;
        }
        "#,
    );

    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Bar: t v
        ⋮Foo: t v
    "###);
}

#[test]
fn cfg_not_test() {
    let map = def_map(
        r#"
        //- /main.rs crate:main deps:std
        use {Foo, Bar, Baz};

        //- /lib.rs crate:std
        #[prelude_import]
        pub use self::prelude::*;
        mod prelude {
            #[cfg(test)]
            pub struct Foo;
            #[cfg(not(test))]
            pub struct Bar;
            #[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))]
            pub struct Baz;
        }
        "#,
    );

    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Bar: t v
        ⋮Baz: _
        ⋮Foo: _
    "###);
}

#[test]
fn cfg_test() {
    let map = def_map(
        r#"
        //- /main.rs crate:main deps:std
        use {Foo, Bar, Baz};

        //- /lib.rs crate:std cfg:test,feature=foo,feature=bar,opt=42
        #[prelude_import]
        pub use self::prelude::*;
        mod prelude {
            #[cfg(test)]
            pub struct Foo;
            #[cfg(not(test))]
            pub struct Bar;
            #[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))]
            pub struct Baz;
        }
        "#,
    );

    assert_snapshot!(map, @r###"
        ⋮crate
        ⋮Bar: _
        ⋮Baz: t v
        ⋮Foo: t v
    "###);
}

#[test]
fn infer_multiple_namespace() {
    let map = def_map(
        r#"
//- /main.rs
mod a {
    pub type T = ();
    pub use crate::b::*;
}

use crate::a::T;

mod b {
    pub const T: () = ();
}
"#,
    );

    assert_snapshot!(map, @r###"
    ⋮crate
    ⋮T: t v
    ⋮a: t
    ⋮b: t
    ⋮
    ⋮crate::b
    ⋮T: v
    ⋮
    ⋮crate::a
    ⋮T: t v
"###);
}