#![doc = "Types used for deserializing attributes (via Darling)"]

use darling::ast::Data;
use darling::util::{Flag, Override, PathList};
use darling::{FromDeriveInput, FromField, FromMeta};
use heck::AsUpperCamelCase;
use proc_macro_error2::abort;
use quote::{format_ident, ToTokens};
use syn::spanned::Spanned;
use syn::{Attribute, Ident, Path};

// - validate trait

pub(crate) type InvalidityReason = String;
pub(crate) type HasInvalidity = Option<Vec<InvalidityReason>>;

/// Performs baseline validation of local fields.
///
/// Should not perform higher-level validation with other types
pub(crate) trait ValidateScoped {
    fn validate_within_scope(&self) -> HasInvalidity {
        None
    }
}

// - darling types

/// Root derive options
#[derive(Debug, Clone, FromDeriveInput)]
#[darling(
    attributes(shrinkwrap),
    forward_attrs(allow, doc, cfg, shrinkwrap_attr),
    supports(struct_named)
)]
pub(crate) struct DeriveItemOpts {
    pub ident: Ident,
    pub data: Data<(), DeriveItemFieldOpts>,
    pub attrs: Vec<Attribute>,

    #[darling(default, rename = "wrapper")]
    pub wrapper_opts: WrapperOpts,

    #[darling(default, rename = "extra")]
    pub extra_opts: ExtraOpts,

    #[darling(default, rename = "nest", multiple)]
    pub nest_opts: Vec<NestOpts>,
}
impl ValidateScoped for DeriveItemOpts {
    fn validate_within_scope(&self) -> HasInvalidity {
        let mut issues = Vec::new();
        if let Some(new_issues) = self.wrapper_opts.validate_within_scope() {
            issues.extend(new_issues);
        }
        if let Some(new_issues) = self.extra_opts.validate_within_scope() {
            issues.extend(new_issues);
        }
        for nest_group in &self.nest_opts {
            if let Some(nest_issues) = nest_group.validate_within_scope() {
                issues.extend(nest_issues);
            }
        }

        if issues.is_empty() {
            None
        } else {
            issues.into()
        }
    }
}

/// Options for struct wrapper attribute
#[derive(Debug, Clone, Default, FromMeta)]
pub struct WrapperOpts {
    /// Set the struct name suffix used by all associated wrappers (primary + any nested wrappers).
    ///
    /// Defaults to `Wrapper`
    ///
    /// E.g. For a data struct named: `MyData`, the default corresponding wrapper struct would be `MyDataWrapper`
    #[darling(default)]
    struct_suffix: Option<Ident>,

    /// Derives to apply to the wrapper struct
    #[darling(default)]
    pub derive: PathList,

    /// Sets documentation for the generated Wrapper struct
    #[darling(default = String::new)]
    pub doc: String,

    /// Field name for data struct, defaults to data
    #[darling(default)]
    data_field_name: Option<Ident>,

    /// Sets field-level documentation for data field
    #[darling(default = String::new)]
    pub data_field_doc: String,

    /// Serializes data contents into the wrapper inline via `#[serde(flatten)`.
    ///
    /// **NOTE:** `#[serde(flatten)]` is applied to the wrapper data field, **and not the wrapper itself**.
    ///
    /// `flatten = false` will disable data flattening and retain nesting during serialization.
    ///
    /// ### Side effects
    ///
    /// Disabling data flattening may cause some unexpected changes to rendered data hierarchy (via `#[shrinkwrap(nest(.., nested(origin = ..)))]`).
    ///
    /// The current behaviour for parent nests (nests with subsequent data further nested below them), is to provide an intermediate `Wrapper` between itself and the deeply nested data.
    ///
    /// This is done on nests for the the same reason it is done on root data struct - it provides the exact same set of benefits.
    ///
    /// As a result, when flattening is disabled, data trees become inconsistent. Where non-leaf nests have an extra 'data' object between it and it's data, whereas leaf nests will not have this.
    ///
    /// For APIs, this will inevitably lead to a terrible UX for clients. When resources/data structs are shared among responses, the resulting effect is data remaining the same, yet the surrounding schema 'skeleton' changes per-route. =
    ///
    /// ##### This is the opposite of what most would expect.
    ///
    /// <div class="warning">
    /// If the derived structs will be exposed as a response format, API or otherwise, then<br>
    /// <br>
    /// <b>Do not disable struct flattening</b>
    /// </div>
    flatten: Option<Override<bool>>,

