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

use super::{Error, MessageContext};
use crate::macro_impl::derive::ReferenceKind;

use fixed_decimal::{Decimal, FloatPrecision};
use fluent_syntax::ast::{Expression, InlineExpression, PatternElement, VariantKey};
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
use miette::SourceSpan;
use quote::{format_ident, quote};
use syn::parse_quote;

pub fn message_body(message_context: MessageContext) -> Result<syn::Expr, Error> {
    let mut write_expressions: Vec<syn::Expr> =
        Vec::with_capacity(message_context.pattern.elements.len());

    for element in &message_context.pattern.elements {
        match element {
            PatternElement::TextElement { value, span: _ } => {
                let byte_string_literal = proc_macro2::Literal::byte_string(value.as_bytes());
                write_expressions.push(parse_quote!(writer.write_all(#byte_string_literal)?));
            }
            PatternElement::Placeable {
                expression,
                span: _,
            } => {
                write_expressions.push(match expression {
                    Expression::Select { selector, variants, span: _ } => {
                        let match_target = inline_expression(selector, message_context)?;
                        let default_arm = OnceCell::new();
                        let mut additional_arms = Vec::with_capacity(variants.len());

                        for variant in variants {
                            let variant_pattern: syn::Pat = match &variant.key {
                                VariantKey::Identifier { name } => {
                                    let ident_pascal_case =
                                        format_ident!("{}", name.to_pascal_case());
                                    parse_quote!(::fluent_embed::icu_plurals::PluralCategory::#ident_pascal_case)
                                }
                                VariantKey::NumberLiteral { .. } => todo!(),
                            };

                            // Create a new `MessageContext` for each variant body
                            let variant_context = MessageContext {
                                pattern: &variant.value,
                                ..message_context
                            };
                            let variant_body = message_body(variant_context)?;

                            // The default pattern must go last so we don't generate invalid match stataments

                            if variant.default {
                                default_arm
                                    .set(quote!(#variant_pattern | _ => #variant_body))
                                    .expect("Multiple default patterns for match statement.");
                            } else {
                                additional_arms.push(quote!(#variant_pattern => #variant_body));
                            }
                        }

                        // The parser should guarantee a default arm is available
                        let default_arm = default_arm.get().unwrap();

                        parse_quote! {
                            match plural_rules.category_for(*#match_target) {
                                #(#additional_arms,)*
                                #default_arm,
                            }
                        }
                    }
                    Expression::Inline(expression, _span) => {
                        let expression = inline_expression(expression, message_context)?;
                        parse_quote!(#expression.message_for_locale(writer, locale)?)
                    }
                });
            }
        }
    }

    Ok(parse_quote! {
        {
            #(#write_expressions;)*
        }
    })
}

fn inline_expression(
    expression: &InlineExpression<String>,
    message_context: MessageContext,
) -> Result<syn::Expr, Error> {
    Ok(match expression {
        InlineExpression::StringLiteral { value, span: _ } => {
            let byte_string_literal = proc_macro2::Literal::byte_string(value.as_bytes());
            parse_quote!(#byte_string_literal)
        }
        InlineExpression::NumberLiteral { value, span: _ } => {
            let parsed_value: f64 = value.parse().unwrap();
            // Check validity at compile-time, so we avoid generating code that will break at runtime
            assert!(Decimal::try_from_f64(parsed_value, FloatPrecision::RoundTrip).is_ok());

            let float_literal = proc_macro2::Literal::f64_suffixed(parsed_value);
            parse_quote!(::fluent_embed::Decimal::try_from_f64(#float_literal, ::fluent_embed::FloatPrecision::RoundTrip).unwrap())
        }
        InlineExpression::VariableReference { id, span } => {
            // Make sure the referenced variable is in the set of valid variables
            let fluent_name = &id.name;
            let rust_name = id.name.to_snake_case();

            let ident = if let Some(variable) = message_context
                .derive_context
                .valid_references
                .get(&rust_name)
            {
                format_ident!("{variable}")
            } else {
                let source_code = message_context.source.named_source.clone();

                return Err(Error::InvalidReference {
                    fluent_name: fluent_name.clone(),
                    rust_name,
                    source_code,
                    span: SourceSpan::from(span.0.to_owned()),
                    valid_references: format!(
                        "the following references are valid:\n{}",
                        message_context
                            .derive_context
                            .valid_references
                            .iter()
                            .map(|field| format!("- ${}", field.to_lower_camel_case()))
                            .collect::<Vec<String>>()
                            .join("\n")
                    ),
                });
            };

            match message_context.derive_context.reference_kind {
                ReferenceKind::EnumField => parse_quote!(#ident),
                ReferenceKind::StructField => parse_quote!(self.#ident),
            }
        }
        _ => {
            dbg!(expression);
            todo!()
        }
    })
}