//! atproto-static-web   A simple web viewer for AT Proto.
//! Copyright (C) 2026  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 crate::{
	atproto::Handle,
	constants::{GENERATOR, REPO_ISSUES_URL, REPO_URL, VERSION, WEB_COPYRIGHT_NOTICE},
	env::Env,
	server::lang::{Lang, tr},
};
use core::fmt::Display;
use maud::{DOCTYPE, Markup, PreEscaped, html};
use std::borrow::Cow;
use url::Url;

pub fn base(env: &Env, meta: Meta<'_>, main: Markup) -> Markup {
	let lang = meta.lang;

	html! {
		(DOCTYPE)
		(WEB_COPYRIGHT_NOTICE)
		html lang=(meta.lang) {
			head {
				meta charset="UTF-8";
				title { (meta.title) }
				link rel="icon" type="image/png" href="data:image/png;base64,";
				meta name="generator" content=(GENERATOR);
				meta name="robots" content="noai";
				meta name="viewport" content="width=device-width, initial-scale=1.0";
				meta name="color-scheme" content="light dark";
				meta property="og:type" content=(meta.og_type);
				@for locale in meta.content_locales {
					meta property="og:locale" content=(locale);
				}
				meta property="og:site_name" content=(env.site_name());
				meta property="og:title" content=(meta.og_title);
				meta property="og:url" content=(meta.canonical_url);
				@if let Some(published_time) = meta.og_published_time {
					meta property="og:published_time" content=(published_time);
					meta property="article:published_time" content=(published_time);
				}
				@if let Some(profile_username) = meta.profile_username {
					meta property="profile:username" content=(profile_username);
				}
				meta property="og:description" content=(meta.og_description);
				meta name="description" content=(meta.og_description);
				@for image in meta.og_images {
					meta property="og:image" content=(image.content);
					@if !image.alt.is_empty() {
						meta property="og:image:alt" content=(image.alt);
					}
				}
				@if let Some(video) = meta.og_videos {
					meta property="og:video" content=(video.content);
					@if !video.alt.is_empty() {
						meta property="og:video:alt" content=(video.alt);
					}
				}
				@for (i, (label, value)) in meta.labels.iter().enumerate() {
					meta property=(format!("twitter:label{}", i + 1)) content=(label);
					meta property=(format!("twitter:value{}", i + 1)) content=(value);
				}
				meta name="twitter:card" content=(meta.og_card);
				style { (PreEscaped(include_str!("../static/styles/typography.css"))) }
				style { (PreEscaped(include_str!("../static/styles/main.css"))) }
			}
			body {
				nav class="meta" {
					div class="container" {
						a href="/" class="logo" aria-label=(tr!(lang, "home", site_name = env.site_name())) {
							(PreEscaped(include_str!("icons/atproto.svg")))
							span class="program" aria-hidden="true" { (env.site_name()) }
						}
						p class="tagline" { (tr!(lang, "hook")) }
						a rel="external noopener noreferrer" href="https://docs.bsky.app/showcase?tags=client" class="clients thin" {
							(tr!(lang, "join"))
						}
						a rel="external noopener noreferrer" href="https://docs.bsky.app/showcase?tags=client" class="clients thick" {
							(tr!(lang, "find_client"))
						}
						form action="" method="GET" class="locale" {
							// Uses JS to auto-submit on select:
							select name="lang" onchange="this.form.submit()" {
								@for available_lang in Lang::all() {
									@let name_tag = format!("locales.{available_lang}");
									@let proper_name = tr!(available_lang, &name_tag); // FIXME: This shows the translated value in qqx
									@let local_name = tr!(lang, &name_tag);

									@if proper_name == local_name {
										@if available_lang == lang {
											option value=(available_lang) selected { (proper_name) }
										} @else {
											option value=(available_lang) { (proper_name) }
										}
									} @else {
										// Short version (no localized title, for thin displays)
										@if available_lang == lang {
											option value=(available_lang) class="thin" selected { (proper_name) }
										} @else {
											option value=(available_lang) class="thin" { (proper_name) }
										}

										// Long version (for wide displays)
										@if available_lang == lang {
											option value=(available_lang) class="thick" selected { (proper_name)" – "(local_name) }
										} @else {
											option value=(available_lang) class="thick" { (proper_name)" – "(local_name) }
										}
									}
								}
							}
							noscript class="submit" {
								// Fallback in case JS isn't available to submit the form for us:
								button type="submit" aria-label=(tr!(lang, "locales.prompt_label")) {
									(tr!(lang, "locales.prompt"))
								}
							}
						}
						(info(lang, true)) // for thin displays (<footer> is hidden)
					}
				}
				main {
					(main)
				}
				footer {
					search {
						form action="/search" method="GET" {
							(PreEscaped(include_str!("icons/search.svg")))
							input type="search" name="q" autocorrect="off" autocomplete="off" autocapitalize="none" placeholder=(tr!(lang, "search")) dir="auto" enterkeyhint="search" spellcheck="false" virtualkeyboardpolicy="auto" aria-label=(tr!(lang, "search"));

							input type="submit" aria-hidden="true" style="display:none" value=(tr!(lang, "search")); // so that the Return key submits the form
						}
					}
					(info(lang, false)) // for wide displays
				}
			}
		}
	}
}

fn info(lang: Lang, has_search: bool) -> Markup {
	html! {
		nav class="info" {
			ul {
				@if has_search {
					li { a href="/search" { (tr!(lang, "search")) } }
				}
				li { a rel="external noopener noreferrer" href=(&*REPO_ISSUES_URL) { (tr!(lang, "help")) } }
				li { a rel="external noopener noreferrer" href=(&*REPO_URL) aria-label=(tr!(lang, "version_label", version = VERSION)) { (tr!(lang, "version", version = VERSION)) } }
			}
		}
	}
}

#[derive(Debug)]
pub struct Meta<'a> {
	/// `og:url`
	pub canonical_url: &'a Url,

	/// `<title>`
	pub title: &'a str,

	/// `profile:username`
	pub profile_username: Option<&'a Handle>,

	/// `og:published_time` and `article:published_time`
	pub og_published_time: Option<&'a str>,

	/// `og:title`
	pub og_title: &'a str,

	/// `og:locale`
	pub content_locales: &'a [oxilangtag::LanguageTag<String>],

	/// `og:type`
	pub og_type: OgType,

	/// `og:description`
	pub og_description: Cow<'a, str>,

	/// `og:image` etc.
	pub og_images: &'a [OgMedia<'a>],

	/// `og:video` etc.
	pub og_videos: Option<OgMedia<'a>>,

	/// `twitter:card`
	pub og_card: OgCard,

	/// `<html lang="">` and `og:locale`
	pub lang: Lang,

	/// `twitter:labelN` and `twitter:valueN` where `N` is the array index plus 1.
	pub labels: &'a [(&'a str, &'a str)],
}

#[derive(Debug, Default, Clone, Copy)]
pub enum OgType {
	Article,
	Profile,
	#[default]
	Website,
}

impl Display for OgType {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			Self::Article => write!(f, "article"),
			Self::Profile => write!(f, "profile"),
			Self::Website => write!(f, "website"),
		}
	}
}

#[derive(Debug, Default, Clone, Copy)]
pub enum OgCard {
	#[default]
	Summary,
	SummaryLargeImage,
}

impl Display for OgCard {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			Self::Summary => write!(f, "summary"),
			Self::SummaryLargeImage => write!(f, "summary_large_image"),
		}
	}
}

#[derive(Debug)]
pub struct OgMedia<'a> {
	pub content: &'a Url,
	pub alt: &'a str,
}
