use either::Either;
use ide_db::{
    defs::{Definition, NameRefClass},
    search::FileReference,
};
use syntax::{
    SyntaxKind, T,
    algo::{next_non_trivia_token, previous_non_trivia_token},
    ast::{
        self, AstNode, HasArgList, HasAttrs, HasGenericParams, HasVisibility,
        syntax_factory::SyntaxFactory,
    },
    match_ast,
    syntax_editor::{Element, Position, SyntaxEditor},
};

use crate::{
    AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder, utils::cover_edit_range,
};

// Assist: convert_tuple_struct_to_named_struct
//
// Converts tuple struct to struct with named fields, and analogously for tuple enum variants.
//
// ```
// struct Point$0(f32, f32);
//
// impl Point {
//     pub fn new(x: f32, y: f32) -> Self {
//         Point(x, y)
//     }
//
//     pub fn x(&self) -> f32 {
//         self.0
//     }
//
//     pub fn y(&self) -> f32 {
//         self.1
//     }
// }
// ```
// ->
// ```
// struct Point { field1: f32, field2: f32 }
//
// impl Point {
//     pub fn new(x: f32, y: f32) -> Self {
//         Point { field1: x, field2: y }
//     }
//
//     pub fn x(&self) -> f32 {
//         self.field1
//     }
//
//     pub fn y(&self) -> f32 {
//         self.field2
//     }
// }
// ```
pub(crate) fn convert_tuple_struct_to_named_struct(
    acc: &mut Assists,
    ctx: &AssistContext<'_, '_>,
) -> Option<()> {
    let strukt_or_variant = ctx
        .find_node_at_offset::<ast::Struct>()
        .map(Either::Left)
        .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?;
    let field_list = strukt_or_variant.as_ref().either(|s| s.field_list(), |v| v.field_list())?;

    if ctx.offset() > field_list.syntax().text_range().start() {
        // Assist could be distracting after the braces
        return None;
    }

    let tuple_fields = match field_list {
        ast::FieldList::TupleFieldList(it) => it,
        ast::FieldList::RecordFieldList(_) => return None,
    };
    let strukt_def = match &strukt_or_variant {
        Either::Left(s) => Either::Left(ctx.sema.to_def(s)?),
        Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
    };
    let target = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range();
    let syntax = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax());
    acc.add(
        AssistId::refactor_rewrite("convert_tuple_struct_to_named_struct"),
        "Convert to named struct",
        target,
        |edit| {
            let editor = edit.make_editor(syntax);
            let names = generate_names(tuple_fields.fields(), editor.make());
            edit_field_references(ctx, edit, tuple_fields.fields(), &names);
            edit_struct_references(ctx, edit, strukt_def, &names);
            edit_struct_def(&editor, &strukt_or_variant, tuple_fields, names);
            edit.add_file_edits(ctx.vfs_file_id(), editor);
        },
    )
}

fn edit_struct_def(
    editor: &SyntaxEditor,
    strukt: &Either<ast::Struct, ast::Variant>,
    tuple_fields: ast::TupleFieldList,
    names: Vec<ast::Name>,
) {
    let make = editor.make();
    let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| {
        let (field_editor, field) =
            SyntaxEditor::with_ast_node(&make.record_field(f.visibility(), name, f.ty()?));
        field_editor.insert_all(
            Position::first_child_of(field.syntax()),
            f.attrs().map(|attr| attr.syntax().clone().into()).collect(),
        );
        ast::RecordField::cast(field_editor.finish().new_root().clone())
    });
    let record_fields = make.record_field_list(record_fields);
    let tuple_fields_before = Position::before(tuple_fields.syntax());

    if let Either::Left(strukt) = strukt {
        if let Some(w) = strukt.where_clause() {
            editor.delete(w.syntax());
            let mut insert_element = Vec::new();
            insert_element.push(make.whitespace("\n").syntax_element());
            insert_element.push(w.syntax().syntax_element());
            if w.syntax().last_token().is_none_or(|t| t.kind() != SyntaxKind::COMMA) {
                insert_element.push(make.token(T![,]).into());
            }
            insert_element.push(make.whitespace("\n").syntax_element());
            editor.insert_all(tuple_fields_before, insert_element);
        } else {
            editor.insert(tuple_fields_before, make.whitespace(" "));
        }
        if let Some(t) = strukt.semicolon_token() {
            editor.delete(t);
        }
    } else {
        editor.insert(tuple_fields_before, make.whitespace(" "));
    }

    editor.replace(tuple_fields.syntax(), record_fields.syntax());
}

