Documentation
use proc_macro2::Span;
use proc_macro2::TokenStream;
use procmeta::prelude::*;
use quote::format_ident;
use quote::quote;
use syn::Data;
use syn::DataEnum;
use syn::DeriveInput;
use syn::Error;
use syn::Field;
use syn::Result;

pub fn fn_impl_expand(props: Vec<Field>, data: &DataEnum) -> Result<TokenStream> {
    if props.is_empty() {
        return Ok(quote!());
    }
    let mut result_token = quote!();
    let mut fn_tokens = vec![];
    for field in props {
        fn_tokens.push((field, quote!()));
    }
    for variant in &data.variants {
        let mut values = vec![];
        for attr in &variant.attrs {
            let value_meta = attr.meta.require_list()?;
            if !value_meta.path.is_ident("values") {
                continue;
            }
            if !values.is_empty() {
                return Err(Error::new(Span::call_site(), "duplicate define values"));
            }
            let mut parse_result = meta_list_to_expr(value_meta)?;
            values.append(&mut parse_result);
        }
        if values.len() != fn_tokens.len() {
            return Err(Error::new(
                Span::call_site(),
                "vaues's len not eq props's len",
            ));
        }

        let var_ident = &variant.ident;
        let match_branch_code = match &variant.fields {
            syn::Fields::Named(named) => {
                let mut fields = quote!();
                for field in &named.named {
                    let field_name = &field.ident;
                    fields = quote!(#field_name,);
                }
                quote!(Self::#var_ident {#fields})
            }
            syn::Fields::Unnamed(unamed) => {
                let mut fields = quote!();
                for (index, _field) in unamed.unnamed.iter().enumerate() {
                    let field_name = format_ident!("_{index}");
                    fields = quote!(#field_name,);
                }
                quote!(Self::#var_ident (#fields))
            }
            syn::Fields::Unit => {
                quote!(Self::#var_ident)
            }
        };
        for (index, fn_token) in fn_tokens.iter_mut().enumerate() {
            let value_token = &values[index];
            let field_token = &fn_token.1;
            fn_token.1 = quote! {
                #field_token
                #match_branch_code => #value_token.into(),
            };
        }
    }
    for (field, token) in fn_tokens {
        let field_name = field
            .ident
            .as_ref()
            .map(|t| t.to_string())
            .unwrap_or_default();
        let fn_name = format_ident!("get_{field_name}");
        let ret_ty = field.ty;
        result_token = quote! {
            #result_token

            pub fn #fn_name(&self) -> #ret_ty {
                match self {
                    #token
                }
            }
        };
    }
    Ok(result_token)
}

pub fn expand(input: DeriveInput) -> Result<TokenStream> {
    let ty = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let result_token = match &input.data {
        Data::Struct(_) => unimplemented!(),
        Data::Enum(data) => {
            let mut fields_named: Option<Vec<Field>> = None;
            for attr in input.attrs {
                let prop_meta = attr.meta.require_list()?;
                if !prop_meta.path.is_ident("props") {
                    continue;
                }
                if fields_named.is_some() {
                    return Err(Error::new(Span::call_site(), "duplicate define props"));
                }
                let parse_result = meta_list_to_fields(prop_meta)?;
                fields_named = Some(parse_result);
            }
            let fields = match fields_named {
                Some(props) => props,
                None => return Err(Error::new(Span::call_site(), "miss define values")),
            };
            let fn_tokens = fn_impl_expand(fields, data)?;
            quote! {
                impl #impl_generics #ty #ty_generics #where_clause {
                    #fn_tokens
                }
            }
        }
        Data::Union(_) => unimplemented!(),
    };
    Ok(result_token)
}