// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use serde::{Serialize, Deserialize};
use colored::Colorize;
use const_format::formatcp;

use crate::utils::escape;

const NAME: &str = "Spaceships";

/// One requirement of the GNU AGPLv3 (section 13) is for modified copies to
/// provide users with an "opportunity" to receive the source code of the
/// modified server.
///
/// The purpose of this struct is to provide more than enough information
/// necessary to obtain the source code, as well as general information about
/// the game server.
#[allow(clippy::unsafe_derive_deserialize)] // The only unsafe code is inside the `formatcp` macro, so this isn't an issue
#[derive(Serialize, Deserialize)]
pub struct Info {
	/// The name of the program the game server is running.
	name: Box<str>,

	/// A general description about the program.
	desc: Box<str>,

	/// The program's version.
	version: Box<str>,

	/// The authors of the program,
	authors: Box<str>,

	/// A URL to the "main website", whatever that is, of this project.
	main_url: Option<Box<str>>,

	/// A URL to somewhere that provides the source code of this project.
	///
	/// This can be a URL to the Git repository, or an archive of that specific
	/// version's source code.
	src_url: Box<str>,

	/// A human-readable representation of the commit hash.
	commit_hash: Option<Box<str>>,

	/// A tag of the current commit of this build.
	commit_tag: Option<Box<str>>,

	/// Any additional notes about this specific build.
	build_notes: Option<Box<str>>,
}

pub enum InfoType {
	Text,
	Url,
}

mod built_info {
	include!(concat!(env!("OUT_DIR"), "/built.rs"));
}

impl Info {
	pub fn new() -> Info {
		#[allow(clippy::const_is_empty)] // Evaluation isn't constant, instead depending on feature flags
		const BUILD_INFO: &str = if built_info::FEATURES.is_empty() {
			formatcp!("Compiled with all Cargo features disabled (`cargo build --no-default-features`).")
		} else {
			formatcp!("Cargo features compiled with: [{}].", built_info::FEATURES_LOWERCASE_STR)
		};

		Info {
			name: Box::from(NAME),
			desc: Box::from(built_info::PKG_DESCRIPTION),
			version: Box::from(formatcp!("v{}", built_info::PKG_VERSION)),
			authors: Box::from(built_info::PKG_AUTHORS),
			main_url: Info::none_if_empty(built_info::PKG_HOMEPAGE).map(Box::from),
			src_url: Box::from(built_info::PKG_REPOSITORY),
			commit_hash: built_info::GIT_COMMIT_HASH.map(Box::from),
			commit_tag: built_info::GIT_VERSION.map(Box::from),
			build_notes: Some(Box::from(BUILD_INFO)),
		}
	}

	fn none_if_empty(s: &str) -> Option<&str> {
		(!s.is_empty()).then_some(s)
	}

	pub fn for_each(&self, mut f: impl FnMut(&str, &str, InfoType)) {
		f("Name:", self.name.as_ref(), InfoType::Text);
		f("Description:", self.desc.as_ref(), InfoType::Text);
		f("Version:", self.version.as_ref(), InfoType::Text);
		f("Authors:", self.authors.as_ref(), InfoType::Text);
		if let Some(main_url) = &self.main_url { f("Main URL:", main_url, InfoType::Url); }
		f("Source URL:", self.src_url.as_ref(), InfoType::Url);
		if let Some(commit_hash) = &self.commit_hash { f("Commit Hash:", commit_hash, InfoType::Text); }
		if let Some(commit_tag) = &self.commit_tag { f("Commit Tag:", commit_tag, InfoType::Text); }
		if let Some(build_notes) = &self.build_notes { f("Build Notes:", build_notes, InfoType::Text); }
	}

	pub fn print(&self) {
		self.for_each(Info::print_line);
	}

	fn print_line(key: &str, value: &str, _info_type: InfoType) {
		println!("{} {}", key.yellow(), escape::multiline(value));
	}
}
