use ident_case::RenameRule;

use crate::ast::{Data, Fields, Style};
use crate::codegen;
use crate::codegen::PostfixTransform;
use crate::options::{DefaultExpression, InputField, InputVariant, ParseAttribute, ParseData};
use crate::{Error, FromMeta, Result};

/// A struct or enum which should have `FromMeta` or `FromDeriveInput` implementations
/// generated.
#[derive(Debug, Clone)]
pub struct Core {
    /// The type identifier.
    pub ident: syn::Ident,

    /// The type's generics. If the type does not use any generics, this will
    /// be an empty instance.
    pub generics: syn::Generics,

    /// Controls whether missing properties should cause errors or should be filled by
    /// the result of a function call. This can be overridden at the field level.
    pub default: Option<DefaultExpression>,

    /// The rule that should be used to rename all fields/variants in the container.
    pub rename_rule: RenameRule,

    /// A transform which will be called on `darling::Result<Self>`. It must either be
    /// an `FnOnce(T) -> T` when `map` is used, or `FnOnce(T) -> darling::Result<T>` when
    /// `and_then` is used.
    ///
    /// `map` and `and_then` are mutually-exclusive to avoid confusion about the order in
    /// which the two are applied.
    pub post_transform: Option<codegen::PostfixTransform>,

    /// The body of the _deriving_ type.
    pub data: Data<InputVariant, InputField>,

    /// The custom bound to apply to the generated impl
    pub bound: Option<Vec<syn::WherePredicate>>,

    /// Whether or not unknown fields should produce an error at compilation time.
    pub allow_unknown_fields: Option<bool>,
}

impl Core {
    /// Partially initializes `Core` by reading the identity, generics, and body shape.
    pub fn start(di: &syn::DeriveInput) -> Result<Self> {
        Ok(Core {
            ident: di.ident.clone(),
            generics: di.generics.clone(),
            data: Data::try_empty_from(&di.data)?,
            default: Default::default(),
            // See https://github.com/TedDriggs/darling/issues/10: We default to snake_case
            // for enums to help authors produce more idiomatic APIs.
            rename_rule: if let syn::Data::Enum(_) = di.data {
                RenameRule::SnakeCase
            } else {
                Default::default()
            },
            post_transform: Default::default(),
            bound: Default::default(),
            allow_unknown_fields: Default::default(),
        })
    }

    fn as_codegen_default(&self) -> Option<codegen::DefaultExpression<'_>> {
        self.default.as_ref().map(|expr| match *expr {
            DefaultExpression::Explicit(ref path) => codegen::DefaultExpression::Explicit(path),
            DefaultExpression::Inherit => {
                // It should be impossible for any input to get here,
                // so panic rather than returning an error or pretending
                // everything is fine.
                panic!("DefaultExpression::Inherit is not valid at container level")
            }
            DefaultExpression::Trait { span } => codegen::DefaultExpression::Trait { span },
        })
    }
}

impl ParseAttribute for Core {
    fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
        let path = mi.path();

        if path.is_ident("default") {
            if self.default.is_some() {
                return Err(Error::duplicate_field("default").with_span(mi));
            }

            self.default = FromMeta::from_meta(mi)?;
        } else if path.is_ident("rename_all") {
            // WARNING: This may have been set based on body shape previously,
            // so an overwrite may be permissible.
            self.rename_rule = FromMeta::from_meta(mi)?;
        } else if path.is_ident("map") || path.is_ident("and_then") {
            // This unwrap is safe because we just called is_ident above
            let transformer = path.get_ident().unwrap().clone();

            if let Some(post_transform) = &self.post_transform {
                if transformer == post_transform.transformer {
                    return Err(Error::duplicate_field(&transformer.to_string()).with_span(mi));
                } else {
                    return Err(Error::custom(format!(
                        "Options `{}` and `{}` are mutually exclusive",
                        transformer, post_transform.transformer
                    ))
                    .with_span(mi));
                }
            }

            self.post_transform =
                Some(PostfixTransform::new(transformer, FromMeta::from_meta(mi)?));
        } else if path.is_ident("bound") {
            self.bound = FromMeta::from_meta(mi)?;
        } else if path.is_ident("allow_unknown_fields") {
            if self.allow_unknown_fields.is_some() {
                return Err(Error::duplicate_field("allow_unknown_fields").with_span(mi));
            }

            self.allow_unknown_fields = FromMeta::from_meta(mi)?;
        } else {
            return Err(Error::unknown_field_path(path).with_span(mi));
        }

        Ok(())
    }
}

impl ParseData for Core {
    fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
        let v = InputVariant::from_variant(variant, Some(self))?;

        match self.data {
            Data::Enum(ref mut variants) => {
                variants.push(v);
                Ok(())
            }
            Data::Struct(_) => panic!("Core::parse_variant should never be called for a struct"),
        }
    }

    fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
        let f = InputField::from_field(field, Some(self))?;

        match self.data {
            Data::Struct(Fields {
                style: Style::Unit, ..
            }) => panic!("Core::parse_field should not be called on unit"),
            Data::Struct(Fields { ref mut fields, .. }) => {
                fields.push(f);
                Ok(())
            }
            Data::Enum(_) => panic!("Core::parse_field should never be called for an enum"),
        }
    }
}

impl<'a> From<&'a Core> for codegen::TraitImpl<'a> {
    fn from(v: &'a Core) -> Self {
        codegen::TraitImpl {
            ident: &v.ident,
            generics: &v.generics,
            data: v
                .data
                .as_ref()
                .map_struct_fields(InputField::as_codegen_field)
                .map_enum_variants(|variant| variant.as_codegen_variant(&v.ident)),
            default: v.as_codegen_default(),
            post_transform: v.post_transform.as_ref(),
            bound: v.bound.as_deref(),
            allow_unknown_fields: v.allow_unknown_fields.unwrap_or_default(),
        }
    }
}
