use expect_test::{Expect, expect};
use span::Edition;
use test_fixture::WithFixture;

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

fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
    let (db, file_id) = TestDB::with_single_file(ra_fixture);
    let item_tree = db.file_item_tree(file_id.into(), db.test_crate());
    let pretty = item_tree.pretty_print(&db, Edition::CURRENT);
    expect.assert_eq(&pretty);
}

#[test]
fn imports() {
    check(
        r#"
//! file comment
#![no_std]
//! another file comment

extern crate self as renamed;
pub(super) extern crate bli;

pub use crate::path::{nested, items as renamed, Trait as _};
use globs::*;

/// docs on import
use crate::{A, B};

use a::{c, d::{e}};
        "#,
        expect![[r#"
            #![no_std]

            // AstId: ExternCrate[070B, 0]
            pub(self) extern crate self as renamed;

            // AstId: ExternCrate[1EA5, 0]
            pub(in super) extern crate bli;

            // AstId: Use[0000, 0]
            pub use crate::path::{nested, items as renamed, Trait as _};

            // AstId: Use[0000, 1]
            pub(self) use globs::*;

            // AstId: Use[0000, 2]
            pub(self) use crate::{A, B};

            // AstId: Use[0000, 3]
            pub(self) use a::{c, d::{e}};
        "#]],
    );
}

#[test]
fn extern_blocks() {
    check(
        r#"
#[on_extern_block]
extern "C" {
    #[on_extern_type]
    type ExType;

    #[on_extern_static]
    static EX_STATIC: u8;

    #[on_extern_fn]
    fn ex_fn();
}
        "#,
        expect![[r#"
            #[on_extern_block]
            // AstId: ExternBlock[0000, 0]
            extern {
                #[on_extern_type]
                // AstId: TypeAlias[A09C, 0]
                pub(self) type ExType;

                #[on_extern_static]
                // AstId: Static[D85E, 0]
                pub(self) static EX_STATIC = _;

                #[on_extern_fn]
                // AstId: Fn[B240, 0]
                pub(self) fn ex_fn;
            }
        "#]],
    );
}

#[test]
fn adts() {
    check(
        r#"
struct Unit;

#[derive(Debug)]
struct Struct {
    /// fld docs
    fld: (),
}

struct Tuple(#[attr] u8);

union Ize {
    a: (),
    b: (),
}

enum E {
    /// comment on Unit
    Unit,
    /// comment on Tuple
    Tuple(u8),
    Struct {
        /// comment on a: u8
        a: u8,
    }
}
        "#,
        expect![[r#"
            // AstId: Struct[ED35, 0]
            pub(self) struct Unit;

            #[derive(Debug)]
            // AstId: Struct[A47C, 0]
            pub(self) struct Struct { ... }

            // AstId: Struct[C8C9, 0]
            pub(self) struct Tuple(...);

            // AstId: Union[2797, 0]
            pub(self) union Ize { ... }

            // AstId: Enum[7D23, 0]
            pub(self) enum E { ... }
        "#]],
    );
}

#[test]
fn misc() {
    check(
        r#"
pub static mut ST: () = ();

const _: Anon = ();

#[attr]
fn f(#[attr] arg: u8, _: ()) {
    #![inner_attr_in_fn]
}

trait Tr: SuperTrait + 'lifetime {
    type Assoc: AssocBound = Default;
    fn method(&self);
}
        "#,
        expect![[r#"
            // AstId: Static[F7C1, 0]
            pub static ST = _;

            // AstId: Const[84BB, 0]
            pub(self) const _ = _;

            #[attr]
            #[inner_attr_in_fn]
            // AstId: Fn[BE8F, 0]
            pub(self) fn f;

            // AstId: Trait[9320, 0]
            pub(self) trait Tr { ... }
        "#]],
    );
}

#[test]
fn modules() {
    check(
        r#"
/// outer
mod inline {
    //! inner

    use super::*;

    fn fn_in_module() {}
}

mod outline;
        "#,
        expect![[r##"
            // AstId: Module[03AE, 0]
            pub(self) mod inline {
                // AstId: Use[0000, 0]
                pub(self) use super::*;

                // AstId: Fn[2A78, 0]
                pub(self) fn fn_in_module;
            }

            // AstId: Module[C08B, 0]
            pub(self) mod outline;
        "##]],
    );
}

#[test]
fn macros() {
    check(
        r#"
macro_rules! m {
    () => {};
}

pub macro m2() {}

m!();
        "#,
        expect![[r#"
            // AstId: MacroRules[7E68, 0]
            macro_rules! m { ... }

            // AstId: MacroDef[1C1E, 0]
            pub macro m2 { ... }

            // AstId: MacroCall[7E68, 0], SyntaxContextId: ROOT2024, ExpandTo: Items
            m!(...);
        "#]],
    );
}

#[test]
fn pub_self() {
    check(
        r#"
pub(self) struct S;
        "#,
        expect![[r#"
            // AstId: Struct[5024, 0]
            pub(self) struct S;
        "#]],
    )
}

#[test]
fn crate_attrs_should_preserve_order() {
    check(
        r#"
//- /main.rs crate:foo crate-attr:no_std crate-attr:features(f16) crate-attr:crate_type="bin"
        "#,
        expect![[r##"
            #![no_std]
            #![features(f16)]
            #![crate_type = "bin"]
        "##]],
    );
}

#[test]
fn crate_attrs_with_disabled_cfg_injected() {
    check(
        r#"
//- /main.rs crate:foo crate-attr:no_std crate-attr:cfg(false) crate-attr:features(f16,f128) crate-attr:crate_type="bin"
        "#,
        expect![[r#"
            #![no_std]
            #![cfg(false)]
        "#]],
    );
}

#[test]
fn crate_attrs_with_disabled_cfg_in_source() {
    check(
        r#"
//- /lib.rs crate:foo crate-attr:no_std
#![cfg(false)]
#![no_core]
        "#,
        expect![[r#"
            #![no_std]
            #![cfg(false)]
        "#]],
    );
}