    /// Field name for extra struct, defaults to data
    #[darling(default)]
    extra_field_name: Option<Ident>,

    /// Sets field-level documentation for extra field
    #[darling(default = String::new)]
    pub extra_field_doc: String,
}
impl WrapperOpts {
    fn struct_name_suffix_default() -> Ident {
        format_ident!("Wrapper")
    }
    pub fn struct_name_suffix(&self) -> Ident {
        match &self.struct_suffix {
            Some(suffix) => suffix.clone(),
            None => Self::struct_name_suffix_default(),
        }
    }
    pub fn struct_name(&self, data_ident: &Ident) -> Ident {
        format_ident!("{}{}", data_ident, self.struct_name_suffix())
    }
    fn data_field_name_default() -> Ident {
        format_ident!("data")
    }
    pub fn data_field_name(&self) -> Ident {
        match &self.data_field_name {
            Some(name) => name.clone(),
            None => Self::data_field_name_default(),
        }
    }
    pub fn flatten(&self) -> bool {
        match self.flatten {
            Some(Override::Inherit)
            | Some(Override::Explicit(true))
            | None => true,
            Some(Override::Explicit(false)) => false,
        }
    }
    fn extra_field_name_default() -> Ident {
        format_ident!("extra")
    }
    pub fn extra_field_name(&self) -> Ident {
        match &self.extra_field_name {
            Some(name) => name.clone(),
            None => Self::extra_field_name_default(),
        }
    }
}
impl ValidateScoped for WrapperOpts {}

/// Options for struct extra attribute
#[derive(Debug, Clone, Default, FromMeta)]
pub struct ExtraOpts {
    /// Set the `extra` struct name suffix - defaults to `Extra`
    ///
    /// E.g. For a data struct named: `MyData`, the default corresponding extra struct would be `MyDataExtra`
    #[darling(default)]
    struct_suffix: Option<Ident>,

    /// Derives to apply to the extra struct - Debug, Clone, and serde::Serialize are required and auto-derived
    #[darling(default)]
    pub derive: PathList,

    /// Sets struct-level documentation for the generated Extra struct
    #[darling(default = String::new)]
    pub doc: String,
}
impl ExtraOpts {
    fn struct_name_suffix_default() -> Ident {
        format_ident!("Extra")
    }
    pub fn struct_name_suffix(&self) -> Ident {
        match &self.struct_suffix {
            Some(suffix) => suffix.clone(),
            None => Self::struct_name_suffix_default(),
        }
    }
    pub fn struct_name(&self, parent_data_ident: &Ident) -> Ident {
        format_ident!("{}{}", parent_data_ident, self.struct_name_suffix())
    }
}
impl ValidateScoped for ExtraOpts {}

#[derive(Debug, Clone, FromMeta, PartialEq, Eq)]
#[darling(rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
pub enum NestMapStrategy {
    From,
    Transform { with: Path },
    Nested { origin: Path }
}
impl NestMapStrategy {
    pub fn maps_with_from(&self) -> bool {
        matches!(self, Self::From)
    }
    pub fn maps_with_transform(&self) -> bool {
        matches!(self, Self::Transform { .. })
    }
    pub fn map_transform_type(&self) -> Option<Path> {
        match &self {
            NestMapStrategy::Transform { with } => Some(with.clone()),
            NestMapStrategy::From => None,
            NestMapStrategy::Nested { .. } => None,
        }
    }
}