fn edit_struct_references(
    ctx: &AssistContext<'_, '_>,
    edit: &mut SourceChangeBuilder,
    strukt: Either<hir::Struct, hir::EnumVariant>,
    names: &[ast::Name],
) {
    let strukt_def = match strukt {
        Either::Left(s) => Definition::Adt(hir::Adt::Struct(s)),
        Either::Right(v) => Definition::EnumVariant(v),
    };
    let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();

    for (file_id, refs) in usages {
        let source = ctx.sema.parse(file_id);
        let editor = edit.make_editor(source.syntax());

        for r in refs {
            process_struct_name_reference(ctx, r, &editor, &source, &strukt_def, names);
        }

        edit.add_file_edits(file_id.file_id(ctx.db()), editor);
    }
}

fn process_struct_name_reference(
    ctx: &AssistContext<'_, '_>,
    r: FileReference,
    editor: &SyntaxEditor,
    source: &ast::SourceFile,
    strukt_def: &Definition<'_>,
    names: &[ast::Name],
) -> Option<()> {
    let make = editor.make();
    let name_ref = r.name.as_name_ref()?;
    let path_segment = name_ref.syntax().parent().and_then(ast::PathSegment::cast)?;
    let full_path = path_segment.syntax().parent().and_then(ast::Path::cast)?.top_path();

    if full_path.segment()?.name_ref()? != *name_ref {
        // `name_ref` isn't the last segment of the path, so `full_path` doesn't point to the
        // struct we want to edit.
        return None;
    }

    let parent = full_path.syntax().parent()?;
    match_ast! {
        match parent {
            ast::TupleStructPat(tuple_struct_pat) => {
                let (targets, rest_pat) = tuple_pat_field_targets(&tuple_struct_pat, names);

                let mut first_insert = vec![];
                for (pat, name) in targets {
                    let range = ctx.sema.original_range_opt(pat.syntax())?.range;
                    let place = cover_edit_range(source.syntax(), range);
                    let elements = vec![
                        make.name_ref(name.text()).syntax().clone().into(),
                        make.token(T![:]).into(),
                        make.whitespace(" ").into(),
                    ];
                    if first_insert.is_empty() {
                        // XXX: SyntaxEditor cannot insert after deleted element
                        first_insert = elements;
                    } else {
                        editor.insert_all(Position::before(place.start()), elements);
                    }
                }

                // Record patterns only allow `..` as the trailing field, so it can't stay
                // in place like the other fields; relocate it to just before the closing brace.
                let mut trailing_insert = vec![];
                if let Some(rest_pat) = rest_pat {
                    if !first_insert.is_empty() {
                        trailing_insert.push(make.token(T![,]).into());
                        trailing_insert.push(make.whitespace(" ").into());
                    }
                    trailing_insert.push(make.rest_pat().syntax().clone().into());
                    delete_rest_pat(ctx, source, editor, &rest_pat);
                }

                let (l_paren, r_paren) = tuple_struct_pat_parens(ctx, source, &tuple_struct_pat)?;
                process_delimiter(editor, l_paren, r_paren, first_insert, trailing_insert);
            },
            ast::PathExpr(path_expr) => {
                let call_expr = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;

                // this also includes method calls like Foo::new(42), we should skip them
                match NameRefClass::classify(&ctx.sema, name_ref) {
                    Some(NameRefClass::Definition(Definition::SelfType(_), _)) => {},
                    Some(NameRefClass::Definition(def, _)) if def == *strukt_def => {},
                    _ => return None,
                }

                let arg_list = call_expr.arg_list()?;
                let mut first_insert = vec![];
                for (expr, name) in arg_list.args().zip(names) {
                    let range = ctx.sema.original_range_opt(expr.syntax())?.range;
                    let place = cover_edit_range(source.syntax(), range);
                    let elements = vec![
                        make.name_ref(name.text()).syntax().clone().into(),
                        make.token(T![:]).into(),
                        make.whitespace(" ").into(),
                    ];
                    if first_insert.is_empty() {
                        // XXX: SyntaxEditor cannot insert after deleted element
                        first_insert = elements;
                    } else {
                        editor.insert_all(Position::before(place.start()), elements);
                    }
                }
                let range = ctx.sema.original_range_opt(arg_list.syntax())?.range;
                let place = cover_edit_range(source.syntax(), range);
                let l_paren = first_token_of(place.start().clone())?;
                let r_paren = last_token_of(place.end().clone())?;
                process_delimiter(editor, l_paren, r_paren, first_insert, vec![]);
            },
            _ => {}
        }
    }
    Some(())
}

