//! atproto-static-web   A simple web viewer for AT Proto.
//! Copyright (C) 2025  AverageHelper
//!
//! This program is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by
//! the Free Software Foundation, either version 3 of the License, or
//! (at your option) any later version.
//!
//! This program is distributed in the hope that it will be useful,
//! but WITHOUT ANY WARRANTY; without even the implied warranty of
//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//! GNU General Public License for more details.
//!
//! You should have received a copy of the GNU General Public License
//! along with this program.  If not, see <https://www.gnu.org/licenses/>.

use rocket::{
	Response,
	fairing::{Fairing, Info},
	http::hyper::header::{ACCEPT_LANGUAGE, CONTENT_LANGUAGE},
	request::{FromRequest, Outcome, Request},
};
use std::{convert::Infallible, fmt::Display};

/// Represents a locale code into which our interface has been translated.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Lang(LangImpl);

// `LangImpl` is an efficient enum of translation strings generated via build.rs:
include!(concat!(env!("OUT_DIR"), "/langs.rs"));

static DEFAULT_LANG: LangImpl = LangImpl::En;
static DEBUG_LANG: LangImpl = LangImpl::Qqx;
static AVAILABLE_LANGS: &[LangImpl] = LangImpl::all();

impl Default for Lang {
	fn default() -> Self {
		Self(DEFAULT_LANG)
	}
}

impl Display for Lang {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", self.0)
	}
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for Lang {
	type Error = Infallible;

	async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
		let langs_from_query = req
			.query_value::<Vec<&str>>("lang")
			.unwrap_or(Ok(Vec::new()))
			.unwrap_or_default();

		let langs_from_header = req
			.headers()
			.get_one(ACCEPT_LANGUAGE.as_str())
			.unwrap_or(DEFAULT_LANG.as_str())
			// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language
			.split(",")
			.filter_map(|lang|
				// omit the country code
				lang.split(&['-', ';'][..]).next());

		let requested_langs = [langs_from_query, langs_from_header.collect()].concat();

		// The first requested locale that we support
		for lang in requested_langs {
			if lang == DEBUG_LANG.as_str() {
				// "qqx" isn't a listed locale, but we accept it, with special meaning: show the translation keys in-place instead of the localized values.
				return Outcome::Success(Self::debug());
			}
			if let Some(lang) = AVAILABLE_LANGS.iter().find(|l| l.as_str() == lang) {
				return Outcome::Success(Self(*lang));
			}
		}

		Outcome::Success(Self::default())
	}
}

impl Lang {
	/// Returns the list of all supported locales.
	#[inline]
	pub fn all() -> Vec<Self> {
		AVAILABLE_LANGS.iter().copied().map(Self).collect()
	}

	/// Returns the locale string, e.g. `"en"`.
	#[inline]
	pub const fn as_str(&self) -> &'static str {
		self.0.as_str()
	}

	/// Returns `true` if the locale is the special `qqx` debug locale.
	#[inline]
	pub fn is_debug(&self) -> bool {
		self.0 == DEBUG_LANG
	}

	/// The special debug locale.
	#[inline]
	const fn debug() -> Self {
		Self(DEBUG_LANG)
	}
}

/// A Rocket `Fairing` that returns the interface language in the `Content-Language` response header.
pub struct ContentLanguage(());

/// Returns a Rocket `Fairing` that returns the interface language in the `Content-Language` response header.
pub fn content_language() -> ContentLanguage {
	ContentLanguage(())
}

#[rocket::async_trait]
impl Fairing for ContentLanguage {
	fn info(&self) -> Info {
		Info {
			name: "Content Language",
			kind: rocket::fairing::Kind::Response,
		}
	}

	async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
		let lang: Lang = req.guard().await.expect("lang never fails");
		res.set_raw_header(CONTENT_LANGUAGE.as_str(), lang.as_str());
		// TODO: Specify multiple languages if a post has a different set of langs than the content. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Language#indicating_a_target_audience_for_a_resource
	}
}

/// Translates the given key to the requested lang.
#[macro_export]
macro_rules! tr {
	($lang:ident, $key:expr) => {
		if $lang.is_debug() {
			::std::borrow::Cow::from(["(", $key, ")"].concat())
		} else {
			::rust_i18n::t!($key, locale = $lang.as_str())
		}
	};
	($lang:ident, $key:expr, $($vars:tt)*) => {
		if $lang.is_debug() {
			::std::borrow::Cow::from(["(", $key, ")"].concat())
		} else {
			::rust_i18n::t!($key, $($vars)*, locale = $lang.as_str())
		}
	};
}
pub use tr;
