pub(crate) fn impl_macro(ast: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    use darling::FromDeriveInput;

    let attr = crate::attr::Container::from_derive_input(ast).unwrap();
    let envir = attr.envir();

    let fields = match ast.data {
        syn::Data::Struct(ref s) => &s.fields,
        _ => return crate::error(ast, "this derive macro only works on structs"),
    };

    if matches!(fields, syn::Fields::Unnamed(_)) {
        return crate::error(
            ast,
            "this derive macro only works on structs with named field",
        );
    }

    let export_body = fields
        .iter()
        .map(|x| gen_field(&attr, x))
        .collect::<Result<Vec<_>, _>>()?;

    let name = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

    let de = quote::quote! {
        #[automatically_derived]
        impl #impl_generics #envir::Serialize for #name #ty_generics #where_clause {
            fn collect(&self) -> ::std::collections::HashMap<String, String> {
                let mut hash_map = ::std::collections::HashMap::new();

                #(#export_body; )*

                hash_map
            }
        }
    };

    Ok(de)
}

fn gen_field(
    attr: &crate::attr::Container,
    field: &syn::Field,
) -> syn::Result<Option<proc_macro2::TokenStream>> {
    use darling::FromField;

    let envir = attr.envir();
    let field_attr = crate::attr::Field::from_field(field)?;

    if field_attr.skip || field_attr.skip_export {
        return Ok(None);
    }

    let name = &field.ident;
    let var = format!(
        "{}{}",
        attr.prefix.as_deref().unwrap_or(""),
        field_attr
            .name
            .unwrap_or_else(|| field.ident.as_ref().unwrap().to_string().to_uppercase())
    );

    let separator = field_attr.separator.unwrap_or(',');

    let mut r#gen = if let Some(export_with) = field_attr.export_with {
        return Ok(Some(quote::quote! {
            hash_map.extend(#export_with(&self.#name));
        }));
    } else if crate::is_option(&field.ty) && field_attr.nested {
        quote::quote! {
            if let ::std::option::Option::Some(ref v) = self.#name {
                hash_map.extend(#envir::Serialize::collect(v));
            }
        }
    } else if crate::is_option_vec(&field.ty) {
        quote::quote! {
            if let ::std::option::Option::Some(ref v) = self.#name {
                hash_map.insert(#var.to_string(), v.iter().map(|x| x.to_string()).collect::<::std::vec::Vec<_>>().join(&#separator.to_string()));
            }
        }
    } else if crate::is_vec(&field.ty) {
        quote::quote! {
            hash_map.insert(#var.to_string(), self.#name.iter().map(|x| x.to_string()).collect::<::std::vec::Vec<_>>().join(&#separator.to_string()));
        }
    } else if crate::is_option(&field.ty) {
        quote::quote! {
            if let ::std::option::Option::Some(ref v) = self.#name {
                hash_map.insert(#var.to_string(), v.to_string());
            }
        }
    } else if field_attr.nested {
        quote::quote! {
            hash_map.extend(#envir::Serialize::collect(&self.#name))
        }
    } else {
        quote::quote! {
            hash_map.insert(#var.to_string(), self.#name.to_string())
        }
    };

    if let Some(skip_export_if) = field_attr.skip_export_if {
        r#gen = quote::quote! {
            if !#skip_export_if(&self.#name) {
                #r#gen;
            }
        }
    }

    Ok(Some(r#gen))
}
