use proc_macro::{TokenStream, Span};  // 手続き的マクロ用
use syn::{parse_macro_input, DeriveInput, Expr, ExprBinary, Ident, ItemFn}; // 手続き的マクロ用
use quote::quote;
use syn::{Data}; // 理解度チェック用

// 関数風マクロ
#[proc_macro]
pub fn as_is(input: TokenStream) -> TokenStream {
    // dbg!(&input);
    input
}

#[proc_macro]
pub fn my_power(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as Expr);
    //dbg!(&ast);
    let res = quote! {
        (#ast) * (#ast)
    };
    res.into()
}

#[proc_macro]
pub fn to_sub(input: TokenStream) -> TokenStream {
    let mut ast = parse_macro_input!(input as ExprBinary);
    ast.op = syn::BinOp::Sub(syn::token::Minus {
        spans: [Span::call_site().into()]
    });
    //dbg!(&ast);
    let res = quote! {
        #ast
    };
    res.into()
}

// deriveマクロ
#[proc_macro_derive(DeriveTrait)]
pub fn derive_trait_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = ast.ident;
    let res = quote! {
        impl DeriveTrait for #name {
            fn introduce(&self) -> String {
                format!("私の名前は {} です！", stringify!(#name))
            }
        }
    };
    res.into()
}

// 属性風マクロ
#[proc_macro_attribute]
pub fn measure_time(attr: TokenStream, item: TokenStream) -> TokenStream {
    let ident = parse_macro_input!(attr as Ident);
    let item = parse_macro_input!(item as ItemFn);
    let signature = &item.sig;
    let block = &item.block;
    let res = quote! {
        #signature {
            let start = std::time::Instant::now();
            println!("関数 {} の計測開始", stringify!(#ident));
            #block
            println!("関数 {} の実行時間: {:?}", stringify!(#ident), start.elapsed());
        }
    };
    res.into()
}

// 理解度チェック用
#[proc_macro]
pub fn generate_func(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as Ident);
    let res = quote! {
        fn #ast() {
            println!("これはマクロで生成された関数です: {}", stringify!(#ast));
        }
    };
    res.into()
}

#[proc_macro_derive(DeriveItem)]
pub fn derive_item_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;
    let data = &ast.data;
    let res = match data {
        Data::Struct(_) => {
            quote! {
                impl DeriveItem for #name {
                    fn get_type(&self) -> String {
                        "構造体".to_string()
                    }
                }
            }
        },
        Data::Enum(_) => {
            quote! {
                impl DeriveItem for #name {
                    fn get_type(&self) -> String {
                        "列挙体".to_string()
                    }
                }
            }
        },
        Data::Union(_) => {
            quote! {
                impl DeriveItem for #name {
                    fn get_type(&self) -> String {
                        "共用体".to_string()
                    }
                }
            }
        },
    };
    res.into()
}
