//! Implementations of [`AsRef`]/[`AsMut`] derive macros.

pub(crate) mod r#mut;
pub(crate) mod r#ref;

use std::{borrow::Cow, iter};

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_quote, spanned::Spanned, Token};

use crate::utils::{
    attr::{self, ParseMultiple as _},
    Either, GenericsSearch, Spanning,
};

/// Expands an [`AsRef`]/[`AsMut`] derive macro.
pub fn expand(
    input: &syn::DeriveInput,
    trait_info: ExpansionCtx<'_>,
) -> syn::Result<TokenStream> {
    let (trait_ident, attr_name, _) = trait_info;

    let data = match &input.data {
        syn::Data::Struct(data) => Ok(data),
        syn::Data::Enum(e) => Err(syn::Error::new(
            e.enum_token.span(),
            format!("`{trait_ident}` cannot be derived for enums"),
        )),
        syn::Data::Union(u) => Err(syn::Error::new(
            u.union_token.span(),
            format!("`{trait_ident}` cannot be derived for unions"),
        )),
    }?;

    let expansions = if let Some(attr) =
        StructAttribute::parse_attrs(&input.attrs, attr_name)?
    {
        if data.fields.len() != 1 {
            return Err(syn::Error::new(
                if data.fields.is_empty() {
                    data.struct_token.span
                } else {
                    data.fields.span()
                },
                format!(
                    "`#[{attr_name}(...)]` attribute can only be placed on structs with exactly \
                     one field",
                ),
            ));
        }

        let field = data.fields.iter().next().unwrap();
        if FieldAttribute::parse_attrs(&field.attrs, attr_name)?.is_some() {
            return Err(syn::Error::new(
                field.span(),
                format!("`#[{attr_name}(...)]` cannot be placed on both struct and its field"),
            ));
        }

        vec![Expansion {
            trait_info,
            ident: &input.ident,
            generics: &input.generics,
            field,
            field_index: 0,
            conversions: Some(attr.into_inner()),
        }]
    } else {
        let attrs = data
            .fields
            .iter()
            .map(|field| FieldAttribute::parse_attrs(&field.attrs, attr_name))
            .collect::<syn::Result<Vec<_>>>()?;

        let present_attrs = attrs.iter().filter_map(Option::as_ref).collect::<Vec<_>>();

        let all = present_attrs
            .iter()
            .all(|attr| matches!(attr.item, FieldAttribute::Skip(_)));

        if !all {
            if let Some(skip_attr) = present_attrs.iter().find_map(|attr| {
                if let FieldAttribute::Skip(skip) = &attr.item {
                    Some(attr.as_ref().map(|_| skip))
                } else {
                    None
                }
            }) {
                return Err(syn::Error::new(
                    skip_attr.span(),
                    format!(
                        "`#[{attr_name}({})]` cannot be used in the same struct with other \
                         `#[{attr_name}(...)]` attributes",
                        skip_attr.name(),
                    ),
                ));
            }
        }

        if all {
            data.fields
                .iter()
                .enumerate()
                .zip(attrs)
                .filter_map(|((i, field), attr)| {
                    attr.is_none().then_some(Expansion {
                        trait_info,
                        ident: &input.ident,
                        generics: &input.generics,
                        field,
                        field_index: i,
                        conversions: None,
                    })
                })
                .collect()
        } else {
            data.fields
                .iter()
                .enumerate()
                .zip(attrs)
                .filter_map(|((i, field), attr)| match attr.map(Spanning::into_inner) {
                    Some(
                        attr @ (FieldAttribute::Empty(_)
                        | FieldAttribute::Forward(_)
                        | FieldAttribute::Types(_)),
                    ) => Some(Expansion {
                        trait_info,
                        ident: &input.ident,
                        generics: &input.generics,
                        field,
                        field_index: i,
                        conversions: attr.into(),
                    }),
                    Some(FieldAttribute::Skip(_)) => unreachable!(),
                    None => None,
                })
                .collect()
        }
    };
    Ok(expansions
        .into_iter()
        .map(ToTokens::into_token_stream)
        .collect())
}

/// Type alias for an expansion context:
/// - [`syn::Ident`] of the derived trait.
/// - [`syn::Ident`] of the derived trait method.
/// - Optional `mut` token indicating [`AsMut`] expansion.
///
/// [`syn::Ident`]: struct@syn::Ident
type ExpansionCtx<'a> = (&'a syn::Ident, &'a syn::Ident, Option<&'a Token![mut]>);