fn process_delimiter(
    editor: &SyntaxEditor,
    l_paren: syntax::SyntaxToken,
    r_paren: syntax::SyntaxToken,
    first_insert: Vec<syntax::SyntaxElement>,
    trailing_insert: Vec<syntax::SyntaxElement>,
) {
    let make = editor.make();
    if l_paren.kind() == T!['('] {
        let mut open_delim = vec![
            make.whitespace(" ").into(),
            make.token(T!['{']).into(),
            make.whitespace(" ").into(),
        ];
        open_delim.extend(first_insert);
        editor.replace_with_many(l_paren, open_delim);
    }
    if r_paren.kind() == T![')'] {
        let mut close_delim = trailing_insert;
        close_delim.push(make.whitespace(" ").into());
        close_delim.push(make.token(T!['}']).into());
        editor.replace_with_many(r_paren, close_delim);
    }
}

fn first_token_of(elem: syntax::SyntaxElement) -> Option<syntax::SyntaxToken> {
    match elem {
        syntax::NodeOrToken::Node(node) => node.first_token(),
        syntax::NodeOrToken::Token(t) => Some(t),
    }
}

fn last_token_of(elem: syntax::SyntaxElement) -> Option<syntax::SyntaxToken> {
    match elem {
        syntax::NodeOrToken::Node(node) => node.last_token(),
        syntax::NodeOrToken::Token(t) => Some(t),
    }
}

/// Locates the `(` and `)` delimiting `pat`'s fields, skipping over its path. Unlike
/// [`ast::ArgList`], a [`ast::TupleStructPat`] has no dedicated field-list node to anchor on.
fn tuple_struct_pat_parens(
    ctx: &AssistContext<'_, '_>,
    source: &ast::SourceFile,
    pat: &ast::TupleStructPat,
) -> Option<(syntax::SyntaxToken, syntax::SyntaxToken)> {
    let path_range = ctx.sema.original_range_opt(pat.path()?.syntax())?.range;
    let l_paren =
        next_non_trivia_token(cover_edit_range(source.syntax(), path_range).end().clone())?;

    let pat_range = ctx.sema.original_range_opt(pat.syntax())?.range;
    let r_paren = last_token_of(cover_edit_range(source.syntax(), pat_range).end().clone())?;
    Some((l_paren, r_paren))
}

