use hir::{HasCrate, ModuleDef, Semantics};
use ide_db::use_trivial_constructor::use_trivial_constructor_with_factory;
use ide_db::{
    RootDatabase, famous_defs::FamousDefs, helpers::mod_path_to_ast_with_factory,
    imports::import_assets::item_for_path_search,
};
use syntax::ast::{
    self, AstNode, HasAttrs, HasGenericParams, HasName, edit::AstNodeEdit,
    syntax_factory::SyntaxFactory,
};
use syntax::syntax_editor::{Position, SyntaxEditor};

use crate::{
    AssistId,
    assist_context::{AssistContext, Assists},
};

// Assist: generate_single_field_struct_from
//
// Implement From for a single field structure, ignore trivial types.
//
// ```
// # //- minicore: from, phantom_data
// use core::marker::PhantomData;
// struct $0Foo<T> {
//     id: i32,
//     _phantom_data: PhantomData<T>,
// }
// ```
// ->
// ```
// use core::marker::PhantomData;
// struct Foo<T> {
//     id: i32,
//     _phantom_data: PhantomData<T>,
// }
//
// impl<T> From<i32> for Foo<T> {
//     fn from(id: i32) -> Self {
//         Self { id, _phantom_data: PhantomData }
//     }
// }
// ```
pub(crate) fn generate_single_field_struct_from(
    acc: &mut Assists,
    ctx: &AssistContext<'_, '_>,
) -> Option<()> {
    let strukt_name = ctx.find_node_at_offset::<ast::Name>()?;
    let adt = ast::Adt::cast(strukt_name.syntax().parent()?)?;
    let ast::Adt::Struct(strukt) = adt else {
        tracing::debug!(?adt);
        return None;
    };

    let sema = &ctx.sema;
    let (names, types) = get_fields(&strukt)?;

    let module = sema.scope(strukt.syntax())?.module();
    let constructors = make_constructors(ctx, module, &types);

    if constructors.iter().filter(|expr| expr.is_none()).count() != 1 {
        tracing::debug!(?constructors);
        return None;
    }
    let main_field_i = constructors.iter().position(Option::is_none)?;
    if from_impl_exists(&strukt, main_field_i, &ctx.sema).is_some() {
        tracing::debug!(?strukt, ?main_field_i);
        return None;
    }

    let main_field_name = names.as_ref().map_or("value", |names| names[main_field_i].text());
    let main_field_ty = types[main_field_i].clone();

    acc.add(
        AssistId::generate("generate_single_field_struct_from"),
        "Generate single field `From`",
        strukt.syntax().text_range(),
        |builder| {
            let editor = builder.make_editor(strukt.syntax());
            let make = editor.make();

            let indent = strukt.indent_level();
            let ty_where_clause = strukt.where_clause();
            let type_gen_params = strukt.generic_param_list();
            let type_gen_args = type_gen_params.as_ref().map(|params| params.to_generic_args(make));
            let trait_gen_args = Some(make.generic_arg_list(
                [ast::GenericArg::TypeArg(make.type_arg(main_field_ty.clone()))],
                false,
            ));

            let ty = make.ty(strukt_name.text());

            let constructor =
                make_adt_constructor(names.as_deref(), constructors, main_field_name, make);
            let body = make.block_expr([], Some(constructor));

            let fn_ = make
                .fn_(
                    [],
                    None,
                    make.name("from"),
                    None,
                    None,
                    make.param_list(
                        None,
                        [make.param(
                            make.path_pat(make.path_from_text(main_field_name)),
                            main_field_ty,
                        )],
                    ),
                    body,
                    Some(make.ret_type(make.ty("Self"))),
                    false,
                    false,
                    false,
                    false,
                )
                .indent_with_mapping(1.into(), make);

            let cfg_attrs =
                strukt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_))));

            let impl_ = make.impl_trait(
                cfg_attrs,
                false,
                None,
                trait_gen_args,
                type_gen_params,
                type_gen_args,
                false,
                make.ty("From"),
                ty.clone(),
                None,
                ty_where_clause.map(|wc| wc.reset_indent()),
                None,
            );

            let (impl_editor, impl_root) = SyntaxEditor::with_ast_node(&impl_);
            let assoc_list = impl_root.get_or_create_assoc_item_list_with_editor(&impl_editor);
            assoc_list.add_items(&impl_editor, vec![fn_.into()]);
            let impl_ = ast::Impl::cast(impl_editor.finish().new_root().clone())
                .unwrap()
                .indent_with_mapping(indent, make);

            editor.insert_all(
                Position::after(strukt.syntax()),
                vec![
                    make.whitespace(&format!("\n\n{indent}")).into(),
                    impl_.syntax().clone().into(),
                ],
            );
            builder.add_file_edits(ctx.vfs_file_id(), editor);
        },
    )
}

