use heck::ToSnakeCase;
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Lit, Meta, parse_macro_input};

/// Derive `SelectEnum` for unit-variant enums.
///
/// Each variant maps to a `SelectOption` with a label and value string.
/// - **label** defaults to the variant name; override with `#[select(label = "...")]`
/// - **value** defaults to `snake_case(variant_name)`; override with `#[select(value = "...")]`
#[proc_macro_derive(SelectEnum, attributes(select))]
pub fn derive_select_enum(input: TokenStream) -> TokenStream {
  let input = parse_macro_input!(input as DeriveInput);
  let name = &input.ident;

  let variants = match &input.data {
    Data::Enum(data) => &data.variants,
    _ => {
      return syn::Error::new_spanned(&input.ident, "SelectEnum can only be derived for enums")
        .to_compile_error()
        .into();
    }
  };

  let mut option_exprs = Vec::new();
  let mut from_value_arms = Vec::new();

  for variant in variants {
    if !matches!(&variant.fields, Fields::Unit) {
      return syn::Error::new_spanned(variant, "SelectEnum only supports unit variants")
        .to_compile_error()
        .into();
    }

    let ident = &variant.ident;
    let mut label = ident.to_string();
    let mut value = ident.to_string().to_snake_case();

    for attr in variant.attrs.iter().filter(|a| a.path().is_ident("select")) {
      let nested = match attr
        .parse_args_with(syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated)
      {
        Ok(n) => n,
        Err(e) => return e.to_compile_error().into(),
      };

      for meta in nested {
        let Meta::NameValue(nv) = &meta else {
          continue;
        };
        let syn::Expr::Lit(syn::ExprLit {
          lit: Lit::Str(s), ..
        }) = &nv.value
        else {
          continue;
        };
        if nv.path.is_ident("label") {
          label = s.value();
        } else if nv.path.is_ident("value") {
          value = s.value();
        }
      }
    }

    option_exprs.push(quote! {
      blizz::SelectOption {
        label: ::std::string::String::from(#label),
        value: ::std::string::String::from(#value),
      }
    });

    from_value_arms.push(quote! {
      #value => ::core::option::Option::Some(#name::#ident),
    });
  }

  let expanded = quote! {
    impl blizz::SelectEnum for #name {
      fn options() -> ::std::vec::Vec<blizz::SelectOption> {
        ::std::vec![#(#option_exprs),*]
      }

      fn from_value(s: &str) -> ::core::option::Option<Self> {
        match s {
          #(#from_value_arms)*
          _ => ::core::option::Option::None,
        }
      }
    }
  };

  expanded.into()
}
