Experimenting with more structured ways to handle command-line input/output in Rust
use fluent_syntax::ast::{
    Expression, InlineExpression, Message, Pattern, PatternElement, Variant, VariantKey,
};
use heck::{ToPascalCase, ToSnakeCase};
use quote::{format_ident, quote};
use syn::parse_quote;

pub(crate) fn pattern(pattern: &Pattern<String>) -> syn::Expr {
    let mut format_body = String::new();
    let mut format_arguments = Vec::new();

    for element in &pattern.elements {
        match element {
            PatternElement::TextElement { value } => format_body.push_str(value),
            PatternElement::Placeable { expression } => {
                let expression = placeable_expression(&expression);
                format_body.push_str("{}");
                format_arguments.push(quote!(#expression));
            }
        }
    }

    let format_body_literal = proc_macro2::Literal::string(format_body.to_string().as_str());

    parse_quote!(format!(#format_body_literal, #(#format_arguments),*))
}

fn placeable_expression(expression: &Expression<String>) -> syn::Expr {
    match expression {
        Expression::Select { selector, variants } => {
            let target = inline_expression(selector);
            let arms: Vec<syn::Arm> = variants.iter().map(variant).collect();

            parse_quote! {
                match plural_rules.category_for(*#target) {
                    #(#arms),*
                }

            }
        }
        Expression::Inline(expression) => inline_expression(expression),
    }
}

fn inline_expression(expression: &InlineExpression<String>) -> syn::Expr {
    match expression {
        InlineExpression::StringLiteral { value } => {
            let string_literal = proc_macro2::Literal::string(value);
            parse_quote!(#string_literal)
        }
        InlineExpression::NumberLiteral { value } => {
            // FIXME: i128 is "good enough" for now but an incorrect representation!
            // e.g. decimals not supported
            let parsed_value = i128::from_str_radix(value, 10).unwrap();
            let number_literal = proc_macro2::Literal::i128_unsuffixed(parsed_value);
            parse_quote!(#number_literal)
        }
        InlineExpression::VariableReference { id } => {
            let ident = format_ident!("{}", id.name.to_snake_case());
            parse_quote!(#ident)
        }
        _ => {
            dbg!(expression);
            todo!()
        }
    }
}

fn variant(variant: &Variant<String>) -> syn::Arm {
    let base_pattern = variant_key(&variant.key);
    let body = pattern(&variant.value);

    // Default patterns match anything else
    // TODO: this can potentially generate unreachable patterns,
    // should be replaced with a more sophisticated implementation
    let pattern = if variant.default {
        parse_quote!(#base_pattern | _)
    } else {
        base_pattern
    };

    parse_quote!(#pattern => #body)
}

fn variant_key(variant_key: &VariantKey<String>) -> syn::Pat {
    match variant_key {
        VariantKey::Identifier { name } => {
            let ident = format_ident!("{}", name.to_pascal_case());
            parse_quote!(::icu_plurals::PluralCategory::#ident)
        }
        VariantKey::NumberLiteral { value } => todo!(),
    }
}

pub(crate) fn message(message: &Message<String>) -> syn::Expr {
    if let Some(value) = message.value.as_ref() {
        pattern(value)
    } else {
        parse_quote!(())
    }
}