/// Options for struct nest attribute
#[derive(Debug, Clone, FromMeta)]
pub struct NestOpts {
    /// used for specifying/identifying a nest from an attribute. Must be unique among all nests under a given Data struct
    pub id: String,

    /// used for the nest field name under `data.extra`.
    /// This should typically be identical and must be unique among the other sibling nests.
    /// Typically this should only be used when implementing nested data hierarchies via [`origin`](Self::origin)
    ///
    /// Defaults to `self.id`
    field_name: Option<Ident>,

    /// sets the name of the nests' generated struct - defaults to `{DataStructName}{UpperCamel(field_name ? field_name : id)}`
    rename: Option<Ident>,

    /// Derives to apply to the nest struct - Debug, Clone, and serde::Serialize are required and auto-derived
    #[darling(default)]
    pub derive: PathList,

    /// sets the type for the fields in the nested struct
    pub field_type: Path,

    /// Strategy used to map data to this nest.
    ///
    /// 1: **`from`**
    ///
    ///    Use a **pre-existing** `from` impl:
    ///    ```rust
    ///    impl From<&MyData> for MyDataNestedExample
    ///    ```
    /// 2: **`transform(with = TransformTypeGoesHere)`**
    ///
    ///    Use a **pre-existing** transform impl of the provided type.
    ///
    ///    The transform type must implement `TransformToNest`. e.g.
    ///    ```rust
    ///    struct MyTransform {}
    ///
    ///    impl TransformToNest<TestData, TestDataNestedText> for MyTransform {
    ///        fn transform_to_nest(&self, data: &TestData) -> TestDataNestedText {
    ///            TestDataNestedText {
    ///                random_number: data.random_number.to_string(),
    ///            }
    ///        }
    ///    }
    ///    ```
    /// 3: **`nested(origin = MyDataNestedExample)`**
    ///
    ///    Nest this under an existing nest, where origin = "NestStructType".
    ///
    ///    No mapping is needed in this case as mappings are only required for root-level nests.
    ///    (The mapping logic is still supplied by you, however as a part of the parent nest's mapping strategy)
    ///
    ///    This allows you to use either the parent nest's data or the root data as the source for transformation.
    ///    **Note:** This cannot be some arbitrary type. It must be:
    ///      1. Built internally by this derive macro
    ///      2. Exist within this data struct tree (rather than a struct generated for another data tree)
    #[darling(flatten)]
    pub map_strategy: NestMapStrategy,

    /// Sets the struct-level documentation for the generated Nest struct
    #[darling(default = String::new)]
    pub doc: String,

    /// The parent extra struct will type the field for this nest with `Option<T>`, e.g, the generated extra struct would look like
    /// ```rust
    /// pub struct MyDataExtra {
    ///     pub text: Option<MyDataNestedText>,
    /// }
    /// ```
    pub optional: Flag,
}
impl NestOpts {
    fn field_name_default(&self) -> Ident {
        format_ident!("{}", self.id)
    }
    pub fn field_name(&self) -> Ident {
        match &self.field_name {
            Some(name) => name.clone(),
            None => self.field_name_default(),
        }
    }
    pub fn build_struct_name_suffix(field_name: &Ident) -> Ident {
        let suffix = AsUpperCamelCase(field_name.to_string());
        format_ident!("{suffix}")
    }
    pub fn build_default_struct_name(
        origin_ident: &Ident,
        root_ident: &Ident,
        field_name: &Ident,
    ) -> Ident {
        // To avoid obnoxiously long struct names, only include the nested keyword once (root nests)
        let region_descriptor = if origin_ident == root_ident {
            "Nested"
        } else {
            Default::default()
        };
        let suffix = Self::build_struct_name_suffix(field_name);

        format_ident!("{}{region_descriptor}{}", origin_ident, suffix)
    }
    /// `root_ident` is the ident of the top-level data struct containing derive(Wrap)
    /// It is used to form the base struct name when an origin isn't explicitly provided
    pub fn struct_name_default(&self, root_ident: &Ident) -> Ident {
        let origin_ident = match &self.map_strategy {
            NestMapStrategy::Nested { origin } => {
                if let Some(last_path_segment) = origin.segments.last() {
                    &last_path_segment.ident
                } else {
                    abort!(origin.span(), format!("Failed to interpret value of `nested(origin = '...')`: `{}`. Please use a type for the origin.", origin.to_token_stream()))
                }
            },
            _ => root_ident
        };
        let field_name = self.field_name();
        Self::build_default_struct_name(origin_ident, root_ident, &field_name)
    }
    /// `root_ident` is the ident of the top-level data struct containing derive(Wrap)
    /// It is used to form the base struct name when an origin isn't explicitly provided
    pub fn struct_name(&self, root_ident: &Ident) -> Ident {
        match &self.rename {
            Some(name) => name.clone(),
            None => self.struct_name_default(root_ident),
        }
    }

