use crate::error::Error;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{Data, DeriveInput, Error as SynError, Expr, Field, Fields, FieldsNamed, FieldsUnnamed};

pub struct Default;

impl Default {
    const ATTRIBUTE_NAME: &str = "default";

    fn std_default_field_value() -> TokenStream2 {
        quote::quote! { ::std::default::Default::default() }
    }

    fn default_field_value(field: &Field) -> Result<TokenStream2, SynError> {
        for attribute in &field.attrs {
            if attribute.path().is_ident(Self::ATTRIBUTE_NAME) {
                let default_field_value = attribute.parse_args::<Expr>()?;
                let default_field_value = quote::quote! { #default_field_value };

                return Ok(default_field_value);
            }
        }

        Ok(Self::std_default_field_value())
    }

    fn default_field_assignment(field: &Field) -> Result<TokenStream2, SynError> {
        let Some(field_name) = &field.ident else {
            return Err(Error::c_struct_field_missing_name(field));
        };
        let default_field_value = Self::default_field_value(field)?;
        let default_field_assignment = quote::quote! { #field_name: #default_field_value };

        Ok(default_field_assignment)
    }

    fn default_value_for_c_struct(fields_named: &FieldsNamed) -> Result<TokenStream2, SynError> {
        let default_field_assignments = fields_named
            .named
            .iter()
            .map(Self::default_field_assignment)
            .collect::<Result<Vec<TokenStream2>, SynError>>()?;
        let default_value = quote::quote! { Self { #(#default_field_assignments),* } };

        Ok(default_value)
    }

    fn default_value_for_tuple_struct(fields_unnamed: &FieldsUnnamed) -> Result<TokenStream2, SynError> {
        let field_values = fields_unnamed
            .unnamed
            .iter()
            .map(Self::default_field_value)
            .collect::<Result<Vec<TokenStream2>, SynError>>()?;
        let default_value = quote::quote! { Self(#(#field_values),*) };

        Ok(default_value)
    }

    fn default_value_for_unit_struct() -> TokenStream2 {
        quote::quote! { Self }
    }

    fn default_value(input: &DeriveInput) -> Result<TokenStream2, SynError> {
        let Data::Struct(data_struct) = &input.data else {
            return Err(Error::unsupported_item_type(input));
        };
        let default_value = match &data_struct.fields {
            Fields::Named(fields_named) => Self::default_value_for_c_struct(fields_named)?,
            Fields::Unnamed(fields_unnamed) => Self::default_value_for_tuple_struct(fields_unnamed)?,
            Fields::Unit => Self::default_value_for_unit_struct(),
        };

        Ok(default_value)
    }

    fn derive_impl(input: &DeriveInput) -> Result<TokenStream2, SynError> {
        let input_ident = &input.ident;
        let (impl_generics, input_generics, input_where_clause) = input.generics.split_for_impl();
        let default_value = Self::default_value(input)?;
        let default_impl_block_token_stream = quote::quote! {
            impl #impl_generics ::std::default::Default for #input_ident #input_generics #input_where_clause {
                fn default() -> Self {
                    #default_value
                }
            }
        };

        Ok(default_impl_block_token_stream)
    }

    pub fn derive(input_token_stream: TokenStream) -> TokenStream {
        let input = syn::parse_macro_input!(input_token_stream);

        Self::derive_impl(&input)
            .unwrap_or_else(SynError::into_compile_error)
            .into()
    }
}