/// Expansion of a macro for generating [`AsRef`]/[`AsMut`] implementations for a single field of a
/// struct.
struct Expansion<'a> {
    /// [`ExpansionCtx`] of the derived trait.
    trait_info: ExpansionCtx<'a>,

    /// [`syn::Ident`] of the struct.
    ///
    /// [`syn::Ident`]: struct@syn::Ident
    ident: &'a syn::Ident,

    /// [`syn::Generics`] of the struct.
    generics: &'a syn::Generics,

    /// [`syn::Field`] of the struct.
    field: &'a syn::Field,

    /// Index of the [`syn::Field`].
    field_index: usize,

    /// Attribute specifying which conversions should be generated.
    conversions: Option<attr::Conversion>,
}

impl ToTokens for Expansion<'_> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let field_ty = &self.field.ty;
        let field_ident = self.field.ident.as_ref().map_or_else(
            || Either::Right(syn::Index::from(self.field_index)),
            Either::Left,
        );

        let (trait_ident, method_ident, mut_) = &self.trait_info;
        let ty_ident = &self.ident;

        let field_ref = quote! { & #mut_ self.#field_ident };

        let generics_search = GenericsSearch {
            types: self.generics.type_params().map(|p| &p.ident).collect(),
            lifetimes: self
                .generics
                .lifetimes()
                .map(|p| &p.lifetime.ident)
                .collect(),
            consts: self.generics.const_params().map(|p| &p.ident).collect(),
        };
        let field_contains_generics = generics_search.any_in(field_ty);

        let is_blanket =
            matches!(&self.conversions, Some(attr::Conversion::Forward(_)));

        let return_tys = match &self.conversions {
            Some(attr::Conversion::Forward(_)) => {
                Either::Left(iter::once(Cow::Owned(parse_quote! { __AsT })))
            }
            Some(attr::Conversion::Types(tys)) => {
                Either::Right(tys.0.iter().map(Cow::Borrowed))
            }
            None => Either::Left(iter::once(Cow::Borrowed(field_ty))),
        };

        for return_ty in return_tys {
            /// Kind of a generated implementation, chosen based on attribute arguments.
            enum ImplKind {
                /// Returns a reference to a field.
                Direct,

                /// Forwards `as_ref`/`as_mut` call on a field.
                Forwarded,

                /// Uses autoref-based specialization to determine whether to use direct or
                /// forwarded implementation, based on whether the field and the return type match.
                ///
                /// Doesn't work when generics are involved.
                Specialized,
            }

            let impl_kind = if is_blanket {
                ImplKind::Forwarded
            } else if field_ty == return_ty.as_ref() {
                ImplKind::Direct
            } else if field_contains_generics || generics_search.any_in(&return_ty) {
                ImplKind::Forwarded
            } else {
                ImplKind::Specialized
            };

            let trait_ty = quote! {
                derive_more::core::convert::#trait_ident <#return_ty>
            };

            let generics = match &impl_kind {
                ImplKind::Forwarded => {
                    let mut generics = self.generics.clone();
                    generics
                        .make_where_clause()
                        .predicates
                        .push(parse_quote! { #field_ty: #trait_ty });
                    if is_blanket {
                        generics
                            .params
                            .push(parse_quote! { #return_ty: ?derive_more::core::marker::Sized });
                    }
                    Cow::Owned(generics)
                }
                ImplKind::Direct | ImplKind::Specialized => {
                    Cow::Borrowed(self.generics)
                }
            };
            let (impl_gens, _, where_clause) = generics.split_for_impl();
            let (_, ty_gens, _) = self.generics.split_for_impl();

            let body = match &impl_kind {
                ImplKind::Direct => Cow::Borrowed(&field_ref),
                ImplKind::Forwarded => Cow::Owned(quote! {
                    <#field_ty as #trait_ty>::#method_ident(#field_ref)
                }),
                ImplKind::Specialized => Cow::Owned(quote! {
                    use derive_more::__private::ExtractRef as _;

                    let conv =
                        <derive_more::__private::Conv<& #mut_ #field_ty, #return_ty>
                         as derive_more::core::default::Default>::default();
                    (&&conv).__extract_ref(#field_ref)
                }),
            };

            quote! {
                #[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
                #[automatically_derived]
                impl #impl_gens #trait_ty for #ty_ident #ty_gens #where_clause {
                    #[inline]
                    fn #method_ident(& #mut_ self) -> & #mut_ #return_ty {
                        #body
                    }
                }
            }
            .to_tokens(tokens);
        }
    }
}

/// Representation of an [`AsRef`]/[`AsMut`] derive macro struct container attribute.
///
/// ```rust,ignore
/// #[as_ref(forward)]
/// #[as_ref(<types>)]
/// ```
type StructAttribute = attr::Conversion;

/// Representation of an [`AsRef`]/[`AsMut`] derive macro field attribute.
///
/// ```rust,ignore
/// #[as_ref]
/// #[as_ref(skip)] #[as_ref(ignore)]
/// #[as_ref(forward)]
/// #[as_ref(<types>)]
/// ```
type FieldAttribute = attr::FieldConversion;