    pub fn origin<'a>(&'a self, root_ident: &'a Ident) -> &'a Ident {
        match &self.map_strategy {
            NestMapStrategy::Nested { origin } => {
                if let Some(last_path_segment) = origin.segments.last() {
                    &last_path_segment.ident
                } else {
                    abort!(origin.span(), format!("Failed to interpret value of `nested(origin = '...')`: `{}`. Please use a type for the origin.", origin.to_token_stream()))
                }
            },
            _ => root_ident,
        }
    }
}
impl ValidateScoped for NestOpts {
    fn validate_within_scope(&self) -> HasInvalidity {
        let mut issues = Vec::new();
        if self.id.is_empty() {
            issues.push("Nest `id` cannot be empty".into());
        }
        // optional nests aren't compatible with from impl's
        // this is because it's not possible to impl From for Option<T> (orphan rule prevents this)
        if self.optional.is_present() && matches!(&self.map_strategy, NestMapStrategy::From) {
            issues.push("Optional nests (configured with `optional attr`) are not incompatible when also configured with `from`.\n\nThis is because it's not possible to impl `From<..>` for `Option<..>` (the orphan rule prevents this). Please use the `transform` option to handle mapping data for optional nests.".into())
        }

        if issues.is_empty() {
            None
        } else {
            issues.into()
        }
    }
}

/// Options for struct field attributes
#[derive(Debug, Clone, FromField)]
#[darling(attributes(shrinkwrap), forward_attrs(shrinkwrap_attr))]
pub struct DeriveItemFieldOpts {
    /// only None for tuple fields, therefore safe to unwrap
    pub ident: Option<Ident>,

    #[darling(default, multiple, rename = "nest_in")]
    pub nest_in_opts: Vec<NestInOpts>,

    pub attrs: Vec<syn::Attribute>,
}
impl ValidateScoped for DeriveItemFieldOpts {}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, FromMeta)]
#[darling(rename_all = "snake_case")]
pub enum PassthroughAttributeContext {
    #[default]
    All,
    Nest,
    Extra,
    Wrapper,
}
#[derive(Debug, Clone, FromMeta)]
pub struct PassthroughAttribute {
    #[darling(default)]
    pub nest: PathList,
    #[darling(multiple)]
    pub attr: Vec<syn::Meta>,
    #[darling(default)]
    pub context: Option<PassthroughAttributeContext>,
}

/// Wrap field `nest_in` attribute options
#[derive(Debug, Clone, FromMeta)]
pub struct NestInOpts {
    /// Nest key for which this field should be included/mapped
    #[darling(rename = "id")]
    pub nest_id: Ident,

    /// Set the field's documentation for this nest
    #[darling(default = String::new)]
    pub field_doc: String,
}
impl ValidateScoped for NestInOpts {
    fn validate_within_scope(&self) -> HasInvalidity {
        None
    }
}
