use proc_macro::TokenStream;
use syn::{spanned::Spanned, Data, Fields};

#[proc_macro_derive(Extract)]
pub fn extract(input: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(input);

    impl_extract(&ast)
}

fn impl_extract(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;

    match &ast.data {
        // Dispatch over struct: extract args directly from context
        Data::Struct(st) => match &st.fields {
            Fields::Named(fields) => {
                let extracted = fields.named.iter().map(|field| {
                    let ident = &field.ident;
                    let ty = &field.ty;

                    quote::quote_spanned! {field.span()=>
                        #ident : crate::utils::extract_arg!(ctx, #ident, #ty)
                    }
                });

                TokenStream::from(quote::quote! {
                    impl Extract for #name {
                        fn extract(ctx: crate::ApplicationContext) -> Self {
                            Self {
                                #(#extracted,)*
                            }
                        }
                    }
                })
            }
            Fields::Unit => TokenStream::from(quote::quote! {
                impl Extract for #name {
                    fn extract(ctx: crate::ApplicationContext) -> Self {
                        Self {}
                    }
                }
            }),
            _ => {
                panic!("Only named/unit structs can derive Extract");
            }
        },

        _ => {
            panic!("Only structs can derive Extract");
        }
    }
}
