//! `#[skip_serializing_defaults]` attribute macro implementation.
//!
//! Rewrites a struct to add `#[serde(skip_serializing_if = "...")]` and
//! (for `EqualsValue` fields) `#[serde(default = "...")]` to each field,
//! using generated checker functions in the companion module's `Helper`.

use std::collections::HashSet;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{ItemStruct, parse_macro_input};

use crate::field_parser::{FieldAttrs, FieldChecker};
use crate::{ParseAttrs, check_module};

pub fn apply(_attr: TokenStream, item: TokenStream) -> TokenStream
{
    let input = parse_macro_input!(item as ItemStruct);
    match transform(input) {
        Ok(tokens) => tokens.into(),
        Err(e) => e.into_compile_error().into(),
    }
}

fn transform(mut input: ItemStruct) -> syn::Result<TokenStream2>
{
    let has_defaulted_derive = has_derive(&input, "Defaulted");
    let struct_name = &input.ident;
    let generics = &input.generics;

    // Rewrite fields: add serde attributes where needed.
    let syn::Fields::Named(ref mut fields) = input.fields else {
        return Err(syn::Error::new_spanned(
            &input.fields,
            "skip_serializing_defaults requires named fields",
        ));
    };

    for field in &mut fields.named {
        let attrs: FieldAttrs = (&*field).parse_attrs()?;
        let field_name = field.ident.as_ref().expect("named field");

        if !has_skip_serializing_if(field) && !matches!(&attrs.checker, FieldChecker::Ignore) {
            let path = check_module::helper_fn_path(struct_name, generics, field_name);
            field
                .attrs
                .push(syn::parse_quote!(#[serde(skip_serializing_if = #path)]));
        }

        // For EqualsValue fields, also generate #[serde(default = "...")] so
        // deserialization fills in the right value when the key is absent.
        if matches!(&attrs.checker, FieldChecker::EqualsValue(_)) && !has_serde_default(field) {
            let default_fn = format_ident!("{field_name}__default");
            let path = check_module::helper_fn_path(struct_name, generics, &default_fn);
            field
                .attrs
                .push(syn::parse_quote!(#[serde(default = #path)]));
        }
    }

    // Generate the companion module.
    let check_mod = check_module::generate(struct_name, generics, &input.fields)?;

    // For generic structs, tell serde about Defaulted bounds so its
    // generated Serialize impl can call the Helper checker functions.
    if let Some(attr) = serde_serialize_bound(generics, &input.fields)? {
        input.attrs.push(attr);
    }

    if has_defaulted_derive {
        // Tell the derive macro to skip companion module creation.
        input
            .attrs
            .push(syn::parse_quote!(#[defaulted(__has_check_module)]));
    } else {
        // No derive macro to consume #[defaulted(...)] field attrs --
        // strip them to avoid "cannot find attribute" errors.
        strip_defaulted_field_attrs(&mut input.fields);
    }

    Ok(quote! {
        #check_mod
        #input
    })
}

/// Check whether `#[derive(...)]` includes a specific derive name.
fn has_derive(input: &ItemStruct, name: &str) -> bool
{
    input.attrs.iter().any(|attr| {
        if !attr.path().is_ident("derive") {
            return false;
        }
        let Ok(nested) = attr.parse_args_with(
            syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated,
        ) else {
            return false;
        };
        nested.iter().any(|path| path.is_ident(name))
    })
}

/// Check whether a field already has `#[serde(skip_serializing_if = "...")]`.
fn has_skip_serializing_if(field: &syn::Field) -> bool
{
    has_serde_attr(field, "skip_serializing_if")
}

/// Check whether a field already has `#[serde(default)]` or `#[serde(default = "...")]`.
fn has_serde_default(field: &syn::Field) -> bool
{
    has_serde_attr(field, "default")
}

/// Check whether a field has a serde attribute with the given name.
fn has_serde_attr(field: &syn::Field, name: &str) -> bool
{
    field.attrs.iter().any(|attr| {
        if !attr.path().is_ident("serde") {
            return false;
        }
        let Ok(nested) = attr.parse_args_with(
            syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
        ) else {
            return false;
        };
        nested.iter().any(|meta| match meta {
            syn::Meta::Path(path) => path.is_ident(name),
            syn::Meta::NameValue(nv) => nv.path.is_ident(name),
            syn::Meta::List(_) => false,
        })
    })
}

/// Remove `#[defaulted(...)]` attributes from all fields.
fn strip_defaulted_field_attrs(fields: &mut syn::Fields)
{
    let syn::Fields::Named(named) = fields else {
        return;
    };
    for field in &mut named.named {
        field
            .attrs
            .retain(|attr| !attr.path().is_ident("defaulted"));
    }
}

/// Build a `#[serde(bound(serialize = "..."))]` attribute if any type
/// parameters need `Defaulted` bounds for `skip_serializing_if` functions.
///
/// This replaces serde's inferred serialize bounds, so we include
/// `T: ::serde::Serialize` for every type parameter alongside the
/// `Defaulted` bound where needed.  Returns `None` when no type
/// parameters require `Defaulted` (letting serde infer normally).
fn serde_serialize_bound(
    generics: &syn::Generics,
    fields: &syn::Fields,
) -> syn::Result<Option<syn::Attribute>>
{
    let type_params: Vec<_> = generics.type_params().collect();
    if type_params.is_empty() {
        return Ok(None);
    }

    let predicates = check_module::defaulted_predicates(generics, fields)?;
    if predicates.is_empty() {
        return Ok(None);
    }

    let defaulted_idents: HashSet<syn::Ident> = predicates
        .iter()
        .filter_map(|pred| {
            let syn::WherePredicate::Type(pt) = pred else {
                return None;
            };
            let syn::Type::Path(tp) = &pt.bounded_ty else {
                return None;
            };
            tp.path.get_ident().cloned()
        })
        .collect();

    let bound_str = type_params.iter().map(|tp| {
        let id = &tp.ident;
        if defaulted_idents.contains(id) {
            format!("{id}: ::serde::Serialize + ::defaulted::Defaulted")
        } else {
            format!("{id}: ::serde::Serialize")
        }
    });

    Ok(Some(syn::parse_quote!(
        #[serde(bound(serialize = #(#bound_str,)*))]
    )))
}
