use proc_macro::{Ident, TokenStream, TokenTree};

pub(crate) fn build(
    mod_name: Ident,
    ty: TokenTree,
    format: TokenStream,
    raw_format_string: Option<String>,
) -> TokenStream {
    let ty_s = &*ty.to_string();

    let format_description_display = raw_format_string.unwrap_or_else(|| format.to_string());

    let visitor = if cfg!(feature = "parsing") {
        quote! {
            struct Visitor;
            struct OptionVisitor;

            impl<'a> ::serde::de::Visitor<'a> for Visitor {
                type Value = __TimeSerdeType;

                fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                    write!(
                        f,
                        concat!(
                            "a(n) `",
                            #(ty_s),
                            "` in the format \"{}\"",
                        ),
                        #(format_description_display.as_str())
                    )
                }

                fn visit_str<E: ::serde::de::Error>(
                    self,
                    value: &str
                ) -> Result<__TimeSerdeType, E> {
                    __TimeSerdeType::parse(value, &DESCRIPTION).map_err(E::custom)
                }
            }

            impl<'a> ::serde::de::Visitor<'a> for OptionVisitor {
                type Value = Option<__TimeSerdeType>;

                fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                    write!(
                        f,
                        concat!(
                            "an `Option<",
                            #(ty_s),
                            ">` in the format \"{}\"",
                        ),
                        #(format_description_display.as_str())
                    )
                }

                fn visit_some<D: ::serde::de::Deserializer<'a>>(
                    self,
                    deserializer: D
                ) -> Result<Option<__TimeSerdeType>, D::Error> {
                    deserializer
                        .deserialize_any(Visitor)
                        .map(Some)
                }

                fn visit_none<E: ::serde::de::Error>(
                    self
                ) -> Result<Option<__TimeSerdeType>, E> {
                    Ok(None)
                }
            }
        }
    } else {
        quote!()
    };

    let serialize_primary = if cfg!(feature = "formatting") {
        quote! {
            pub fn serialize<S: ::serde::Serializer>(
                datetime: &__TimeSerdeType,
                serializer: S,
            ) -> Result<S::Ok, S::Error> {
                use ::serde::Serialize;
                datetime
                    .format(&DESCRIPTION)
                    .map_err(::time::error::Format::into_invalid_serde_value::<S>)?
                    .serialize(serializer)
            }
        }
    } else {
        quote!()
    };

    let deserialize_primary = if cfg!(feature = "parsing") {
        quote! {
            pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
                deserializer: D
            ) -> Result<__TimeSerdeType, D::Error> {
                use ::serde::Deserialize;
                deserializer.deserialize_any(Visitor)
            }
        }
    } else {
        quote!()
    };

    let serialize_option = if cfg!(feature = "formatting") {
        quote! {
            pub fn serialize<S: ::serde::Serializer>(
                option: &Option<__TimeSerdeType>,
                serializer: S,
            ) -> Result<S::Ok, S::Error> {
                use ::serde::Serialize;
                option.map(|datetime| datetime.format(&DESCRIPTION))
                    .transpose()
                    .map_err(::time::error::Format::into_invalid_serde_value::<S>)?
                    .serialize(serializer)
            }
        }
    } else {
        quote!()
    };

    let deserialize_option = if cfg!(feature = "parsing") {
        quote! {
            pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
                deserializer: D
            ) -> Result<Option<__TimeSerdeType>, D::Error> {
                use ::serde::Deserialize;
                deserializer.deserialize_option(OptionVisitor)
            }
        }
    } else {
        quote!()
    };

    let deserialize_option_imports = if cfg!(feature = "parsing") {
        quote! {
            use super::{OptionVisitor, Visitor};
        }
    } else {
        quote!()
    };

    quote! {
        mod #(mod_name) {
            use ::time::#(ty) as __TimeSerdeType;

            const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = #S(format);

            #S(visitor)
            #S(serialize_primary)
            #S(deserialize_primary)

            pub(super) mod option {
                use super::{DESCRIPTION, __TimeSerdeType};
                #S(deserialize_option_imports)

                #S(serialize_option)
                #S(deserialize_option)
            }
        }
    }
}
