use crate::add_assign_like;
use crate::mul_helpers::generics_and_exprs;
use crate::utils::{AttrParams, MultiFieldData, RefType, State};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::collections::HashSet;
use std::iter;
use syn::{DeriveInput, Ident, Result};

pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
    let method_name = trait_name
        .to_lowercase()
        .trim_end_matches("assign")
        .to_string()
        + "_assign";

    let mut state = State::with_attr_params(
        input,
        trait_name,
        quote!(::core::ops),
        method_name,
        AttrParams::struct_(vec!["forward"]),
    )?;
    if state.default_info.forward {
        return Ok(add_assign_like::expand(input, trait_name));
    }
    let scalar_ident = &Ident::new("__RhsT", Span::call_site());
    state.add_trait_path_type_param(quote!(#scalar_ident));
    let multi_field_data = state.enabled_fields_data();
    let MultiFieldData {
        input_type,
        field_types,
        ty_generics,
        trait_path,
        trait_path_with_params,
        method_ident,
        ..
    } = multi_field_data.clone();

    let tys = field_types.iter().collect::<HashSet<_>>();
    let tys = tys.iter();
    let trait_path_iter = iter::repeat(trait_path_with_params);

    let type_where_clauses = quote! {
        where #(#tys: #trait_path_iter),*
    };

    let (generics, exprs) = generics_and_exprs(
        multi_field_data.clone(),
        scalar_ident,
        type_where_clauses,
        RefType::Mut,
    );
    let (impl_generics, _, where_clause) = generics.split_for_impl();

    Ok(quote!(
        impl#impl_generics #trait_path<#scalar_ident> for #input_type#ty_generics #where_clause{
            #[inline]
            fn #method_ident(&mut self, rhs: #scalar_ident#ty_generics) {
                #(#exprs;
                  )*
            }
        }
    ))
}
