use proc_macro2::{Ident, TokenStream};
use quote::{quote, quote_spanned};
use syn::{spanned::Spanned, Data, Field, Generics};

use crate::{
    constants::EQUAL,
    utils::{get_any_attribute_by_meta_prefix, meta_not_found_in_all_fields, resolve_all_fields},
};

pub fn impl_equal(ident: &Ident, data: &Data, generics: &Generics) -> TokenStream {
    let ident_location = ident.to_string();
    let fields = resolve_all_fields(
        data,
        true,
        &is_equal_field,
        &meta_not_found_in_all_fields(EQUAL, &ident_location),
    );
    let equal_fields_token = equal_fields_token(&fields);
    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
    quote! {
        impl #impl_generics PartialEq for #ident #type_generics #where_clause {
            fn eq(&self, other: &Self) -> bool {
                #equal_fields_token
            }
        }
    }
}

fn is_equal_field(field: &Field) -> bool {
    get_any_attribute_by_meta_prefix(EQUAL, &field.attrs, false, "").is_some()
}

fn equal_fields_token(fields: &[Field]) -> TokenStream {
    let equal_fields = fields.iter().map(|f| equal_field_token(f));
    quote! {
        #(#equal_fields)&&*
    }
}

fn equal_field_token(field: &Field) -> TokenStream {
    let field = field.to_owned();
    let field_span = field.span();
    let field_ident = field.ident.unwrap();
    quote_spanned! { field_span =>
        self.#field_ident == other.#field_ident
    }
}
