Framework for embedding localizations into Rust types
pub mod decimal;
pub mod layout;
pub mod list;
pub mod path;
pub mod string;
pub mod style;
pub mod time;

mod macros;

use std::sync::OnceLock;

use icu_decimal::options::DecimalFormatterOptions;
use icu_decimal::{DecimalFormatter, DecimalFormatterPreferences};
use icu_experimental::relativetime::{
    RelativeTimeFormatter, RelativeTimeFormatterOptions, RelativeTimeFormatterPreferences,
};
use icu_list::options::ListFormatterOptions;
use icu_list::{ListFormatter, ListFormatterPreferences};
use icu_locale::{Locale, locale};
use icu_plurals::{PluralRuleType, PluralRules, PluralRulesOptions, PluralRulesPreferences};

// Public re-exports of dependencies required at runtime, so macro-generated code
// can find all the dependencies it expects as long as this crate exists
pub mod macro_prelude {
    pub use icu_locale;
    pub use icu_plurals;
}

pub trait Localize {
    fn canonical_locale(&self) -> Locale {
        locale!("en-US")
    }

    fn available_locales(&self) -> Vec<Locale> {
        // TODO: keep track of all locales with Fluent data, and return only those
        vec![self.canonical_locale()]
    }

    fn localize(&self, context: &Context, buffer: &mut String);
}

pub struct Context {
    pub locale: Locale,
    pub use_colors: bool,
    decimal_formatter: OnceLock<DecimalFormatter>,
    list_formatters: [OnceLock<ListFormatter>; 3],
    plural_rules: [OnceLock<PluralRules>; 2],
    relative_time_formatters: [OnceLock<RelativeTimeFormatter>; 7],
}

impl Context {
    #[must_use]
    pub const fn new(locale: Locale, use_color: bool) -> Self {
        Self {
            locale,
            use_colors: use_color,
            decimal_formatter: OnceLock::new(),
            list_formatters: [const { OnceLock::new() }; 3],
            plural_rules: [const { OnceLock::new() }; 2],
            relative_time_formatters: [const { OnceLock::new() }; 7],
        }
    }

    pub fn decimal_formatter(&self) -> &DecimalFormatter {
        self.decimal_formatter.get_or_init(|| {
            DecimalFormatter::try_new(
                DecimalFormatterPreferences::from(&self.locale),
                DecimalFormatterOptions::default(),
            )
            .unwrap()
        })
    }

    pub fn list_formatter(&self, list_type: list::ListType) -> &ListFormatter {
        let index = match list_type {
            list::ListType::And => 0,
            list::ListType::Or => 1,
            list::ListType::Unit => 2,
        };

        self.list_formatters[index].get_or_init(|| {
            let preferences = ListFormatterPreferences::from(&self.locale);
            let options = ListFormatterOptions::default();

            match list_type {
                list::ListType::And => ListFormatter::try_new_and(preferences, options),
                list::ListType::Or => ListFormatter::try_new_or(preferences, options),
                list::ListType::Unit => ListFormatter::try_new_unit(preferences, options),
            }
            .unwrap()
        })
    }

    pub fn plural_rule(&self, rule_type: PluralRuleType) -> Option<&PluralRules> {
        let index = match rule_type {
            PluralRuleType::Cardinal => 0,
            PluralRuleType::Ordinal => 1,
            _ => return None,
        };

        let plural_rules = self.plural_rules[index].get_or_init(|| {
            PluralRules::try_new(
                PluralRulesPreferences::from(&self.locale),
                PluralRulesOptions::default().with_type(rule_type),
            )
            .unwrap()
        });
        Some(plural_rules)
    }

    pub fn relative_time_formatter(&self, unit: jiff::Unit) -> Option<&RelativeTimeFormatter> {
        let index = match unit {
            jiff::Unit::Year => 0,
            jiff::Unit::Month => 1,
            jiff::Unit::Week => 2,
            jiff::Unit::Day => 3,
            jiff::Unit::Hour => 4,
            jiff::Unit::Minute => 5,
            jiff::Unit::Second => 6,
            _ => return None,
        };

        let formatter = self.relative_time_formatters[index].get_or_init(|| {
            const OPTIONS: RelativeTimeFormatterOptions = RelativeTimeFormatterOptions {
                numeric: icu_experimental::relativetime::options::Numeric::Auto,
            };
            let preferences = RelativeTimeFormatterPreferences::from(&self.locale);

            match unit {
                jiff::Unit::Year => RelativeTimeFormatter::try_new_long_year(preferences, OPTIONS),
                jiff::Unit::Month => {
                    RelativeTimeFormatter::try_new_long_month(preferences, OPTIONS)
                }
                jiff::Unit::Week => RelativeTimeFormatter::try_new_long_week(preferences, OPTIONS),
                jiff::Unit::Day => RelativeTimeFormatter::try_new_long_day(preferences, OPTIONS),
                jiff::Unit::Hour => RelativeTimeFormatter::try_new_long_hour(preferences, OPTIONS),
                jiff::Unit::Minute => {
                    RelativeTimeFormatter::try_new_long_minute(preferences, OPTIONS)
                }
                jiff::Unit::Second => {
                    RelativeTimeFormatter::try_new_long_second(preferences, OPTIONS)
                }
                _ => unreachable!(),
            }
            .unwrap()
        });

        Some(formatter)
    }
}