use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{ItemFn, FnArg, punctuated::Punctuated, token::Comma, Attribute};
#[derive(Clone, Copy)]
enum When {
    Call,
    Return,
}
#[proc_macro_attribute]
pub fn param(args: TokenStream, func: TokenStream) -> TokenStream {
    parse(args, func, When::Call)
}
#[proc_macro_attribute]
pub fn debug(args: TokenStream, func: TokenStream) -> TokenStream {
    parse(args, func, When::Return)
}
fn parse(_: TokenStream, func: TokenStream, when: When) -> TokenStream {
    let func = syn::parse_macro_input!(func as ItemFn);
    let func_attrs = func.attrs; let attrs = parse_attrs(func_attrs);
    let func_vis = &func.vis; let func_block = &func.block; let sig = &func.sig;
    let func_constness = &sig.constness; let func_async = &sig.asyncness; let func_abi = &sig.abi; let func_name = &sig.ident; let func_generics = &sig.generics; let func_where_clause = &func_generics.where_clause; let func_inputs = &sig.inputs; let func_output = &sig.output; let params = parse_params(func_inputs);
    let (format, values) = get_log_format_values(&func_name.to_string(), params);
    let format = match when {
        When::Call => format!("call {format}"),
        When::Return => format!("called {format}"),
    };
    let log = match (when, func_output) {
        (When::Call, _) => quote! {
            macro_log::d!(#format, #values);
        },
        (_, syn::ReturnType::Default) => quote! {
            macro_log::d!("{}", call);
        },
        (_, syn::ReturnType::Type(_, _)) => quote! {
            macro_log::d!("{} => {:?}", call, return_value);
        },
    };
    let caller = match when {
        When::Call => quote! {
            #attrs
            #func_vis #func_constness #func_async #func_abi fn #func_name #func_generics(#func_inputs) #func_output #func_where_clause {
                #log
                #func_block
            }
        },
        When::Return => quote! {
            #attrs
            #func_vis #func_constness #func_async #func_abi fn #func_name #func_generics(#func_inputs) #func_output #func_where_clause {
                let call = format!(#format, #values);
                let return_value = #func_block;
                #log
                return_value
            }
        },
    };
    caller.into()
}
fn parse_attrs(attrs: Vec<Attribute>) -> proc_macro2::TokenStream {
    attrs
        .iter()
        .map(|attr| attr.to_token_stream().to_string())
        .collect::<Vec<String>>().join(" ")
        .parse().unwrap()
}
fn parse_params(func_inputs: &Punctuated<FnArg, Comma>) -> Vec<String> {
    let mut args = vec![];
    for arg in func_inputs.into_iter() {
        match arg {
            FnArg::Receiver(arg) => {
                let tokens = quote!(#arg).to_string();
                let (name, _vartype) = tokens.split_once(" : ").unwrap();
                args.push(name.into());
            }
            FnArg::Typed(_) => { let tokens = quote!(#arg).to_string();
                let (name, _vartype) = tokens.split_once(" : ").unwrap();
                args.push(name.into());
            }
        }
    }
    args
}
fn get_log_format_values(func_name: &str, args: Vec<String>) -> (String, proc_macro2::TokenStream) {
    let format_args = args.iter()
        .map(|it| if it.as_str() != "_" {
            format!("{it} = {{:?}}")
        } else {
            "_ = ?".to_string()
        })
        .collect::<Vec<String>>().join(", ");
    let format = format!("fn {func_name}({format_args})");
    let values = args.iter()
        .filter(|it| it.as_str() != "_")
        .map(|it| format!("{it}")).collect::<Vec<String>>().join(",");
    let values = values.parse::<proc_macro2::TokenStream>().unwrap();
    (format, values)
}