<?php

namespace App\Entity;

/**
 * Messages returned by the entity workflow (in case the operation or certain checks fail)
 */
class Messages
{

	/**
	 * Stores the class name of the current entity
	 * @var String
	 */
	protected $class;

	/**
	 * Stores the list of messages
	 * @var array
	 */
	protected $messages = [];


	/**
	 * Constructs a new object and stores the object and its class name
	 * @param \App\Entity\GeneralAbstract $entityObj
	 */
	public function __construct(\App\Entity\GeneralAbstract $entityObj) // TODO: when all old entities are replaced - add a type hinting here - dummy entry for "\Entity"
	{
		$this->class = stripClassNamespace(get_class($entityObj));
	}

	/**
	 * Adds a new message to the internal list.
	 * @param \App\Message $message
	 * @return Boolean
	 */
	private function addMessage(string $type, string $text, ?string $code=null, ?string $field=null) :void
	{
		// create message object
		$message = new \App\Message($type, $text, $code);
		if ($field) {
			$message->setField($field);
		}
		// store the message
		$this->messages[] = $message;
		// if this is an error - throw all the messages
		if ($message->getType()=='error') {
			$this->throwMessages();
		}
	}


	/**
	 * Adds a new warning message (by code)
	 * @param string $messageCode - the message code - it wil be translated using lang vars
	 * @param string $field - form field name
	 * @param array|string $params - sprintf added vars
	 * @return boolean
	 */
	public function addWarningCode(string $messageCode, ?string $field=null, array|string|null $params=null) :void
	{

		if ($params!==null) {
			$formattedText = $this->translateMessageByCode($messageCode, (array)$params);
		} else {
			$formattedText = $this->translateMessageByCode($messageCode);
		}

		$this->addMessage('warning', $formattedText, $messageCode, $field);
	}

	/**
	 * Adds a new warning message (by text)
	 * @param string $messageText
	 * @param string $field - form field name
	 * @param array|string $params - sprintf added vars
	 */
	public function addWarningText(string $messageText, ?string $field=null, array|string|null $params=null) :void
	{

		if ($params!==null) {
			$formattedText = vsprintf($messageText, (array)$params);
		} else {
			$formattedText = $messageText;
		}

		$this->addMessage('warning', $formattedText, null, $field);
	}

	/**
	 * Adds a new error message (by code)
	 * @param string $messageCode
	 * @param string $field
	 * @param array|string $params - sprintf added vars
	 */
	public function addErrorCode(string $messageCode, ?string $field=null, array|string|null $params=null) :void
	{

		if ($params!==null) {
			$formattedText = $this->translateMessageByCode($messageCode, (array)$params);
		} else {
			$formattedText = $this->translateMessageByCode($messageCode);
		}

		$this->addMessage('error', $formattedText, $messageCode, $field); // in this case it will also throw entity exception and finalize current scope
	}

	/**
	 * Adds a new error message (by text)
	 * @param string $messageText
	 * @param string $field - form field name
	 * @param array|string $params - sprintf added vars
	 */
	public function addErrorText(string $messageText, ?string $field=null, array|string|null $params=null) :void
	{

		if ($params!==null) {
			$formattedText = vsprintf($messageText, (array)$params);
		} else {
			$formattedText = $messageText;
		}

		$this->addMessage('error', $formattedText, null, $field);
	}


	public function addSuccessCode(string $messageCode, ?string $field=null, array|string|null $params=null) :void
	{

		if ($params!==null) {
			$formattedText = $this->translateMessageByCode($messageCode, (array)$params);
		} else {
			$formattedText = $this->translateMessageByCode($messageCode);
		}

		$this->addMessage('success', $formattedText, $messageCode, $field); // in this case it will also throw entity exception and finalize current scope
	}

