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

use const_format::formatcp;

pub trait Command: Sized {
	fn build<V: CommandVariant<Command = Self>>(s: &str) -> Result<Option<Self>, String> {
		let tokens = super::split(s).map_err(|err| format!("failed parsing line: {err}"))?;
		let (cmd, args) = match tokens.split_at_checked(1) {
			Some((first, last)) => (&first[0], last),
			None => return Ok(None),
		};

		let Some(cmd_var) = V::try_from(cmd) else {
			return Err(format!("unknown command of \"{cmd}\", run `help` for more info"));
		};

		match cmd_var.build(Args(args)) {
			Ok(command) => Ok(Some(command)),
			Err(err) => Err(format!("{cmd}: {err}")),
		}
	}
}

pub trait CommandVariant: Sized {
	type Command;

	fn try_from(cmd: &str) -> Option<Self>;
	fn help(self) -> &'static str;
	fn build(self, args: Args) -> Result<Self::Command, String>;
}

pub struct Args<'a>(&'a [String]);

impl Args<'_> {
	pub fn require_none<T>(&self, val: T) -> Result<T, String> {
		if self.is_empty() {
			Ok(val)
		} else {
			Err(String::from("expected no arguments"))
		}
	}

	pub fn require_n_args<const N: usize>(&self) -> Result<&[String; N], String> {
		self.0.try_into().map_err(|_| format!("expected {N} argument(s), got {}", self.len()))
	}

	pub fn require_at_least_n_args<const N: usize>(&self) -> Result<&[String; N], String> {
		self.0.get(..N).and_then(|args| args.try_into().ok()).ok_or_else(|| format!("expected at least {N} argument(s), got {}", self.len()))
	}

	pub fn check_at_most_n_args(&self, count: usize) -> Result<(), String> {
		if self.len() <= count {
			Ok(())
		} else {
			Err(format!("expected at most {count} argument(s), got {}", self.len()))
		}
	}

	pub fn help_command<V: CommandVariant>(&self) -> Result<Option<V>, String> {
		self.check_at_most_n_args(1)?;

		if let Some(cmd) = self.first() {
			if let Some(variant) = V::try_from(cmd) { Ok(Some(variant)) }
			else { Err(format!("unknown command of \"{cmd}\"")) }
		} else { Ok(None) }
	}
}

impl Deref for Args<'_> {
	type Target = [String];

	fn deref(&self) -> &[String] {
		self.0
	}
}

use super::INDENTATION as I;

pub const HELP_FOR_HELP: &str = formatcp!("help - prints help text\n{I}help: Prints the help text of all comamnds.\n{I}help <COMMAND>: Prints the help text of a particular command.");
