Experimenting with more structured ways to handle command-line input/output in Rust
use std::collections::HashMap;

use crate::group::Group;
use heck::{ToKebabCase, ToSnakeCase};
use icu_locid::{locale, Locale};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::punctuated::Punctuated;

pub fn derive_enum(
    group: Group,
    ident: syn::Ident,
    variants: Punctuated<syn::Variant, syn::token::Comma>,
) -> TokenStream {
    let mut idents = Vec::with_capacity(variants.len());
    let mut extra_messages = Vec::with_capacity(variants.len());
    let mut canonical_messages = Vec::with_capacity(variants.len());

    for variant in variants {
        let kebab_case_ident = variant.ident.to_string().to_kebab_case();

        let variant_ident = variant.ident;
        idents.push(match variant.fields {
            syn::Fields::Named(fields) => {
                // Get the name of each field for pattern-matching
                let field_idents = fields
                    .named
                    .iter()
                    .map(|field| field.ident.as_ref().unwrap())
                    .map(|ident| format_ident!("{}", ident.to_string().to_snake_case()));

                quote!(#variant_ident { #(#field_idents),* })
            }
            syn::Fields::Unnamed(_) => todo!(),
            syn::Fields::Unit => quote!(#variant_ident),
        });

        extra_messages.push(group.message(&kebab_case_ident));
        canonical_messages.push(group.canonical_message(&kebab_case_ident));
    }

    // TODO: locale matching should use a better algorithm
    quote! {
        impl #ident {
            fn localize(&self, locales: &[&::icu_locid::Locale], plural_rules: &::icu_plurals::PluralRules) -> String {
                // Check each locale to see if it's implemented for this message
                for locale in locales {
                    match self {
                        #(Self::#idents => #extra_messages),*
                    }
                }

                // Fall back to default locale
                match self {
                    #(Self::#idents => #canonical_messages),*
                }
            }
        }
    }
}

pub fn attribute_groups(path_literal: syn::LitStr) -> Group {
    // Read the fluent file at the given path
    let manifest_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
    let attribute_glob = path_literal.value();

    let mut resources = HashMap::new();

    let glob = wax::Glob::new(&attribute_glob).unwrap();
    for potential_entry in glob.walk(&manifest_root) {
        // TODO: this assumes that the locale is the first capture
        let entry = potential_entry.unwrap();
        let captured_locale = entry.matched().get(1).unwrap();

        // Captured directories may suffix with a `/`
        let stripped_locale = captured_locale.strip_suffix('/').unwrap_or(captured_locale);
        let locale = Locale::try_from_bytes(stripped_locale.as_bytes()).unwrap();

        // Parse the file into a `Group`
        let fluent_contents = std::fs::read_to_string(entry.path()).unwrap();
        let resource = fluent_syntax::parser::parse(fluent_contents).unwrap();
        resources.insert(locale, resource);
    }

    Group::new(locale!("en-US"), resources)
}

pub fn localize(
    path: syn::LitStr,
    ident: syn::Ident,
    variants: Punctuated<syn::Variant, syn::token::Comma>,
) -> TokenStream {
    let groups = attribute_groups(path);
    derive_enum(groups, ident, variants)
}