//! Completion of names from the current scope, e.g. locals and imported items.

use std::iter;

use hir::{Adt, ModuleDef, ScopeDef, Type};
use syntax::AstNode;
use test_utils::mark;

use crate::{CompletionContext, Completions};

pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
    if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
        return;
    }
    if ctx.record_lit_syntax.is_some()
        || ctx.record_pat_syntax.is_some()
        || ctx.attribute_under_caret.is_some()
        || ctx.mod_declaration_under_caret.is_some()
    {
        return;
    }

    if let Some(ty) = &ctx.expected_type {
        complete_enum_variants(acc, ctx, ty);
    }

    if ctx.is_pat_binding_or_const {
        return;
    }

    ctx.scope.process_all_names(&mut |name, res| {
        if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res {
            mark::hit!(skip_lifetime_completion);
            return;
        }
        if ctx.use_item_syntax.is_some() {
            if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
                if name_ref.syntax().text() == name.to_string().as_str() {
                    mark::hit!(self_fulfilling_completion);
                    return;
                }
            }
        }
        acc.add_resolution(ctx, name.to_string(), &res);
    });
}

fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
    if let Some(Adt::Enum(enum_data)) =
        iter::successors(Some(ty.clone()), |ty| ty.remove_ref()).last().and_then(|ty| ty.as_adt())
    {
        let variants = enum_data.variants(ctx.db);

        let module = if let Some(module) = ctx.scope.module() {
            // Compute path from the completion site if available.
            module
        } else {
            // Otherwise fall back to the enum's definition site.
            enum_data.module(ctx.db)
        };

        for variant in variants {
            if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
                // Variants with trivial paths are already added by the existing completion logic,
                // so we should avoid adding these twice
                if path.segments.len() > 1 {
                    acc.add_qualified_enum_variant(ctx, variant, path);
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use expect_test::{expect, Expect};
    use test_utils::mark;

    use crate::{
        test_utils::{check_edit, completion_list_with_config, TEST_CONFIG},
        CompletionConfig, CompletionKind,
    };

    fn check(ra_fixture: &str, expect: Expect) {
        check_with_config(TEST_CONFIG, ra_fixture, expect);
    }

    fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) {
        let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference);
        expect.assert_eq(&actual)
    }

    #[test]
    fn self_fulfilling_completion() {
        mark::check!(self_fulfilling_completion);
        check(
            r#"
use foo$0
use std::collections;
"#,
            expect![[r#"
                ?? collections
            "#]],
        );
    }

    #[test]
    fn bind_pat_and_path_ignore_at() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(en$0 @ Enum::A) => (),
    }
}
"#,
            expect![[""]],
        );
    }

    #[test]
    fn bind_pat_and_path_ignore_ref() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(ref en$0) => (),
    }
}
"#,
            expect![[""]],
        );
    }

    #[test]
    fn bind_pat_and_path() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(En$0) => (),
    }
}
"#,
            expect![[r#"
                en Enum
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_let() {
        check(
            r#"
fn quux(x: i32) {
    let y = 92;
    1 + $0;
    let z = ();
}
"#,
            expect![[r#"
                lc y       i32
                lc x       i32
                fn quux(…) -> ()
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_if_let() {
        check(
            r#"
fn quux() {
    if let Some(x) = foo() {
        let y = 92;
    };
    if let Some(a) = bar() {
        let b = 62;
        1 + $0
    }
}
"#,
            expect![[r#"
                lc b      i32
                lc a
                fn quux() -> ()
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_for() {
        check(
            r#"
fn quux() {
    for x in &[1, 2, 3] { $0 }
}
"#,
            expect![[r#"
                lc x
                fn quux() -> ()
            "#]],
        );
    }

    #[test]
    fn completes_if_prefix_is_keyword() {
        mark::check!(completes_if_prefix_is_keyword);
        check_edit(
            "wherewolf",
            r#"
fn main() {
    let wherewolf = 92;
    drop(where$0)
}
"#,
            r#"
fn main() {
    let wherewolf = 92;
    drop(wherewolf)
}
"#,
        )
    }

    #[test]
    fn completes_generic_params() {
        check(
            r#"fn quux<T>() { $0 }"#,
            expect![[r#"
                tp T
                fn quux() -> ()
            "#]],
        );
        check(
            r#"fn quux<const C: usize>() { $0 }"#,
            expect![[r#"
                cp C
                fn quux() -> ()
            "#]],
        );
    }

    #[test]
    fn does_not_complete_lifetimes() {
        mark::check!(skip_lifetime_completion);
        check(
            r#"fn quux<'a>() { $0 }"#,
            expect![[r#"
                fn quux() -> ()
            "#]],
        );
    }

    #[test]
    fn completes_generic_params_in_struct() {
        check(
            r#"struct S<T> { x: $0}"#,
            expect![[r#"
                sp Self
                tp T
                st S<…>
            "#]],
        );
    }

    #[test]
    fn completes_self_in_enum() {
        check(
            r#"enum X { Y($0) }"#,
            expect![[r#"
                sp Self
                en X
            "#]],
        );
    }

    #[test]
    fn completes_module_items() {
        check(
            r#"
struct S;
enum E {}
fn quux() { $0 }
"#,
            expect![[r#"
                st S
                fn quux() -> ()
                en E
            "#]],
        );
    }

    /// Regression test for issue #6091.
    #[test]
    fn correctly_completes_module_items_prefixed_with_underscore() {
        check_edit(
            "_alpha",
            r#"
fn main() {
    _$0
}
fn _alpha() {}
"#,
            r#"
fn main() {
    _alpha()$0
}
fn _alpha() {}
"#,
        )
    }

    #[test]
    fn completes_extern_prelude() {
        check(
            r#"
//- /lib.rs crate:main deps:other_crate
use $0;

//- /other_crate/lib.rs crate:other_crate
// nothing here
"#,
            expect![[r#"
                md other_crate
            "#]],
        );
    }

    #[test]
    fn completes_module_items_in_nested_modules() {
        check(
            r#"
struct Foo;
mod m {
    struct Bar;
    fn quux() { $0 }
}
"#,
            expect![[r#"
                fn quux() -> ()
                st Bar
            "#]],
        );
    }

    #[test]
    fn completes_return_type() {
        check(
            r#"
struct Foo;
fn x() -> $0
"#,
            expect![[r#"
                st Foo
                fn x() -> ()
            "#]],
        );
    }

    #[test]
    fn dont_show_both_completions_for_shadowing() {
        check(
            r#"
fn foo() {
    let bar = 92;
    {
        let bar = 62;
        drop($0)
    }
}
"#,
            // FIXME: should be only one bar here
            expect![[r#"
                lc bar   i32
                lc bar   i32
                fn foo() -> ()
            "#]],
        );
    }

    #[test]
    fn completes_self_in_methods() {
        check(
            r#"impl S { fn foo(&self) { $0 } }"#,
            expect![[r#"
                lc self &{unknown}
                sp Self
            "#]],
        );
    }

    #[test]
    fn completes_prelude() {
        check(
            r#"
//- /main.rs crate:main deps:std
fn foo() { let x: $0 }

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

mod prelude { struct Option; }
"#,
            expect![[r#"
                fn foo()  -> ()
                md std
                st Option
            "#]],
        );
    }

    #[test]
    fn completes_prelude_macros() {
        check(
            r#"
//- /main.rs crate:main deps:std
fn f() {$0}

//- /std/lib.rs crate:std
#[prelude_import]
pub use prelude::*;

#[macro_use]
mod prelude {
    pub use crate::concat;
}

mod macros {
    #[rustc_builtin_macro]
    #[macro_export]
    macro_rules! concat { }
}
"#,
            expect![[r##"
                fn f()        -> ()
                ma concat!(…) #[macro_export] macro_rules! concat
                md std
            "##]],
        );
    }

    #[test]
    fn completes_std_prelude_if_core_is_defined() {
        check(
            r#"
//- /main.rs crate:main deps:core,std
fn foo() { let x: $0 }

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

mod prelude { struct Option; }

//- /std/lib.rs crate:std deps:core
#[prelude_import]
use prelude::*;

mod prelude { struct String; }
"#,
            expect![[r#"
                fn foo()  -> ()
                md std
                md core
                st String
            "#]],
        );
    }

    #[test]
    fn completes_macros_as_value() {
        check(
            r#"
macro_rules! foo { () => {} }

#[macro_use]
mod m1 {
    macro_rules! bar { () => {} }
}

mod m2 {
    macro_rules! nope { () => {} }

    #[macro_export]
    macro_rules! baz { () => {} }
}

fn main() { let v = $0 }
"#,
            expect![[r##"
                md m1
                ma baz!(…) #[macro_export] macro_rules! baz
                fn main()  -> ()
                md m2
                ma bar!(…) macro_rules! bar
                ma foo!(…) macro_rules! foo
            "##]],
        );
    }

    #[test]
    fn completes_both_macro_and_value() {
        check(
            r#"
macro_rules! foo { () => {} }
fn foo() { $0 }
"#,
            expect![[r#"
                fn foo()   -> ()
                ma foo!(…) macro_rules! foo
            "#]],
        );
    }

    #[test]
    fn completes_macros_as_type() {
        check(
            r#"
macro_rules! foo { () => {} }
fn main() { let x: $0 }
"#,
            expect![[r#"
                fn main()  -> ()
                ma foo!(…) macro_rules! foo
            "#]],
        );
    }

    #[test]
    fn completes_macros_as_stmt() {
        check(
            r#"
macro_rules! foo { () => {} }
fn main() { $0 }
"#,
            expect![[r#"
                fn main()  -> ()
                ma foo!(…) macro_rules! foo
            "#]],
        );
    }

    #[test]
    fn completes_local_item() {
        check(
            r#"
fn main() {
    return f$0;
    fn frobnicate() {}
}
"#,
            expect![[r#"
                fn frobnicate() -> ()
                fn main()       -> ()
            "#]],
        );
    }

    #[test]
    fn completes_in_simple_macro_1() {
        check(
            r#"
macro_rules! m { ($e:expr) => { $e } }
fn quux(x: i32) {
    let y = 92;
    m!($0);
}
"#,
            expect![[r#"
                lc y       i32
                lc x       i32
                fn quux(…) -> ()
                ma m!(…)   macro_rules! m
            "#]],
        );
    }

    #[test]
    fn completes_in_simple_macro_2() {
        check(
            r"
macro_rules! m { ($e:expr) => { $e } }
fn quux(x: i32) {
    let y = 92;
    m!(x$0);
}
",
            expect![[r#"
                lc y       i32
                lc x       i32
                fn quux(…) -> ()
                ma m!(…)   macro_rules! m
            "#]],
        );
    }

    #[test]
    fn completes_in_simple_macro_without_closing_parens() {
        check(
            r#"
macro_rules! m { ($e:expr) => { $e } }
fn quux(x: i32) {
    let y = 92;
    m!(x$0
}
"#,
            expect![[r#"
                lc y       i32
                lc x       i32
                fn quux(…) -> ()
                ma m!(…)   macro_rules! m
            "#]],
        );
    }

    #[test]
    fn completes_unresolved_uses() {
        check(
            r#"
use spam::Quux;

fn main() { $0 }
"#,
            expect![[r#"
                fn main() -> ()
                ?? Quux
            "#]],
        );
    }

    #[test]
    fn completes_enum_variant_matcharm() {
        check(
            r#"
enum Foo { Bar, Baz, Quux }

fn main() {
    let foo = Foo::Quux;
    match foo { Qu$0 }
}
"#,
            expect![[r#"
                ev Foo::Bar  ()
                ev Foo::Baz  ()
                ev Foo::Quux ()
                en Foo
            "#]],
        )
    }

    #[test]
    fn completes_enum_variant_matcharm_ref() {
        check(
            r#"
enum Foo { Bar, Baz, Quux }

fn main() {
    let foo = Foo::Quux;
    match &foo { Qu$0 }
}
"#,
            expect![[r#"
                ev Foo::Bar  ()
                ev Foo::Baz  ()
                ev Foo::Quux ()
                en Foo
            "#]],
        )
    }

    #[test]
    fn completes_enum_variant_iflet() {
        check(
            r#"
enum Foo { Bar, Baz, Quux }

fn main() {
    let foo = Foo::Quux;
    if let Qu$0 = foo { }
}
"#,
            expect![[r#"
                ev Foo::Bar  ()
                ev Foo::Baz  ()
                ev Foo::Quux ()
                en Foo
            "#]],
        )
    }

    #[test]
    fn completes_enum_variant_basic_expr() {
        check(
            r#"
enum Foo { Bar, Baz, Quux }
fn main() { let foo: Foo = Q$0 }
"#,
            expect![[r#"
                ev Foo::Bar  ()
                ev Foo::Baz  ()
                ev Foo::Quux ()
                en Foo
                fn main()    -> ()
            "#]],
        )
    }

    #[test]
    fn completes_enum_variant_from_module() {
        check(
            r#"
mod m { pub enum E { V } }
fn f() -> m::E { V$0 }
"#,
            expect![[r#"
                ev m::E::V ()
                md m
                fn f()     -> E
            "#]],
        )
    }

    #[test]
    fn dont_complete_attr() {
        check(
            r#"
struct Foo;
#[$0]
fn f() {}
"#,
            expect![[""]],
        )
    }

    #[test]
    fn completes_type_or_trait_in_impl_block() {
        check(
            r#"
trait MyTrait {}
struct MyStruct {}

impl My$0
"#,
            expect![[r#"
                sp Self
                tt MyTrait
                st MyStruct
            "#]],
        )
    }
}