/// Deletes `rest_pat` along with exactly one neighboring comma, so the surviving fields
/// keep a single separator between them (the caller re-inserts `..` at the new position).
fn delete_rest_pat(
    ctx: &AssistContext<'_, '_>,
    source: &ast::SourceFile,
    editor: &SyntaxEditor,
    rest_pat: &ast::RestPat,
) -> Option<()> {
    let range = ctx.sema.original_range_opt(rest_pat.syntax())?.range;
    let place = cover_edit_range(source.syntax(), range);
    editor.delete_all(place.clone());

    let following = next_non_trivia_token(place.end().clone()).filter(|t| t.kind() == T![,]);
    let preceding = previous_non_trivia_token(place.start().clone()).filter(|t| t.kind() == T![,]);
    if let Some(comma) = following.or(preceding) {
        if let Some(ws) = comma.next_token().filter(|t| t.kind() == SyntaxKind::WHITESPACE) {
            editor.delete(ws);
        }
        editor.delete(comma);
    }
    Some(())
}

fn edit_field_references(
    ctx: &AssistContext<'_, '_>,
    edit: &mut SourceChangeBuilder,
    fields: impl Iterator<Item = ast::TupleField>,
    names: &[ast::Name],
) {
    for (field, name) in fields.zip(names) {
        let field = match ctx.sema.to_def(&field) {
            Some(it) => it,
            None => continue,
        };
        let def = Definition::Field(field);
        let usages = def.usages(&ctx.sema).all();
        for (file_id, refs) in usages {
            let source = ctx.sema.parse(file_id);
            let editor = edit.make_editor(source.syntax());
            for r in refs {
                if let Some(name_ref) = r.name.as_name_ref()
                    && let Some(original) = ctx.sema.original_range_opt(name_ref.syntax())
                {
                    editor.replace_all(
                        cover_edit_range(source.syntax(), original.range),
                        vec![name.syntax().clone().into()],
                    );
                }
            }
            edit.add_file_edits(file_id.file_id(ctx.db()), editor);
        }
    }
}

fn generate_names(
    fields: impl Iterator<Item = ast::TupleField>,
    make: &SyntaxFactory,
) -> Vec<ast::Name> {
    fields
        .enumerate()
        .map(|(i, _)| {
            let idx = i + 1;
            make.name(&format!("field{idx}"))
        })
        .collect()
}

/// Pairs each non-`..` field with its generated name. The `..` rest pattern (if any) has
/// no name and is returned separately, since it needs different handling (see [`delete_rest_pat`]).
fn tuple_pat_field_targets(
    pat: &ast::TupleStructPat,
    names: &[ast::Name],
) -> (Vec<(ast::Pat, ast::Name)>, Option<ast::RestPat>) {
    let pure_fields = pat.fields().filter(|p| !matches!(p, ast::Pat::RestPat(_)));
    let rest_len = names.len().saturating_sub(pure_fields.clone().count());
    let rest_pat = pat.fields().find_map(|p| ast::RestPat::cast(p.syntax().clone()));
    let rest_idx =
        pat.fields().position(|p| ast::RestPat::can_cast(p.syntax().kind())).unwrap_or(names.len());
    let before_rest = pat.fields().zip(names.iter().cloned()).take(rest_idx);
    let after_rest = pure_fields.zip(names.iter().skip(rest_len).cloned()).skip(rest_idx);

    (before_rest.chain(after_rest).collect(), rest_pat)
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn not_applicable_other_than_tuple_struct() {
        check_assist_not_applicable(
            convert_tuple_struct_to_named_struct,
            r#"struct Foo$0 { bar: u32 };"#,
        );
        check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
    }
    #[test]
    fn convert_in_macro_args() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
macro_rules! foo {($i:expr) => {$i} }
struct T$0(u8);
fn test() {
    foo!(T(1));
}"#,
            r#"
macro_rules! foo {($i:expr) => {$i} }
struct T { field1: u8 }
fn test() {
    foo!(T { field1: 1 });
}"#,
        );
    }

    #[test]
    fn convert_simple_struct() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Inner;
struct A$0(Inner);

impl A {
    fn new(inner: Inner) -> A {
        A(inner)
    }

    fn new_with_default() -> A {
        A::new(Inner)
    }

    fn into_inner(self) -> Inner {
        self.0
    }
}"#,
            r#"
struct Inner;
struct A { field1: Inner }

impl A {
    fn new(inner: Inner) -> A {
        A { field1: inner }
    }