	/**
	 * Adds a new success message (by text)
	 * @param string $messageText
	 * @param string $field - form field name
	 * @param array|string $params - sprintf added vars
	 */
	public function addSuccessText(string $messageText, ?string $field=null, array|string|null $params=null) :void
	{

		if ($params!==null) {
			$formattedText = vsprintf($messageText, (array)$params);
		} else {
			$formattedText = $messageText;
		}

		$this->addMessage('success', $formattedText, null, $field);
	}

	/**
	 * Removes all the stored success messages.
	 */
	public function removeSuccess() :void
	{
		if ($this->messages) {
			foreach($this->messages as $id=>$msg) {
				if ($msg->getType()=='success') {
					unset($this->messages[$id]);
				}
			}
		}
	}


	/**
	 * Adds the messages from a MessagesCollector object (only warnings and errors)
	 * @param \App\MessagesCollector $msgsCollector
	 */
	public function addMessageCollector(\App\MessagesCollector $msgs) :void
	{
		foreach($msgs->getMessages() as $msg) {
			if ($msg->getType()=='warning' or $msg->getType()=='error') {
				$this->addMessage($msg->getType(), $msg->getText(), $msg->getCode(), $msg->getField());
			}
		}
	}

	/**
	 * Translates a message based on entity languages - it searches specific entity translations and also global entity translations.
	 * @param string $messageCode - the message code
	 * @param array $params - the params to be replaced using sprintf (is optional)
	 * @return string - the formatted text
	 */
	private function translateMessageByCode(string $messageCode, array $params=[]) :string
	{

		$langLoader = \App\Loader_Lang::getInstance();
		$langLoader->loadAppFile("_customLoad/entities/{$this->class}.lang.php");
		$langLoader->loadAppFile("_customLoad/entitiesMt/{$this->class}.lang.php");
		$_LANG = $langLoader->getAll();

		if ($_LANG['globalEntityMessages']["::{$this->class}"][$messageCode]) {
			$orgText = $_LANG['globalEntityMessages']["::{$this->class}"][$messageCode];
		}
		// ... global langs ...
		elseif ($_LANG['globalEntityMessages'][$messageCode]) {
			$orgText = $_LANG['globalEntityMessages'][$messageCode];
		}
		// ... otherwise keep the code
		else {
			// enabling this will pollute the server logs with lots of messages. It's useful for debugging though
			// \Log::getInstance()->error(sprintf("[%s] There was an error translating the message: %s", __METHOD__, $messageCode), [
			// 	'class' => $this->class,
			// 	'messageCode' => $messageCode,
			// 	'params' => 	new \Engine\Logger\Context\Auto($params),
			// ]);
			$orgText = sprintf($_LANG['globalEntityMessages']['untranslatedMessage'], $messageCode);
		}

		// replace %s-s in the text
		$formattedText = $orgText;
		if ($params) {
			$formattedText = vsprintf($orgText, $params);
		}

		return $formattedText;
	}


	/**
	 * If some messages were collected, an App\Entity\Exception is thrown
	 */
	public function throwMessages() :void
	{

		// if some messages set - throw the exception
		if ($this->messages) {

			// cleans up all selectForUpdate cache on all entities (stored in RAM)
			\App\Entity\Cache::getInstance()->flushInternal(); // TODO: why is this here? is it ok?

			// collecta all messages from entity messages so messages collector and throw exception
			$msgs = new \App\MessagesCollector();
			foreach($this->messages as $msg) {
				$msgs->addMessage($msg);
			}
			throw new \App\Entity\Exception($msgs);
		}

	}

	/**
	 * Returns the list of collected messages
	 * @return Array
	 */
	public function getMessages() :array
	{
		return $this->messages;
	}

	/**
	 * Indicates if the current executing operation must be stopped (it has a warning or an error message).
	 * @return Boolean
	 */
	public function hasErrorOrWarning() :bool
	{
		return (bool)array_filter($this->messages, function(\App\Message $msg) {
			return in_array($msg->getType(), ['warning', 'error']);
		});
	}
}