fn make_adt_constructor(
    names: Option<&[ast::Name]>,
    constructors: Vec<Option<ast::Expr>>,
    main_field_name: &str,
    make: &SyntaxFactory,
) -> ast::Expr {
    if let Some(names) = names {
        let fields = make.record_expr_field_list(names.iter().zip(constructors).map(
            |(name, initializer)| make.record_expr_field(make.name_ref(name.text()), initializer),
        ));
        make.record_expr(make.path_from_text("Self"), fields).into()
    } else {
        let arg_list = make.arg_list(constructors.into_iter().map(|expr| {
            expr.unwrap_or_else(|| make.expr_path(make.path_from_text(main_field_name)))
        }));
        make.expr_call(make.expr_path(make.path_from_text("Self")), arg_list).into()
    }
}

fn make_constructors(
    ctx: &AssistContext<'_, '_>,
    module: hir::Module,
    types: &[ast::Type],
) -> Vec<Option<ast::Expr>> {
    let make = SyntaxFactory::without_mappings();
    let (db, sema) = (ctx.db(), &ctx.sema);
    let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
    types
        .iter()
        .map(|ty| {
            let ty = sema.resolve_type(ty)?;
            if ty.is_unit() {
                return Some(make.expr_tuple([]).into());
            }
            let item_in_ns = ModuleDef::Adt(ty.as_adt()?).into();
            let edition = module.krate(db).edition(db);

            let ty_path = module.find_path(db, item_for_path_search(db, item_in_ns)?, cfg)?;

            use_trivial_constructor_with_factory(
                &make,
                db,
                mod_path_to_ast_with_factory(&make, &ty_path, edition),
                &ty,
                edition,
            )
        })
        .collect()
}

fn get_fields(strukt: &ast::Struct) -> Option<(Option<Vec<ast::Name>>, Vec<ast::Type>)> {
    Some(match strukt.kind() {
        ast::StructKind::Unit => return None,
        ast::StructKind::Record(fields) => {
            let names = fields.fields().map(|field| field.name()).collect::<Option<_>>()?;
            let types = fields.fields().map(|field| field.ty()).collect::<Option<_>>()?;
            (Some(names), types)
        }
        ast::StructKind::Tuple(fields) => {
            (None, fields.fields().map(|field| field.ty()).collect::<Option<_>>()?)
        }
    })
}