    fn new_with_default() -> A {
        A::new(Inner)
    }

    fn into_inner(self) -> Inner {
        self.field1
    }
}"#,
        );
    }

    #[test]
    fn convert_struct_and_rest_pat() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Inner;
struct A$0(Inner);
fn foo(A(..): A) {}
"#,
            r#"
struct Inner;
struct A { field1: Inner }
fn foo(A { .. }: A) {}
"#,
        );

        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct A;
struct B;
struct C;
struct D;
struct X$0(A, B, C, D);
fn foo(X(a, .., d): X) {}
"#,
            r#"
struct A;
struct B;
struct C;
struct D;
struct X { field1: A, field2: B, field3: C, field4: D }
fn foo(X { field1: a, field4: d, .. }: X) {}
"#,
        );
    }

    #[test]
    fn convert_simple_struct_cursor_on_struct_keyword() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Inner;
struct$0 A(Inner);

impl A {
    fn new(inner: Inner) -> A {
        A(inner)
    }

    fn new_with_default() -> A {
        A::new(Inner)
    }

    fn into_inner(self) -> Inner {
        self.0
    }
}"#,
            r#"
struct Inner;
struct A { field1: Inner }

impl A {
    fn new(inner: Inner) -> A {
        A { field1: inner }
    }

    fn new_with_default() -> A {
        A::new(Inner)
    }

    fn into_inner(self) -> Inner {
        self.field1
    }
}"#,
        );
    }

    #[test]
    fn convert_simple_struct_cursor_on_visibility_keyword() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Inner;
pub$0 struct A(Inner);

impl A {
    fn new(inner: Inner) -> A {
        A(inner)
    }

    fn new_with_default() -> A {
        A::new(Inner)
    }

    fn into_inner(self) -> Inner {
        self.0
    }
}"#,
            r#"
struct Inner;
pub struct A { field1: Inner }

impl A {
    fn new(inner: Inner) -> A {
        A { field1: inner }
    }

    fn new_with_default() -> A {
        A::new(Inner)
    }

    fn into_inner(self) -> Inner {
        self.field1
    }
}"#,
        );
    }

    #[test]
    fn convert_struct_referenced_via_self_kw() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Inner;
struct A$0(Inner);

impl A {
    fn new(inner: Inner) -> Self {
        Self(inner)
    }

    fn new_with_default() -> Self {
        Self::new(Inner)
    }

    fn into_inner(self) -> Inner {
        self.0
    }
}"#,
            r#"
struct Inner;
struct A { field1: Inner }

impl A {
    fn new(inner: Inner) -> Self {
        Self { field1: inner }
    }

    fn new_with_default() -> Self {
        Self::new(Inner)
    }

    fn into_inner(self) -> Inner {
        self.field1
    }
}"#,
        );
    }

    #[test]
    fn convert_destructured_struct() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Inner;
struct A$0(Inner);

impl A {
    fn into_inner(self) -> Inner {
        let A(first) = self;
        first
    }

    fn into_inner_via_self(self) -> Inner {
        let Self(first) = self;
        first
    }
}"#,
            r#"
struct Inner;
struct A { field1: Inner }

impl A {
    fn into_inner(self) -> Inner {
        let A { field1: first } = self;
        first
    }

    fn into_inner_via_self(self) -> Inner {
        let Self { field1: first } = self;
        first
    }
}"#,
        );
    }

    #[test]
    fn convert_struct_with_visibility() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct A$0(pub u32, pub(crate) u64);

impl A {
    fn new() -> A {
        A(42, 42)
    }

    fn into_first(self) -> u32 {
        self.0
    }

    fn into_second(self) -> u64 {
        self.1
    }
}"#,
            r#"
struct A { pub field1: u32, pub(crate) field2: u64 }

impl A {
    fn new() -> A {
        A { field1: 42, field2: 42 }
    }

    fn into_first(self) -> u32 {
        self.field1
    }

