use proc_macro::TokenStream;

use convert_case::{Case, Casing};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::__private::TokenStream2;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{braced, parse_macro_input, Ident, Token};

#[proc_macro]
pub fn impl_extension_attributes(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as ImplExtensionAttributes);
    input.to_token_stream().into()
}

struct ImplExtensionAttributes {
    name: Ident,
    attrs: Punctuated<Ident, Token![,]>,
}

impl Parse for ImplExtensionAttributes {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;

        let name = input.parse()?;
        braced!(content in input);
        let attrs = content.parse_terminated(Ident::parse, Token![,])?;

        Ok(ImplExtensionAttributes { name, attrs })
    }
}

impl ToTokens for ImplExtensionAttributes {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        let name = &self.name;
        let name_string = name.to_string();
        let camel_name = name_string
            .strip_prefix("r#")
            .unwrap_or(&name_string)
            .to_case(Case::UpperCamel);
        let extension_name = Ident::new(format!("{}Extension", &camel_name).as_str(), name.span());

        let impls = self.attrs.iter().map(|ident| {
            let d = quote! { #name::#ident };
            quote! {
                fn #ident(self, value: impl IntoAttributeValue) -> Self {
                    let d = #d;
                    self.push_attribute(d.0, d.1, value, d.2)
                }
            }
        });
        tokens.append_all(quote! {
            pub trait #extension_name: HasAttributes + Sized {
                #(#impls)*
            }
        });
    }
}
