use ide_db::{famous_defs::FamousDefs, RootDatabase};
use syntax::ast::{self, AstNode, HasName};

use crate::{
    utils::generate_trait_impl_text_intransitive, AssistContext, AssistId, AssistKind, Assists,
};

// Assist: generate_from_impl_for_enum
//
// Adds a From impl for this enum variant with one tuple field.
//
// ```
// enum A { $0One(u32) }
// ```
// ->
// ```
// enum A { One(u32) }
//
// impl From<u32> for A {
//     fn from(v: u32) -> Self {
//         Self::One(v)
//     }
// }
// ```
pub(crate) fn generate_from_impl_for_enum(
    acc: &mut Assists,
    ctx: &AssistContext<'_>,
) -> Option<()> {
    let variant = ctx.find_node_at_offset::<ast::Variant>()?;
    let variant_name = variant.name()?;
    let enum_ = ast::Adt::Enum(variant.parent_enum());
    let (field_name, field_type) = match variant.kind() {
        ast::StructKind::Tuple(field_list) => {
            if field_list.fields().count() != 1 {
                return None;
            }
            (None, field_list.fields().next()?.ty()?)
        }
        ast::StructKind::Record(field_list) => {
            if field_list.fields().count() != 1 {
                return None;
            }
            let field = field_list.fields().next()?;
            (Some(field.name()?), field.ty()?)
        }
        ast::StructKind::Unit => return None,
    };

    if existing_from_impl(&ctx.sema, &variant).is_some() {
        cov_mark::hit!(test_add_from_impl_already_exists);
        return None;
    }

    let target = variant.syntax().text_range();
    acc.add(
        AssistId("generate_from_impl_for_enum", AssistKind::Generate),
        "Generate `From` impl for this enum variant",
        target,
        |edit| {
            let start_offset = variant.parent_enum().syntax().text_range().end();
            let from_trait = format!("From<{field_type}>");
            let impl_code = if let Some(name) = field_name {
                format!(
                    r#"    fn from({name}: {field_type}) -> Self {{
        Self::{variant_name} {{ {name} }}
    }}"#
                )
            } else {
                format!(
                    r#"    fn from(v: {field_type}) -> Self {{
        Self::{variant_name}(v)
    }}"#
                )
            };
            let from_impl = generate_trait_impl_text_intransitive(&enum_, &from_trait, &impl_code);
            edit.insert(start_offset, from_impl);
        },
    )
}

fn existing_from_impl(
    sema: &'_ hir::Semantics<'_, RootDatabase>,
    variant: &ast::Variant,
) -> Option<()> {
    let variant = sema.to_def(variant)?;
    let enum_ = variant.parent_enum(sema.db);
    let krate = enum_.module(sema.db).krate();

    let from_trait = FamousDefs(sema, krate).core_convert_From()?;

    let enum_type = enum_.ty(sema.db);

    let wrapped_type = variant.fields(sema.db).get(0)?.ty(sema.db);

    if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
        Some(())
    } else {
        None
    }
}

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

    use super::*;

    #[test]
    fn test_generate_from_impl_for_enum() {
        check_assist(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum A { $0One(u32) }
"#,
            r#"
enum A { One(u32) }

impl From<u32> for A {
    fn from(v: u32) -> Self {
        Self::One(v)
    }
}
"#,
        );
    }

    #[test]
    fn test_generate_from_impl_for_enum_complicated_path() {
        check_assist(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum A { $0One(foo::bar::baz::Boo) }
"#,
            r#"
enum A { One(foo::bar::baz::Boo) }

impl From<foo::bar::baz::Boo> for A {
    fn from(v: foo::bar::baz::Boo) -> Self {
        Self::One(v)
    }
}
"#,
        );
    }

    #[test]
    fn test_add_from_impl_no_element() {
        check_assist_not_applicable(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum A { $0One }
"#,
        );
    }

    #[test]
    fn test_add_from_impl_more_than_one_element_in_tuple() {
        check_assist_not_applicable(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum A { $0One(u32, String) }
"#,
        );
    }

    #[test]
    fn test_add_from_impl_struct_variant() {
        check_assist(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum A { $0One { x: u32 } }
"#,
            r#"
enum A { One { x: u32 } }

impl From<u32> for A {
    fn from(x: u32) -> Self {
        Self::One { x }
    }
}
"#,
        );
    }

    #[test]
    fn test_add_from_impl_already_exists() {
        cov_mark::check!(test_add_from_impl_already_exists);
        check_assist_not_applicable(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum A { $0One(u32), }

impl From<u32> for A {
    fn from(v: u32) -> Self {
        Self::One(v)
    }
}
"#,
        );
    }

    #[test]
    fn test_add_from_impl_different_variant_impl_exists() {
        check_assist(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum A { $0One(u32), Two(String), }

impl From<String> for A {
    fn from(v: String) -> Self {
        A::Two(v)
    }
}

pub trait From<T> {
    fn from(T) -> Self;
}
"#,
            r#"
enum A { One(u32), Two(String), }

impl From<u32> for A {
    fn from(v: u32) -> Self {
        Self::One(v)
    }
}

impl From<String> for A {
    fn from(v: String) -> Self {
        A::Two(v)
    }
}

pub trait From<T> {
    fn from(T) -> Self;
}
"#,
        );
    }

    #[test]
    fn test_add_from_impl_static_str() {
        check_assist(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum A { $0One(&'static str) }
"#,
            r#"
enum A { One(&'static str) }

impl From<&'static str> for A {
    fn from(v: &'static str) -> Self {
        Self::One(v)
    }
}
"#,
        );
    }

    #[test]
    fn test_add_from_impl_generic_enum() {
        check_assist(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum Generic<T, U: Clone> { $0One(T), Two(U) }
"#,
            r#"
enum Generic<T, U: Clone> { One(T), Two(U) }

impl<T, U: Clone> From<T> for Generic<T, U> {
    fn from(v: T) -> Self {
        Self::One(v)
    }
}
"#,
        );
    }

    #[test]
    fn test_add_from_impl_with_lifetime() {
        check_assist(
            generate_from_impl_for_enum,
            r#"
//- minicore: from
enum Generic<'a> { $0One(&'a i32) }
"#,
            r#"
enum Generic<'a> { One(&'a i32) }

impl<'a> From<&'a i32> for Generic<'a> {
    fn from(v: &'a i32) -> Self {
        Self::One(v)
    }
}
"#,
        );
    }
}
