#![allow(dead_code)]

use std::{
	collections::HashMap,
	fs,
	path::PathBuf,
	sync::{LazyLock, OnceLock},
};

use askama::Template;
use blake3::{Hash, Hasher};
use tracing_subscriber::EnvFilter;

mod common;

#[cfg(feature = "bbs")]
mod bbs;

#[cfg(feature = "ddd")]
mod ddd;

#[cfg(feature = "kh1")]
mod kh1;

#[cfg(feature = "kh1")]
mod kh1fm;

#[cfg(feature = "kh2")]
mod kh2fm;

#[cfg(feature = "kh3")]
mod kh3;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ASSETS_FOLDER_PATH: &str = "./public/assets";
pub const SCRIPTS_FOLDER_PATH: &str = "./public/scripts";
pub const STYLES_FOLDER_PATH: &str = "./public/styles";
static BASE_URL: OnceLock<String> = OnceLock::new();
static FILE_HASHES: LazyLock<HashMap<String, Hash>> = LazyLock::new(|| {
	let mut map = HashMap::new();

	fn parse_path(path: PathBuf, map: &mut HashMap<String, Hash>) {
		if let Ok(paths) = fs::read_dir(path) {
			for path in paths.flatten() {
				let path = path.path();

				if path.metadata().is_ok_and(|p| p.is_dir()) {
					parse_path(path, map);
					continue;
				}

				let dir = path.parent().unwrap().to_str().unwrap();
				let path = path.to_str().unwrap();
				let is_module = path.contains("/common");

				let mut hasher = Hasher::new();
				hash_file(path.to_string().into(), &mut hasher);
				if !is_module {
					hash_files_in_dir(format!("{dir}/common").into(), &mut hasher);
				}

				let hash = hasher.finalize();
				map.insert(path.to_string(), hash);
			}
		}
	}

	parse_path(SCRIPTS_FOLDER_PATH.into(), &mut map);
	parse_path(STYLES_FOLDER_PATH.into(), &mut map);

	map
});

pub trait RuntimeModule {
	fn start_module();
}

#[derive(Template)]
#[template(path = "pages/index.html")]
struct IndexTemplate {}

fn main() {
	// Initialize tracing
	tracing_subscriber::fmt()
		.with_env_filter(EnvFilter::from_default_env())
		.event_format(tracing_subscriber::fmt::format().with_source_location(true))
		.init();

	tracing::info!("Generating the main menu template");
	let template = IndexTemplate {};

	std::fs::write("./out/index.html", template.render().unwrap()).unwrap();
	std::process::Command::new("cp")
		.args(["-r", "./assets", "./out"])
		.output()
		.unwrap();

	#[cfg(feature = "bbs")]
	start_module::<bbs::Module>();

	#[cfg(feature = "ddd")]
	start_module::<ddd::Module>();

	#[cfg(feature = "kh1")]
	{
		start_module::<kh1::Module>();
		start_module::<kh1fm::Module>();
	}

	#[cfg(feature = "kh2")]
	start_module::<kh2fm::Module>();

	#[cfg(feature = "kh3")]
	start_module::<kh3::Module>();
}

fn start_module<M: RuntimeModule>() {
	M::start_module();
}

fn base_url() -> String {
	BASE_URL
		.get_or_init(|| std::env::var("BASE_URL").unwrap_or("".to_string()))
		.to_string()
}

fn find_hash(file: &str) -> String {
	let map = &*FILE_HASHES;
	let base_url = base_url();

	let hash = map.get(&format!(".{file}"));
	if let Some(hash) = hash {
		return format!("{base_url}{file}?hash={hash}");
	}

	format!("{base_url}{file}")
}

fn hash_files_in_dir(path: PathBuf, hasher: &mut Hasher) {
	if let Ok(paths) = fs::read_dir(path) {
		for path in paths.flatten() {
			let path = path.path();
			if path.metadata().is_ok_and(|p| p.is_dir()) {
				hash_files_in_dir(path, hasher);
				continue;
			}

			hash_file(path, hasher);
		}
	}
}

fn hash_file(path: PathBuf, hasher: &mut Hasher) {
	if let Ok(file) = fs::read(path) {
		hasher.update(&file);
	}
}

fn create_file(dir: &str, file: &str, buf: impl Template) -> std::io::Result<()> {
	std::fs::create_dir_all(dir)?;
	std::fs::write(format!("{}/{}.html", dir, file), buf.render().unwrap())?;
	Ok(())
}
