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

use fluent_syntax::ast::{Entry, Message, Resource};
use icu_locid::Locale;
use syn::parse_quote;

#[derive(Clone, Debug)]
struct LocaleGroup {
    locale: Locale,
    messages: Box<[Option<Message<String>>]>,
}

impl LocaleGroup {
    fn new(
        locale: Locale,
        resource: Resource<String>,
        canonical_messages: &[Message<String>],
    ) -> Self {
        let mut messages = vec![None; canonical_messages.len()].into_boxed_slice();

        for entry in resource.body {
            if let Entry::Message(message) = entry {
                let index = canonical_messages
                    .iter()
                    .position(|canonical_message| canonical_message.id.name == message.id.name)
                    .expect("Message ID must be in canonical group");

                assert!(messages[index].is_none());
                messages[index] = Some(message);
            }
        }

        Self { locale, messages }
    }
}

#[derive(Clone, Debug)]
pub struct Group {
    canonical_locale: Locale,
    canonical_messages: Vec<Message<String>>,
    extra_locales: Vec<LocaleGroup>,
}

impl Group {
    pub fn new(canonical_locale: Locale, mut resources: HashMap<Locale, Resource<String>>) -> Self {
        let canonical_resource = resources.remove(&canonical_locale).unwrap();

        let mut canonical_messages = Vec::new();

        for entry in canonical_resource.body {
            if let Entry::Message(message) = entry {
                canonical_messages.push(message);
            }
        }

        let extra_locales = resources
            .into_iter()
            .map(|(locale, resource)| LocaleGroup::new(locale, resource, &canonical_messages))
            .collect::<Vec<_>>();

        Self {
            canonical_locale,
            canonical_messages,
            extra_locales,
        }
    }

    /// Returns an iterator over the localized messages paired with the relevant locale
    fn messages_in_column(
        &self,
        message_column: usize,
    ) -> impl Iterator<Item = (&Locale, &Message<String>)> {
        self.extra_locales.iter().filter_map(move |locale_group| {
            locale_group
                .messages
                .get(message_column)
                .unwrap()
                .as_ref()
                .map(|message| (&locale_group.locale, message))
        })
    }

    pub fn message(&self, id: &str) -> syn::ExprBlock {
        let message_column = self
            .canonical_messages
            .iter()
            .position(|message| message.id.name == id)
            .expect("Message id must be valid");

        let additional_locale_names = self
            .messages_in_column(message_column)
            .map(|(locale, _message)| locale.to_string())
            .map(|locale_string| syn::LitStr::new(&locale_string, proc_macro2::Span::call_site()));

        let additional_locale_messages = self
            .messages_in_column(message_column)
            .map(|(_locale, message)| crate::parse_fluent::message(message));

        parse_quote! {{
            #(if locale.normalizing_eq(#additional_locale_names) { return #additional_locale_messages })else*
        }}
    }

    pub fn canonical_message(&self, id: &str) -> syn::Expr {
        let message_column = self
            .canonical_messages
            .iter()
            .position(|message| message.id.name == id)
            .expect("Message id must be valid");
        crate::parse_fluent::message(&self.canonical_messages[message_column])
    }
}