    fn into_second(self) -> u64 {
        self.field2
    }
}"#,
        );
    }

    #[test]
    fn convert_struct_with_wrapped_references() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Inner$0(u32);
struct Outer(Inner);

impl Outer {
    fn new() -> Self {
        Self(Inner(42))
    }

    fn into_inner(self) -> u32 {
        (self.0).0
    }

    fn into_inner_destructed(self) -> u32 {
        let Outer(Inner(x)) = self;
        x
    }
}"#,
            r#"
struct Inner { field1: u32 }
struct Outer(Inner);

impl Outer {
    fn new() -> Self {
        Self(Inner { field1: 42 })
    }

    fn into_inner(self) -> u32 {
        (self.0).field1
    }

    fn into_inner_destructed(self) -> u32 {
        let Outer(Inner { field1: x }) = self;
        x
    }
}"#,
        );

        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Inner(u32);
struct Outer$0(Inner);

impl Outer {
    fn new() -> Self {
        Self(Inner(42))
    }

    fn into_inner(self) -> u32 {
        (self.0).0
    }

    fn into_inner_destructed(self) -> u32 {
        let Outer(Inner(x)) = self;
        x
    }
}"#,
            r#"
struct Inner(u32);
struct Outer { field1: Inner }

impl Outer {
    fn new() -> Self {
        Self { field1: Inner(42) }
    }

    fn into_inner(self) -> u32 {
        (self.field1).0
    }

    fn into_inner_destructed(self) -> u32 {
        let Outer { field1: Inner(x) } = self;
        x
    }
}"#,
        );
    }

    #[test]
    fn convert_struct_with_multi_file_references() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
//- /main.rs
struct Inner;
struct A$0(Inner);

mod foo;

//- /foo.rs
use crate::{A, Inner};
fn f() {
    let a = A(Inner);
}
"#,
            r#"
//- /main.rs
struct Inner;
struct A { field1: Inner }

mod foo;

//- /foo.rs
use crate::{A, Inner};
fn f() {
    let a = A { field1: Inner };
}
"#,
        );
    }

    #[test]
    fn convert_struct_with_where_clause() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
struct Wrap$0<T>(T)
where
    T: Display;
"#,
            r#"
struct Wrap<T>
where
    T: Display,
{ field1: T }

"#,
        );
    }

    #[test]
    fn convert_expr_uses_self() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
macro_rules! id {
    ($($t:tt)*) => { $($t)* }
}
struct T$0(u8);
fn test(t: T) {
    T(t.0);
    id!(T(t.0));
}"#,
            r#"
macro_rules! id {
    ($($t:tt)*) => { $($t)* }
}
struct T { field1: u8 }
fn test(t: T) {
    T { field1: t.field1 };
    id!(T { field1: t.field1 });
}"#,
        );
    }

    #[test]
    fn convert_pat_uses_self() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
macro_rules! id {
    ($($t:tt)*) => { $($t)* }
}
enum T {
    $0Value(&'static T),
    Nil,
}
fn test(t: T) {
    if let T::Value(T::Value(t)) = t {}
    if let id!(T::Value(T::Value(t))) = t {}
}"#,
            r#"
macro_rules! id {
    ($($t:tt)*) => { $($t)* }
}
enum T {
    Value { field1: &'static T },
    Nil,
}
fn test(t: T) {
    if let T::Value { field1: T::Value { field1: t } } = t {}
    if let id!(T::Value { field1: T::Value { field1: t } }) = t {}
}"#,
        );
    }

    #[test]
    fn not_applicable_other_than_tuple_variant() {
        check_assist_not_applicable(
            convert_tuple_struct_to_named_struct,
            r#"enum Enum { Variant$0 { value: usize } };"#,
        );
        check_assist_not_applicable(
            convert_tuple_struct_to_named_struct,
            r#"enum Enum { Variant$0 }"#,
        );
    }

    #[test]
    fn convert_variant_in_macro_args() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
