use proc_macro2::Span;
use quote::{ToTokens, quote_spanned};

use super::*;

fn is_default_str_literal(meta: &syn::MetaNameValue) -> bool
{
    meta.path.is_ident("default")
        && matches!(
            &meta.value,
            syn::Expr::Lit(syn::ExprLit {
                lit: syn::Lit::Str(_),
                ..
            })
        )
}

#[derive(Default)]
pub struct FieldAttrs
{
    pub checker: FieldChecker,
}

#[derive(Default)]
pub enum FieldChecker
{
    Ignore,
    Func(syn::ExprPath),
    Closure(syn::ExprClosure),
    EqualsValue(syn::Expr),
    EqualsRef(syn::Expr),
    #[default]
    Delegate,
}
impl FieldChecker
{
    fn assert_unset(&self, ctx: impl ToTokens) -> syn::Result<()>
    {
        if matches!(self, Self::Delegate) {
            Ok(())
        } else {
            Err(syn::Error::new_spanned(
                ctx,
                "Multiple defaulted implementations specified",
            ))
        }
    }
    fn set(&mut self, ctx: impl ToTokens, value: Self) -> syn::Result<()>
    {
        self.assert_unset(ctx)?;
        *self = value;
        Ok(())
    }
    fn set_with(
        &mut self,
        ctx: impl ToTokens,
        mut make_value: impl FnMut() -> Self,
    ) -> syn::Result<()>
    {
        self.assert_unset(ctx)?;
        *self = make_value();
        Ok(())
    }

    /// For `EqualsValue` or `EqualsRef` checkers, return the default value expression.
    pub(crate) fn default_value_expr(&self, span: Span) -> Option<TokenStream2>
    {
        match self {
            Self::EqualsValue(expr) => Some(quote_spanned! {span=> #expr }),
            Self::EqualsRef(expr) => Some(quote_spanned! {span=> #expr .to_owned()}),
            _ => None,
        }
    }
}

impl ParseAttrs<FieldAttrs> for &syn::Field
{
    fn parse_attrs_into(self, attrs: &mut FieldAttrs) -> syn::Result<()>
    {
        self.attrs.parse_attrs_into(attrs)
    }
}
impl ParseAttrs<FieldAttrs> for &syn::Attribute
{
    fn parse_attrs_into(self, attrs: &mut FieldAttrs) -> syn::Result<()>
    {
        if self.path().is_ident(stringify!(defaulted)) {
            let meta = self.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
            meta.parse_attrs_into(attrs)?;
        }
        Ok(())
    }
}
impl ParseAttrs<FieldAttrs> for Meta
{
    fn parse_attrs_into(self, attrs: &mut FieldAttrs) -> syn::Result<()>
    {
        let checker = &mut attrs.checker;
        match &self {
            Self::Path(path) if path.is_ident("ignore") => checker.set(path, FieldChecker::Ignore),
            Self::NameValue(nv) if nv.path.is_ident("default_ref") => {
                checker.set_with(nv, || FieldChecker::EqualsRef(nv.value.clone()))
            }
            Self::NameValue(nv) if is_default_str_literal(nv) => {
                checker.set_with(nv, || FieldChecker::EqualsRef(nv.value.clone()))
            }
            Self::NameValue(nv) if nv.path.is_ident("default") => {
                checker.set_with(nv, || FieldChecker::EqualsValue(nv.value.clone()))
            }
            Self::NameValue(nv) if nv.path.is_ident("with") => match &nv.value {
                syn::Expr::Path(func) => {
                    checker.set_with(func, || FieldChecker::Func(func.clone()))
                }
                syn::Expr::Closure(closure) => {
                    checker.set_with(closure, || FieldChecker::Closure(closure.clone()))
                }
                _ => Err(syn::Error::new_spanned(
                    nv.value.clone(),
                    "Expected a function or closure",
                )),
            },
            _ => Err(syn::Error::new_spanned(self, "Invalid to_derive specifier")),
        }
    }
}
