#![allow(clippy::option_if_let_else)]

use std::convert::identity;

use convert_case::{Case, Casing};
use darling::{
    ast::{self, Data},
    util::{Flag, Override, PathList},
    Error, FromDeriveInput, FromField, FromMeta,
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_quote, spanned::Spanned};

#[derive(Debug, FromField)]
#[darling(attributes(config))]
#[darling(forward_attrs(doc))]
pub struct ConfigField {
    ident: Option<syn::Ident>,
    vis: syn::Visibility,
    ty: syn::Type,
    attrs: Vec<syn::Attribute>,
    min: Option<Override<syn::Expr>>,
    max: Option<Override<syn::Expr>>,
    default: Option<Override<syn::Expr>>,
    typ: Option<syn::Type>,
    get: Option<syn::Path>,
    set: Option<syn::Path>,
    no_serde: Flag,
    #[darling(skip)]
    description: Option<syn::Expr>,
    #[darling(skip)]
    parent_ident: Option<syn::Ident>,
}

impl ConfigField {
    pub(crate) fn needs_newtype(&self) -> bool {
        self.helper_keys().len() > 1 || self.typ.is_some()
    }

    pub(crate) fn helper(&self, attrs: &[syn::Attribute]) -> (Option<TokenStream>, syn::Type) {
        if self.needs_newtype() {
            let default = self.helper_default();
            let derives = attrs
                .iter()
                .find(|attr| attr.path().is_ident("derive"))
                .map(|attr| {
                    let mut derives = (*PathList::from_meta(&attr.meta).unwrap()).clone();
                    derives.retain(|derive| {
                        derive.segments.last().map(|s| {
                            let derive = s.ident.to_string();
                            derive.contains("Tree")
                                || derive.contains("Serialize")
                                || derive.contains("Deserialize")
                                || (default.is_some() && derive.contains("Default"))
                        }) == Some(false)
                    });
                    quote! {#[derive(#(#derives,)*)]}
                });
            let new_type_ident = self.helper_ident();
            let vis = &self.vis;
            let ty = &self.ty;

            let new = self.helper_new();
            let serde = self.helper_serde();
            let tree = self.helper_tree();

            (
                Some(quote! {
                    #derives
                    #vis struct #new_type_ident(#ty);

                    #new
                    #default
                    #serde
                    #tree
                }),
                parse_quote!(#new_type_ident),
            )
        } else {
            (None, self.ty.clone())
        }
    }

    pub(crate) fn helper_ident(&self) -> syn::Ident {
        format_ident!(
            "__{}{}",
            self.parent_ident
                .as_ref()
                .unwrap_or(&syn::Ident::new("Struct", Span::mixed_site())),
            self.ident
                .as_ref()
                .map_or("Field".to_owned(), |v| v.to_string().to_case(Case::Pascal))
        )
    }

    pub(crate) fn has_custom_limits(&self) -> bool {
        self.typ.is_none()
            && !((self.min.is_none() || self.min == Some(Override::Inherit))
                && (self.max.is_none() || self.max == Some(Override::Inherit)))
    }

    pub(crate) fn helper_new(&self) -> TokenStream {
        let ident = self.helper_ident();
        let ty = &self.ty;
        if self.has_custom_limits() {
            let min = self.min.as_ref().and_then(|v| v.as_ref().explicit());
            let max = self.max.as_ref().and_then(|v| v.as_ref().explicit());
            quote! {
                impl #ident {
                    pub fn new(value: #ty) -> Option<Self> {
                        if (#min..=#max).contains(&value) {
                            Some(Self(value))
                        } else {
                            None
                        }
                    }

                    #[allow(dead_code)]
                    pub const unsafe fn new_unchecked(value: #ty) -> Self {
                        Self(value)
                    }
                }

                impl ::core::ops::Deref for #ident {
                    type Target = #ty;
                    fn deref(&self) -> &Self::Target {
                        &self.0
                    }
                }
            }
        } else {
            quote! {
                impl #ident {
                    pub const fn new(value: #ty) -> Self {
                        Self(value)
                    }
                }

                impl ::core::ops::Deref for #ident {
                    type Target = #ty;
                    fn deref(&self) -> &Self::Target {
                        &self.0
                    }
                }

                impl ::core::ops::DerefMut for #ident {
                    fn deref_mut(&mut self) -> &mut Self::Target {
                        &mut self.0
                    }
                }
            }
        }
    }

    pub(crate) fn helper_default(&self) -> Option<TokenStream> {
        if self.typ.is_some() {
            return None;
        }
        self.default.as_ref().map(|default| {
            let ident = self.helper_ident();
            let default_default = parse_quote!(::core::default::Default::default());
            let default = default.as_ref().unwrap_or(&default_default);
            quote! {
                impl ::core::default::Default for #ident {
                    fn default() -> Self {
                        Self(#default)
                    }
                }
            }
        })
    }

    pub(crate) fn helper_serde(&self) -> Option<TokenStream> {
        if self.no_serde.is_present() {
            return None;
        }
        let ident = self.helper_ident();
        let conversion = if self.has_custom_limits() {
            quote! {
                Self::new(value).ok_or_else(|| {
                    <D::Error as ::serde::de::Error>::custom("checking value bounds")
                })
            }
        } else {
            quote! {
                Ok(Self::new(value))
            }
        };
        let ty = &self.ty;
        Some(quote! {
            impl ::serde::Serialize for #ident {
                fn serialize<S>(&self, serializer: S) -> ::core::result::Result::<S::Ok, S::Error>
                where
                    S: ::serde::Serializer,
                {
                    self.0.serialize(serializer)
                }
            }

            impl<'de> ::serde::Deserialize<'de> for #ident {
                fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
                where
                    D: ::serde::Deserializer<'de>,
                {
                    type T = #ty;
                    let value = T::deserialize(deserializer)?;
                    #conversion
                }
            }
        })
    }

    pub(crate) fn helper_tree(&self) -> TokenStream {
        let mut tokens = self.helper_tree_key();
        tokens.extend(self.helper_tree_serialize());
        tokens.extend(self.helper_tree_deserialize());
        tokens.extend(self.helper_tree_any());
        tokens
    }

    pub(crate) fn helper_keys(&self) -> Vec<(&'static str, syn::Expr)> {
        macro_rules! field_to_key {
            ($field: ident, $default: expr) => {
                self.$field.as_ref().map(|value| {
                    let ty = self.typ.as_ref().unwrap_or(&self.ty);
                    (stringify!($field),
                    value.clone().explicit().map(|val| {
                        parse_quote!({
                            fn type_ascribe(val: #ty) -> #ty {val}
                            type_ascribe(#val)
                        })
                    }).unwrap_or_else(|| {
                        parse_quote!(#ty::$default)
                    }))
                })
            };
            ($field: ident) => {
                self.$field.as_ref().map(|value| {
                    (stringify!($field), value.clone())
                })
            }
        }
        [
            Some(("value", parse_quote!(self))),
            field_to_key!(default, default()),
            field_to_key!(min, MIN),
            field_to_key!(max, MAX),
            field_to_key!(description),
        ]
        .into_iter()
        .flatten()
        .collect::<Vec<_>>()
    }

    pub(crate) fn helper_tree_key(&self) -> TokenStream {
        let ident = self.helper_ident();
        let keys = self.helper_keys();
        let num_keys = keys.len();
        let keys = keys.iter().map(|(name, _)| name);
        let max_length = keys.clone().map(|v| v.len()).max();
        quote! {
            impl ::miniconf::KeyLookup for #ident {
                const LEN: usize = #num_keys;
                const NAMES: &'static [&'static str] = &[#(#keys,)*];

                fn name_to_index(value: &str) -> Option<usize> {
                    Self::NAMES.iter().position(|name| *name == value)
                }
            }

            impl ::miniconf::TreeKey<1> for #ident {
                fn metadata() -> ::miniconf::Metadata {
                    let mut metadata = ::miniconf::Metadata::default();
                    metadata.max_depth = 1;
                    metadata.count = #num_keys;
                    metadata.max_length = #max_length;
                    metadata
                }

                fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> ::core::result::Result<usize, ::miniconf::Error<E>>
                where
                    K: ::miniconf::Keys,
                    // Writing this to return an iterator instead of using a callback
                    // would have worse performance (O(n^2) instead of O(n) for matching)
                    F: FnMut(usize, Option<&'static str>, usize) -> ::core::result::Result<(), E>,
                {
                    let ::core::result::Result::Ok(key) = keys.next::<Self>() else { return ::core::result::Result::Ok(0) };
                    let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
                    let name = <Self as ::miniconf::KeyLookup>::NAMES
                        .get(index)
                        .ok_or(::miniconf::Traversal::NotFound(1))?;
                    func(index, Some(name), #num_keys).map_err(|err| ::miniconf::Error::Inner(1, err))?;
                    ::core::result::Result::Ok(1)
                }
            }
        }
    }

    pub(crate) fn helper_tree_serialize(&self) -> TokenStream {
        let matches = self
            .helper_keys()
            .iter()
            .enumerate()
            .skip(1)
            .map(|(i, (_, expr))| {
                quote! {
                    #i => ::serde::Serialize::serialize(&#expr, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
                }
            })
            .collect::<Vec<_>>();
        let ident = self.helper_ident();
        let get = self
            .get
            .as_ref()
            .map_or_else(|| quote! {self}, |getter| quote! {#getter(&self.0)});
        quote! {
            impl ::miniconf::TreeSerialize<1> for #ident {
                fn serialize_by_key<K, S>(
                    &self,
                    mut keys: K,
                    ser: S,
                ) -> ::core::result::Result<usize, ::miniconf::Error<S::Error>>
                where
                    K: ::miniconf::Keys,
                    S: ::serde::Serializer,
                {
                    let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
                        return ::serde::Serialize::serialize(&#get, ser).map_err(|err| ::miniconf::Error::Inner(0, err)).map(|_| 0);
                    };
                    let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(0))?;
                    if !keys.finalize() {
                        return ::core::result::Result::Err(::miniconf::Traversal::TooLong(0).into());
                    }
                    match index {
                        0 => ::serde::Serialize::serialize(&#get, ser).map_err(|err| ::miniconf::Error::Inner(0, err)),
                        #(#matches)*
                        _ => unreachable!(),
                    }?;
                    Ok(0)
                }
            }
        }
    }

    pub(crate) fn helper_tree_deserialize(&self) -> TokenStream {
        let ident = self.helper_ident();
        let num_keys = self.helper_keys().len();
        let set = self.set.as_ref().map_or_else(
            || quote!((|val| {*self = val; core::result::Result::<_, ::miniconf::Traversal>::Ok(())})),
            |set| quote!((|val| #set(&mut self.0, val).map_err(|e| ::miniconf::Traversal::Invalid(0, e)))),
        );
        let typ = self
            .typ
            .as_ref()
            .map_or_else(|| quote!(Self), |typ| quote!(#typ));
        let match_range = if num_keys > 1 {
            Some(
                quote!(1..=#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(0, "Cannot write limits").into()),),
            )
        } else {
            None
        };
        quote! {
            impl<'de> ::miniconf::TreeDeserialize<'de, 1> for #ident {
                fn deserialize_by_key<K, D>(
                    &mut self,
                    mut keys: K,
                    de: D,
                ) -> ::core::result::Result<usize, ::miniconf::Error<D::Error>>
                where
                    K: ::miniconf::Keys,
                    D: ::serde::Deserializer<'de>,
                {
                    let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
                        #set(<#typ as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?)?;
                        return ::core::result::Result::Ok(0);
                    };
                    let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
                    if !keys.finalize() {
                        return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1).into());
                    }
                    match index {
                        0 => {
                            #set(<#typ as ::serde::Deserialize>::deserialize(de).map_err(|err| ::miniconf::Error::Inner(0, err))?)?;
                            Ok(0)
                        }
                        #match_range
                        _ => unreachable!(),
                    }
                }
            }
        }
    }

    pub(crate) fn helper_tree_any(&self) -> TokenStream {
        let ident = self.helper_ident();
        let num_keys = self.helper_keys().len();
        let ref_mut = if self.has_custom_limits() {
            quote! {::core::result::Result::Err(::miniconf::Traversal::Access(0, "field has custom limits"))}
        } else {
            quote!(::core::result::Result::Ok(&mut *self))
        };
        let match_range = if num_keys > 1 {
            Some(
                quote!(1..#num_keys => ::core::result::Result::Err(::miniconf::Traversal::Access(1, "cannot return reference to local variable")),),
            )
        } else {
            None
        };
        quote! {
            impl ::miniconf::TreeAny<1> for #ident {
                fn ref_any_by_key<K>(&self, mut keys: K) -> ::core::result::Result<&dyn ::core::any::Any, ::miniconf::Traversal>
                where
                    K: ::miniconf::Keys,
                {
                    let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
                        return ::core::result::Result::Ok(&*self);
                    };
                    let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
                    if !keys.finalize() {
                        return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1));
                    }
                    match index {
                        0 => ::core::result::Result::Ok(&*self),
                        #match_range
                        _ => unreachable!(),
                    }
                }

                fn mut_any_by_key<K>(&mut self, mut keys: K) -> ::core::result::Result<&mut dyn ::core::any::Any, ::miniconf::Traversal>
                where
                    K: ::miniconf::Keys,
                {
                    let ::core::result::Result::Ok(key) = keys.next::<Self>() else {
                        return #ref_mut;
                    };
                    let index = ::miniconf::Key::find::<Self>(&key).ok_or(::miniconf::Traversal::NotFound(1))?;
                    if !keys.finalize() {
                        return ::core::result::Result::Err(::miniconf::Traversal::TooLong(1));
                    }
                    match index {
                        0 => #ref_mut,
                        #match_range
                        _ => unreachable!(),
                    }
                }
            }
        }
    }
}

#[derive(Debug, FromDeriveInput)]
#[darling(forward_attrs(derive))]
#[darling(supports(struct_any))]
pub struct Config {
    ident: syn::Ident,
    // generics: syn::Generics,
    // vis: syn::Visibility,
    data: ast::Data<(), ConfigField>,
    pub attrs: Vec<syn::Attribute>,
}

impl Config {
    pub(crate) fn parse(input: &syn::DeriveInput) -> Result<Self, Error> {
        let mut config = Self::from_derive_input(input)?;
        let ident = config.ident.clone();
        let fields = config.fields_mut();
        for field in fields.iter_mut() {
            field.description = field
                .attrs
                .iter()
                .find(|attr| attr.path().is_ident("doc"))
                .map(|attr| {
                    let description = syn::LitStr::from_meta(&attr.meta).unwrap().value();
                    let description = description.trim();
                    parse_quote!(#description)
                });
            field.parent_ident = Some(ident.clone());
            let get_set_typ = [
                field.typ.is_some(),
                field.get.is_some(),
                field.set.is_some(),
            ];
            if get_set_typ.iter().copied().any(identity) && !get_set_typ.into_iter().all(identity) {
                return Err(
                    Error::custom("need all or none of 'typ', 'get', 'set'").with_span(
                        &field.typ.as_ref().map_or_else(
                            || {
                                field.get.as_ref().map_or_else(
                                    || field.set.as_ref().unwrap().span(),
                                    Spanned::span,
                                )
                            },
                            Spanned::span,
                        ),
                    ),
                );
            }
        }
        Ok(config)
    }

    pub(crate) fn fields(&self) -> &Vec<ConfigField> {
        let Data::Struct(fields) = &self.data else {
            unreachable!()
        };
        &fields.fields
    }

    pub(crate) fn fields_mut(&mut self) -> &mut Vec<ConfigField> {
        let Data::Struct(fields) = &mut self.data else {
            unreachable!()
        };
        &mut fields.fields
    }
}