macro_rules! foo {($i:expr) => {$i} }
enum T {
  V$0(u8)
}
fn test() {
    foo!(T::V(1));
}"#,
            r#"
macro_rules! foo {($i:expr) => {$i} }
enum T {
  V { field1: u8 }
}
fn test() {
    foo!(T::V { field1: 1 });
}"#,
        );
    }

    #[test]
    fn convert_simple_variant() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
enum A {
    $0Variant(usize),
}

impl A {
    fn new(value: usize) -> A {
        A::Variant(value)
    }

    fn new_with_default() -> A {
        A::new(Default::default())
    }

    fn value(self) -> usize {
        match self {
            A::Variant(value) => value,
        }
    }
}"#,
            r#"
enum A {
    Variant { field1: usize },
}

impl A {
    fn new(value: usize) -> A {
        A::Variant { field1: value }
    }

    fn new_with_default() -> A {
        A::new(Default::default())
    }

    fn value(self) -> usize {
        match self {
            A::Variant { field1: value } => value,
        }
    }
}"#,
        );
    }

    #[test]
    fn convert_variant_referenced_via_self_kw() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
enum A {
    $0Variant(usize),
}

impl A {
    fn new(value: usize) -> A {
        Self::Variant(value)
    }

    fn new_with_default() -> A {
        Self::new(Default::default())
    }

    fn value(self) -> usize {
        match self {
            Self::Variant(value) => value,
        }
    }
}"#,
            r#"
enum A {
    Variant { field1: usize },
}

impl A {
    fn new(value: usize) -> A {
        Self::Variant { field1: value }
    }

    fn new_with_default() -> A {
        Self::new(Default::default())
    }

    fn value(self) -> usize {
        match self {
            Self::Variant { field1: value } => value,
        }
    }
}"#,
        );
    }

    #[test]
    fn convert_destructured_variant() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
enum A {
    $0Variant(usize),
}

impl A {
    fn into_inner(self) -> usize {
        let A::Variant(first) = self;
        first
    }

    fn into_inner_via_self(self) -> usize {
        let Self::Variant(first) = self;
        first
    }
}"#,
            r#"
enum A {
    Variant { field1: usize },
}

impl A {
    fn into_inner(self) -> usize {
        let A::Variant { field1: first } = self;
        first
    }

    fn into_inner_via_self(self) -> usize {
        let Self::Variant { field1: first } = self;
        first
    }
}"#,
        );
    }

    #[test]
    fn convert_variant_with_wrapped_references() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
enum Inner {
    $0Variant(usize),
}
enum Outer {
    Variant(Inner),
}

impl Outer {
    fn new() -> Self {
        Self::Variant(Inner::Variant(42))
    }

    fn into_inner_destructed(self) -> u32 {
        let Outer::Variant(Inner::Variant(x)) = self;
        x
    }
}"#,
            r#"
enum Inner {
    Variant { field1: usize },
}
enum Outer {
    Variant(Inner),
}

impl Outer {
    fn new() -> Self {
        Self::Variant(Inner::Variant { field1: 42 })
    }

    fn into_inner_destructed(self) -> u32 {
        let Outer::Variant(Inner::Variant { field1: x }) = self;
        x
    }
}"#,
        );

        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
enum Inner {
    Variant(usize),
}
enum Outer {
    $0Variant(Inner),
}

impl Outer {
    fn new() -> Self {
        Self::Variant(Inner::Variant(42))
    }

    fn into_inner_destructed(self) -> u32 {
        let Outer::Variant(Inner::Variant(x)) = self;
        x
    }
}"#,
            r#"
enum Inner {
    Variant(usize),
}
enum Outer {
    Variant { field1: Inner },
}

impl Outer {
    fn new() -> Self {
        Self::Variant { field1: Inner::Variant(42) }
    }

    fn into_inner_destructed(self) -> u32 {
        let Outer::Variant { field1: Inner::Variant(x) } = self;
        x
    }
}"#,
        );
    }

    #[test]
    fn convert_variant_with_multi_file_references() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
