// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::sync::Arc;

use strum_macros::EnumIter;
use const_format::formatcp;

use super::super::common::events::underpower::{Underpower, UnderpowerEffect, UnderpowerCommand};

use crate::world::{colour::Colour, player::PlayerName};
use crate::utils::command::{Args, Command as GenericCommand, CommandVariant as GenericCommandVariant, HELP_FOR_HELP};

pub enum Command {
	Help(Option<CommandVariant>),
	Spectate,
	Play,
	Ready,
	Underpower(UnderpowerCommand),
	Name(PlayerName),
	Colour(Colour),
	Team(Box<str>),
	Teams,
}

impl GenericCommand for Command {}

#[derive(Clone, Copy, EnumIter)]
pub enum CommandVariant { Help, Spectate, Play, Ready, Underpower, Name, Colour, Team, Teams }

impl GenericCommandVariant for CommandVariant {
	type Command = Command;

	fn try_from(cmd: &str) -> Option<CommandVariant> {
		match cmd {
			"help" => Some(CommandVariant::Help),
			"spectate" => Some(CommandVariant::Spectate),
			"play" => Some(CommandVariant::Play),
			"ready" | "r" => Some(CommandVariant::Ready),
			"underpower" | "up" => Some(CommandVariant::Underpower),
			"name" => Some(CommandVariant::Name),
			"colour" => Some(CommandVariant::Colour),
			"team" => Some(CommandVariant::Team),
			"teams" => Some(CommandVariant::Teams),
			_ => None,
		}
	}

	fn help(self) -> &'static str {
		use crate::utils::INDENTATION as I;

		match self {
			CommandVariant::Help => HELP_FOR_HELP,
			CommandVariant::Spectate => "spectate - enters spectator mode",
			CommandVariant::Ready => "ready (or `r`) - votes for the game to start early",
			CommandVariant::Play => "play - leaves spectator mode",
			CommandVariant::Underpower => formatcp!("underpower (or `up`) - weakens the player\n{I}underpower: Prints the power of all effects.\n{I}underpower <EFFECT>: Prints the power of a particular effect.\n{I}underpower <EFFECT> <POWER>: Sets the power of an effect (can be a number or a percentage).\n{I}underpower clear: Clears all effects.\n{I}underpower clear <EFFECT>: Clears a particular effect."),
			CommandVariant::Name => formatcp!("name <NAME> - changes your name"),
			CommandVariant::Colour => formatcp!("colour - changes your colour\n{I}colour <NAME>: Takes the name of a colour like \"red\" or \"blue\".\n{I}colour #<HEX>: Takes the colour as six hexadecimal digits."),
			CommandVariant::Team => formatcp!("team <TEAM> - changes your team"),
			CommandVariant::Teams => formatcp!("teams - lists all teams"),
		}
	}

	fn build(self, args: Args) -> Result<Command, String> {
		Ok(match self {
			CommandVariant::Help => Command::Help(args.help_command::<CommandVariant>()?),
			CommandVariant::Spectate => args.require_none(Command::Spectate)?,
			CommandVariant::Ready => args.require_none(Command::Ready)?,
			CommandVariant::Play => args.require_none(Command::Play)?,
			CommandVariant::Underpower => {
				args.check_at_most_n_args(2)?;

				Command::Underpower(if let Some(arg) = args.first() {
					if let Some(effect) = UnderpowerEffect::try_from(arg.as_str()) {
						if let Some(mut power_str) = args.get(1).map(String::as_str) {
							let percentage = power_str.ends_with('%');
							if percentage {
								power_str = power_str.get(..power_str.len() - 1).ok_or_else(|| String::from("this shouldn't happen"))?;
							}

							let mut power: f32 = power_str.parse().map_err(|_| format!("invalid number of \"{power_str}\""))?;
							if percentage {
								power /= 100.0;
							}

							let power = Underpower::try_from(power).map_err(|()| String::from("power must be in [0, 1]"))?;
							UnderpowerCommand::Change(effect, power)
						} else {
							UnderpowerCommand::Status(Some(effect))
						}
					} else if arg == "clear" {
						if let Some(effect_str) = args.get(1) {
							let effect = UnderpowerEffect::try_from(effect_str).ok_or_else(|| format!("invalid effect of \"{effect_str}\""))?;
							UnderpowerCommand::Clear(Some(effect))
						} else {
							UnderpowerCommand::Clear(None)
						}
					} else {
						return Err(format!("invalid argument of \"{arg}\""));
					}
				} else {
					UnderpowerCommand::Status(None)
				})
			},
			CommandVariant::Name => {
				let name = Arc::from(args.require_n_args::<1>()?[0].as_str());
				Command::Name(PlayerName::try_from(name).map_err(|err| format!("invalid name: {err}"))?)
			},
			CommandVariant::Colour => Command::Colour(Colour::try_from(args.require_n_args::<1>()?[0].as_str()).map_err(ToOwned::to_owned)?),
			CommandVariant::Team => Command::Team(args.require_n_args::<1>()?[0].clone().into_boxed_str()),
			CommandVariant::Teams => args.require_none(Command::Teams)?,
		})
	}
}
