use config::Config;
use tera::{Error, Function, Kwargs, State, TeraResult};

#[derive(Debug, Default)]
pub struct Trans {
    config: Config,
}

impl Trans {
    pub fn new(config: Config) -> Self {
        Self { config }
    }
}

impl Function<TeraResult<String>> for Trans {
    fn call(&self, kwargs: Kwargs, state: &State) -> TeraResult<String> {
        let key: String = kwargs.must_get("key")?;
        let lang: String = kwargs
            .get("lang")?
            .or(state.get("lang")?)
            .unwrap_or_else(|| self.config.default_language.clone());

        let term = self
            .config
            .get_translation(&lang, &key)
            .map_err(|e| Error::chain("Failed to retrieve term translation", e))?;

        Ok(term)
    }
}

#[derive(Debug)]
pub struct TextDirection {
    config: Config,
}

impl TextDirection {
    pub fn new(config: Config) -> Self {
        Self { config }
    }
}

impl Function<TeraResult<String>> for TextDirection {
    fn call(&self, kwargs: Kwargs, state: &State) -> TeraResult<String> {
        use unic_langid::{CharacterDirection, LanguageIdentifier};

        let lang: String = kwargs
            .get("lang")?
            .or(state.get("lang")?)
            .unwrap_or_else(|| self.config.default_language.clone());

        let lang: LanguageIdentifier = lang
            .parse()
            .map_err(|_| {
                Error::message(format!("`text_direction`: Language `{lang}` is not a valid Unicode Language Identifier (see http://unicode.org/reports/tr35/#Unicode_language_identifier)"))
            })?;

        let dir = match lang.character_direction() {
            CharacterDirection::LTR => "ltr",
            CharacterDirection::RTL => "rtl",
            // String must be compatible with the [`dir=` global HTML attribute][dir], so we only deal
            // with horizontal text directionality (only "ltr" or "rtl"). Traditional Mongolian is
            // written top-to-buttom with lines progressing left-to-right. Therefore, report "ltr" here.
            //
            // [dir]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/dir
            CharacterDirection::TTB => "ltr",
        };

        Ok(dir.to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tera::{Context, Kwargs, Value};

    const TRANS_CONFIG: &str = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"

[translations]
title = "Un titre"

[languages]
[languages.en]
[languages.en.translations]
title = "A title" "#;

    fn make_context_with_lang(lang: &str) -> Context {
        let mut ctx = Context::new();
        ctx.insert("lang", &lang);
        ctx
    }

    #[test]
    fn can_translate_a_string() {
        let config = Config::parse(TRANS_CONFIG).unwrap();
        let trans = Trans::new(config);

        // Default lang (fr)
        let kwargs = Kwargs::from([("key", Value::from("title"))]);
        let ctx = Context::new();
        assert_eq!(trans.call(kwargs, &State::new(&ctx)).unwrap(), "Un titre");

        // Explicit lang in kwargs
        let kwargs = Kwargs::from([("key", Value::from("title")), ("lang", Value::from("en"))]);
        let ctx = Context::new();
        assert_eq!(trans.call(kwargs, &State::new(&ctx)).unwrap(), "A title");

        // Lang from context
        let kwargs = Kwargs::from([("key", Value::from("title"))]);
        let ctx = make_context_with_lang("en");
        assert_eq!(trans.call(kwargs, &State::new(&ctx)).unwrap(), "A title");

        // Explicit lang in kwargs overrides context
        let kwargs = Kwargs::from([("key", Value::from("title")), ("lang", Value::from("fr"))]);
        let ctx = make_context_with_lang("en");
        assert_eq!(trans.call(kwargs, &State::new(&ctx)).unwrap(), "Un titre");
    }

    #[test]
    fn error_on_absent_translation_lang() {
        let config = Config::parse(TRANS_CONFIG).unwrap();
        let trans = Trans::new(config);

        let kwargs = Kwargs::from([("key", Value::from("title")), ("lang", Value::from("absent"))]);
        let ctx = Context::new();
        let error = trans.call(kwargs, &State::new(&ctx)).unwrap_err();
        assert!(error.to_string().contains("Failed to retrieve term translation"));
    }

    #[test]
    fn error_on_absent_translation_key() {
        let config = Config::parse(TRANS_CONFIG).unwrap();
        let trans = Trans::new(config);

        let kwargs = Kwargs::from([("key", Value::from("absent")), ("lang", Value::from("en"))]);
        let ctx = Context::new();
        let error = trans.call(kwargs, &State::new(&ctx)).unwrap_err();
        assert!(error.to_string().contains("Failed to retrieve term translation"));
    }

    #[test]
    fn can_determine_text_direction() {
        let config = Config::parse(TRANS_CONFIG).unwrap();
        let text_direction = TextDirection::new(config);

        // Default lang (fr)
        let kwargs = Kwargs::default();
        let ctx = Context::new();
        assert_eq!(text_direction.call(kwargs, &State::new(&ctx)).unwrap(), "ltr");

        // Explicit lang in kwargs (en)
        let kwargs = Kwargs::from([("lang", Value::from("en"))]);
        let ctx = Context::new();
        assert_eq!(text_direction.call(kwargs, &State::new(&ctx)).unwrap(), "ltr");

        // Lang from context (ar)
        let kwargs = Kwargs::from([("lang", Value::from("ar"))]);
        let ctx = make_context_with_lang("ar");
        assert_eq!(text_direction.call(kwargs, &State::new(&ctx)).unwrap(), "rtl");

        // Explicit lang in kwargs overrides context
        let kwargs = Kwargs::from([("lang", Value::from("ar"))]);
        let ctx = make_context_with_lang("en");
        assert_eq!(text_direction.call(kwargs, &State::new(&ctx)).unwrap(), "rtl");
    }

    #[test]
    fn error_on_invalid_lang_identifier() {
        let config = Config::parse(TRANS_CONFIG).unwrap();
        let text_direction = TextDirection::new(config);

        let kwargs = Kwargs::from([("lang", Value::from(123))]);
        let ctx = Context::new();
        let error = text_direction.call(kwargs, &State::new(&ctx)).unwrap_err();
        assert_eq!(
            error.to_string(),
            "`text_direction`: Language `123` is not a valid Unicode Language Identifier (see http://unicode.org/reports/tr35/#Unicode_language_identifier)",
        );

        let kwargs = Kwargs::from([("lang", Value::from("z"))]);
        let ctx = Context::new();
        let error = text_direction.call(kwargs, &State::new(&ctx)).unwrap_err();
        assert_eq!(
            error.to_string(),
            "`text_direction`: Language `z` is not a valid Unicode Language Identifier (see http://unicode.org/reports/tr35/#Unicode_language_identifier)",
        );
    }
}