//- /main.rs
struct Inner;
enum A {
    $0Variant(Inner),
}

mod foo;

//- /foo.rs
use crate::{A, Inner};
fn f() {
    let a = A::Variant(Inner);
}
"#,
            r#"
//- /main.rs
struct Inner;
enum A {
    Variant { field1: Inner },
}

mod foo;

//- /foo.rs
use crate::{A, Inner};
fn f() {
    let a = A::Variant { field1: Inner };
}
"#,
        );
    }

    #[test]
    fn convert_directly_used_variant() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
//- /main.rs
struct Inner;
enum A {
    $0Variant(Inner),
}

mod foo;

//- /foo.rs
use crate::{A::Variant, Inner};
fn f() {
    let a = Variant(Inner);
}
"#,
            r#"
//- /main.rs
struct Inner;
enum A {
    Variant { field1: Inner },
}

mod foo;

//- /foo.rs
use crate::{A::Variant, Inner};
fn f() {
    let a = Variant { field1: Inner };
}
"#,
        );
    }

    #[test]
    fn where_clause_with_trailing_comma() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
trait Foo {}

struct Bar$0<T>(pub T)
where
    T: Foo,;
"#,
            r#"
trait Foo {}

struct Bar<T>
where
    T: Foo,
{ pub field1: T }

"#,
        );
    }

    #[test]
    fn fields_with_attrs() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
pub struct $0Foo(#[my_custom_attr] u32);
"#,
            r#"
pub struct Foo { #[my_custom_attr]field1: u32 }
"#,
        );
    }

    #[test]
    fn convert_in_macro_pattern_args() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
macro_rules! foo {
    ($expression:expr, $pattern:pat) => {
        match $expression {
            $pattern => true,
            _ => false
        }
    };
}
enum Expr {
    A$0(usize),
}
fn main() {
    let e = Expr::A(0);
    foo!(e, Expr::A(0));
}
"#,
            r#"
macro_rules! foo {
    ($expression:expr, $pattern:pat) => {
        match $expression {
            $pattern => true,
            _ => false
        }
    };
}
enum Expr {
    A { field1: usize },
}
fn main() {
    let e = Expr::A { field1: 0 };
    foo!(e, Expr::A { field1: 0 });
}
"#,
        );
    }

    #[test]
    fn convert_in_multi_file_macro_pattern_args() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
//- /main.rs
mod foo;

enum Test {
    A$0(i32)
}

//- /foo.rs
use crate::Test;

macro_rules! foo {
    ($expression:expr, $pattern:pat) => {
        match $expression {
            $pattern => true,
            _ => false
        }
    };
}

fn foo() {
    let a = Test::A(0);
    foo!(a, Test::A(0));
}
"#,
            r#"
//- /main.rs
mod foo;

enum Test {
    A { field1: i32 }
}

//- /foo.rs
use crate::Test;

macro_rules! foo {
    ($expression:expr, $pattern:pat) => {
        match $expression {
            $pattern => true,
            _ => false
        }
    };
}

fn foo() {
    let a = Test::A { field1: 0 };
    foo!(a, Test::A { field1: 0 });
}
"#,
        );
    }

    #[test]
    fn regression_issue_21020() {
        check_assist(
            convert_tuple_struct_to_named_struct,
            r#"
pub struct S$0(pub ());

trait T {
    fn id(&self) -> usize;
}

trait T2 {
    fn foo(&self) -> usize;
}

impl T for S {
    fn id(&self) -> usize {
        self.0.len()
    }
}

impl T2 for S {
    fn foo(&self) -> usize {
        self.0.len()
    }
}
            "#,
            r#"
pub struct S { pub field1: () }

trait T {
    fn id(&self) -> usize;
}

trait T2 {
    fn foo(&self) -> usize;
}

impl T for S {
    fn id(&self) -> usize {
        self.field1.len()
    }
}

impl T2 for S {
    fn foo(&self) -> usize {
        self.field1.len()
    }
}
            "#,
        );
    }
}