#[tracing::instrument(ret)]
fn from_impl_exists(
    strukt: &ast::Struct,
    main_field_i: usize,
    sema: &Semantics<'_, RootDatabase>,
) -> Option<()> {
    let db = sema.db;
    let strukt = sema.to_def(strukt)?;
    let krate = strukt.krate(db);
    let from_trait = FamousDefs(sema, krate).core_convert_From()?;

    let field_ty = strukt.fields(db).get(main_field_i)?.ty(db);
    let struct_ty = strukt.ty(db);
    tracing::debug!(?strukt, ?field_ty, ?struct_ty);
    struct_ty.has_any_impl(db, from_trait, &[field_ty]).then_some(())
}

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

    use super::generate_single_field_struct_from;

    #[test]
    fn works() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo {
                foo: i32,
            }
            "#,
            r#"
            struct Foo {
                foo: i32,
            }

            impl From<i32> for Foo {
                fn from(foo: i32) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from, phantom_data
            struct $0Foo {
                b1: (),
                b2: core::marker::PhantomData,
                foo: i32,
                a1: (),
                a2: core::marker::PhantomData,
            }
            "#,
            r#"
            struct Foo {
                b1: (),
                b2: core::marker::PhantomData,
                foo: i32,
                a1: (),
                a2: core::marker::PhantomData,
            }

            impl From<i32> for Foo {
                fn from(foo: i32) -> Self {
                    Self { b1: (), b2: core::marker::PhantomData, foo, a1: (), a2: core::marker::PhantomData }
                }
            }
            "#,
        );
    }

    #[test]
    fn cfgs() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            #[cfg(feature = "foo")]
            #[cfg(test)]
            struct $0Foo {
                foo: i32,
            }
            "#,
            r#"
            #[cfg(feature = "foo")]
            #[cfg(test)]
            struct Foo {
                foo: i32,
            }

            #[cfg(feature = "foo")]
            #[cfg(test)]
            impl From<i32> for Foo {
                fn from(foo: i32) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
    }

    #[test]
    fn indent() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            mod foo {
                struct $0Foo {
                    foo: i32,
                }
            }
            "#,
            r#"
            mod foo {
                struct Foo {
                    foo: i32,
                }

                impl From<i32> for Foo {
                    fn from(foo: i32) -> Self {
                        Self { foo }
                    }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            mod foo {
                mod bar {
                    struct $0Foo {
                        foo: i32,
                    }
                }
            }
            "#,
            r#"
            mod foo {
                mod bar {
                    struct Foo {
                        foo: i32,
                    }

                    impl From<i32> for Foo {
                        fn from(foo: i32) -> Self {
                            Self { foo }
                        }
                    }
                }
            }
            "#,
        );
    }

    #[test]
    fn where_clause_indent() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            mod foo {
                mod bar {
                    trait Trait {}
                    struct $0Foo<T>
                    where
                        T: Trait,
                    {
                        foo: T,
                    }
                }
            }
            "#,
            r#"
            mod foo {
                mod bar {
                    trait Trait {}
                    struct Foo<T>
                    where
                        T: Trait,
                    {
                        foo: T,
                    }

                    impl<T> From<T> for Foo<T>
                    where
                        T: Trait,
                    {
                        fn from(foo: T) -> Self {
                            Self { foo }
                        }
                    }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            mod foo {
                mod bar {
                    trait Trait<const B: bool> {}
                    struct $0Foo<T>
                    where
                        T: Trait<{
                            true
                        }>
                    {
                        foo: T,
                    }
                }
            }
            "#,
            r#"
            mod foo {
                mod bar {
                    trait Trait<const B: bool> {}
                    struct Foo<T>
                    where
                        T: Trait<{
                            true
                        }>
                    {
                        foo: T,
                    }

                    impl<T> From<T> for Foo<T>
                    where
                        T: Trait<{
                            true
                        }>
                    {
                        fn from(foo: T) -> Self {
                            Self { foo }
                        }
                    }
                }
            }
            "#,
        );
    }

    #[test]
    fn generics() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T> {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T> {
                foo: T,
            }

            impl<T> From<T> for Foo<T> {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send> {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send> {
                foo: T,
            }

            impl<T: Send> From<T> for Foo<T> {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send> where T: Sync,{
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send> where T: Sync,{
                foo: T,
            }

            impl<T: Send> From<T> for Foo<T>
            where T: Sync,
            {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send> where T: Sync {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send> where T: Sync {
                foo: T,
            }

            impl<T: Send> From<T> for Foo<T>
            where T: Sync
            {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send> where T: Sync, Self: Send {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send> where T: Sync, Self: Send {
                foo: T,
            }

            impl<T: Send> From<T> for Foo<T>
            where T: Sync, Self: Send
            {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send>
            where T: Sync, Self: Send
            {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send>
            where T: Sync, Self: Send
            {
                foo: T,
            }

            impl<T: Send> From<T> for Foo<T>
            where T: Sync, Self: Send
            {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send>
            where T: Sync, Self: Send,
            {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send>
            where T: Sync, Self: Send,
            {
                foo: T,
            }

            impl<T: Send> From<T> for Foo<T>
            where T: Sync, Self: Send,
            {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send>
            where T: Sync,
                  Self: Send,
            {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send>
            where T: Sync,
                  Self: Send,
            {
                foo: T,
            }

            impl<T: Send> From<T> for Foo<T>
            where T: Sync,
                  Self: Send,
            {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send>
            where
                T: Sync,
                Self: Send,
            {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send>
            where
                T: Sync,
                Self: Send,
            {
                foo: T,
            }

            impl<T: Send> From<T> for Foo<T>
            where
                T: Sync,
                Self: Send,
            {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T: Send + Sync>
            where
                T: Sync,
                Self: Send,
            {
                foo: T,
            }
            "#,
            r#"
            struct Foo<T: Send + Sync>
            where
                T: Sync,
                Self: Send,
            {
                foo: T,
            }

            impl<T: Send + Sync> From<T> for Foo<T>
            where
                T: Sync,
                Self: Send,
            {
                fn from(foo: T) -> Self {
                    Self { foo }
                }
            }
            "#,
        );
    }

    #[test]
    fn tuple() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo(i32);
            "#,
            r#"
            struct Foo(i32);

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self(value)
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T>(T);
            "#,
            r#"
            struct Foo<T>(T);

            impl<T> From<T> for Foo<T> {
                fn from(value: T) -> Self {
                    Self(value)
                }
            }
            "#,
        );
    }

    #[test]
    fn trivial() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from, phantom_data
            use core::marker::PhantomData;
            struct $0Foo(i32, PhantomData<i32>);
            "#,
            r#"
            use core::marker::PhantomData;
            struct Foo(i32, PhantomData<i32>);

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self(value, PhantomData)
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from, phantom_data
            use core::marker::PhantomData;
            struct $0Foo(i32, PhantomData<()>);
            "#,
            r#"
            use core::marker::PhantomData;
            struct Foo(i32, PhantomData<()>);

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self(value, PhantomData)
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from, phantom_data
            use core::marker::PhantomData;
            struct $0Foo(PhantomData<()>, i32, PhantomData<()>);
            "#,
            r#"
            use core::marker::PhantomData;
            struct Foo(PhantomData<()>, i32, PhantomData<()>);

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self(PhantomData, value, PhantomData)
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from, phantom_data
            use core::marker::PhantomData;
            struct $0Foo<T>(PhantomData<T>, i32, PhantomData<()>);
            "#,
            r#"
            use core::marker::PhantomData;
            struct Foo<T>(PhantomData<T>, i32, PhantomData<()>);

            impl<T> From<i32> for Foo<T> {
                fn from(value: i32) -> Self {
                    Self(PhantomData, value, PhantomData)
                }
            }
            "#,
        );
    }

    #[test]
    fn unit() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo(i32, ());
            "#,
            r#"
            struct Foo(i32, ());

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self(value, ())
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo((), i32, ());
            "#,
            r#"
            struct Foo((), i32, ());

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self((), value, ())
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo((), (), i32, ());
            "#,
            r#"
            struct Foo((), (), i32, ());

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self((), (), value, ())
                }
            }
            "#,
        );
    }

    #[test]
    fn invalid_multiple_main_field() {
        check_assist_not_applicable(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo(i32, i32);
            "#,
        );
        check_assist_not_applicable(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T>(i32, T);
            "#,
        );
        check_assist_not_applicable(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T>(T, T);
            "#,
        );
        check_assist_not_applicable(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo<T> { foo: T, bar: i32 }
            "#,
        );
        check_assist_not_applicable(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo { foo: i32, bar: i64 }
            "#,
        );
    }

    #[test]
    fn exists_other_from() {
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo(i32);

            impl From<&i32> for Foo {
                fn from(value: &i32) -> Self {
                    todo!()
                }
            }
            "#,
            r#"
            struct Foo(i32);

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self(value)
                }
            }

            impl From<&i32> for Foo {
                fn from(value: &i32) -> Self {
                    todo!()
                }
            }
            "#,
        );
        check_assist(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo(i32);

            type X = i32;

            impl From<&X> for Foo {
                fn from(value: &X) -> Self {
                    todo!()
                }
            }
            "#,
            r#"
            struct Foo(i32);

            impl From<i32> for Foo {
                fn from(value: i32) -> Self {
                    Self(value)
                }
            }

            type X = i32;

            impl From<&X> for Foo {
                fn from(value: &X) -> Self {
                    todo!()
                }
            }
            "#,
        );
    }

    #[test]
    fn exists_from() {
        check_assist_not_applicable(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo(i32);

            impl From<i32> for Foo {
                fn from(_: i32) -> Self {
                    todo!()
                }
            }
            "#,
        );
        check_assist_not_applicable(
            generate_single_field_struct_from,
            r#"
            //- minicore: from
            struct $0Foo(i32);

            type X = i32;

            impl From<X> for Foo {
                fn from(_: X) -> Self {
                    todo!()
                }
            }
            "#,
        );
    }
}
