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 from_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::Deserialize for #name #ty_generics #where_clause {
            fn from(env: &std::collections::HashMap<String, String>) -> #envir::Result<Self> {
                Ok(Self {
                    #(#from_body, )*
                })
            }
        }
    };

    Ok(de)
}

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

    let field_attr = crate::attr::Field::from_field(field)?;
    let envir = attr.envir();
    let name = &field.ident;
    let mut var = field_attr
        .name
        .clone()
        .unwrap_or_else(|| field.ident.as_ref().unwrap().to_string().to_uppercase());

    if !field_attr.noprefix {
        var.insert_str(0, attr.prefix.as_deref().unwrap_or(""));
    }

    if field_attr.skip || field_attr.skip_load {
        return Ok(quote::quote! {
            #name: Default::default()
        });
    }

    if let Some(load_with) = field_attr.load_with {
        return Ok(quote::quote! {
            #name: #load_with(&env)?
        });
    }

    if field_attr.nested {
        return Ok(quote::quote! {
            #name: #envir::Deserialize::from(env)?
        });
    }

    let load = if crate::is_vec(&field.ty) || crate::is_option_vec(&field.ty) {
        quote::quote! { load_vec }
    } else {
        quote::quote! { load_option }
    };

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

    if crate::is_option(&field.ty) {
        return Ok(quote::quote! {
            #name: #envir::#load(env, #var, None, #separator)?
        });
    }

    let r#gen = match &field_attr.default {
        None => quote::quote! {
            #name: #envir::#load(env, #var, None, #separator)?
                .ok_or(#envir::Error::Missing(#var.to_string()))?
        },
        Some(darling::util::Override::Inherit) => quote::quote! {
            #name: #envir::#load(env, #var, None, #separator)?
                .unwrap_or_else(::std::default::Default::default)
        },
        Some(darling::util::Override::Explicit(path)) => quote::quote! {
            #name: #envir::#load(env, #var, ::std::option::Option::Some(#path.to_string()), #separator)?
                .unwrap()
        },
    };

    Ok(r#gen)
}
