use crate::builder::builder_gen::NamedMember;
use crate::util::prelude::*;

impl super::BuilderGenCtx {
    pub(super) fn builder_decl(&self) -> TokenStream {
        let builder_vis = &self.builder_type.vis;
        let builder_ident = &self.builder_type.ident;
        let generics_decl = &self.generics.decl_with_defaults;
        let where_clause = &self.generics.where_clause;
        let phantom_data = self.phantom_data();
        let state_mod = &self.state_mod.ident;

        // The fields can't be hidden using Rust's privacy syntax.
        // The details about this are described in the blog post:
        // https://bon-rs.com/blog/the-weird-of-function-local-types-in-rust.
        //
        // We could use `#[cfg(not(rust_analyzer))]` to hide the private fields in IDE.
        // However, RA would then not be able to type-check the generated code, which
        // may or may not be a problem, because the main thing is that the type signatures
        // would still work in RA.
        let private_field_attrs = {
            // The message is defined separately to make it single-line in the
            // generated code. This simplifies the task of removing unnecessary
            // attributes from the generated code when preparing for demo purposes.
            let deprecated_msg = "\
                this field should not be used directly; it's an implementation detail, and \
                if you access it directly, you may break some internal unsafe invariants; \
                if you found yourself needing it, then you are probably doing something wrong; \
                feel free to open an issue/discussion in our GitHub repository \
                (https://github.com/elastio/bon) or ask for help in our Discord server \
                (https://bon-rs.com/discord)";

            quote! {
                #[doc(hidden)]
                #[deprecated = #deprecated_msg]
            }
        };

        let receiver_field = self.receiver().map(|receiver| {
            let ident = &receiver.field_ident;
            let ty = &receiver.without_self_keyword;
            quote! {
                #ident: #ty,
            }
        });

        let must_use_message = format!(
            "the builder does nothing until you call `{}()` on it to finish building",
            self.finish_fn.ident
        );

        let allows = super::allow_warnings_on_member_types();

        let start_fn_args_fields_idents = self.start_fn_args().map(|member| &member.ident);
        let start_fn_args_fields_types = self.start_fn_args().map(|member| &member.ty.norm);

        let named_members_types = self.named_members().map(NamedMember::underlying_norm_ty);

        let docs = &self.builder_type.docs;
        let state_var = &self.state_var;

        let custom_fields_idents = self.custom_fields().map(|field| &field.ident);
        let custom_fields_types = self.custom_fields().map(|field| &field.norm_ty);

        quote! {
            #[must_use = #must_use_message]
            #(#docs)*
            #allows
            #[allow(
                // We use `__private` prefix for all fields intentionally to hide them
                clippy::struct_field_names,

                // This lint doesn't emerge until you manually expand the macro. Just
                // because `bon` developers need to expand the macros a lot it makes
                // sense to just silence it to avoid some noise. This lint is triggered
                // by the big PhantomData type generated by the macro
                clippy::type_complexity
            )]
            #builder_vis struct #builder_ident<
                #(#generics_decl,)*
                // Having the `State` trait bound on the struct declaration is important
                // for future proofing. It will allow us to use this bound in the `Drop`
                // implementation of the builder if we ever add one. @Veetaha already did
                // some experiments with `MaybeUninit` that requires a custom drop impl,
                // so this could be useful in the future.
                //
                // On the flip side, if we have a custom `Drop` impl, then partially moving
                // the builder will be impossible. So.. it's a trade-off, and it's probably
                // not a big deal to remove this bound from here if we feel like it.
                #state_var: #state_mod::State = #state_mod::Empty
            >
            #where_clause
            {
                #private_field_attrs
                __unsafe_private_phantom: #phantom_data,

                #receiver_field

                #( #start_fn_args_fields_idents: #start_fn_args_fields_types, )*

                #( #custom_fields_idents: #custom_fields_types, )*

                #private_field_attrs
                __unsafe_private_named: (
                    #(
                        ::core::option::Option<#named_members_types>,
                    )*
                ),
            }
        }
    }
}
