<?php
/**
 * Отключаем ненужные проверки.
 *
 * @noinspection PhpUnusedParameterInspection, PhpUnused
 */

namespace goodboyalex\php_components_pack\classes;

use Closure;
use goodboyalex\php_components_pack\enums\MessageType;
use goodboyalex\php_components_pack\interfaces\ISerializable;
use goodboyalex\php_components_pack\models\ActionStateMessageModel;

/**
 * Состояние действия.
 *
 * @author Александр Бабаев
 * @package php_components_pack
 * @version 1.0
 * @since 1.0
 */
final class ActionState implements ISerializable
{
    /**
     * @var mixed|null $Value Значение
     */
    public mixed $Value;

    /**
     * @var array $Messages Список информации
     */
    private array $Messages = [];

    /**
     * Конструктор
     *
     * @param mixed|null $defValue Значение по умолчанию
     */
    public function __construct (mixed $defValue = null)
    {
        $this->Value = $defValue;
    }

    /**
     * При выводе GetStringMessages выводит только ошибки.
     *
     * @return Closure Возвращает функцию, проверяющую сообщение на соответствие типу.
     */
    public static function GET_STRING_ERROR_ONLY (): Closure
    {
        return fn (ActionStateMessageModel $message)
            => $message->MessageType === MessageType::Error;
    }

    /**
     * При выводе GetStringMessages выводит ошибки и предупреждения.
     *
     * @return Closure Возвращает функцию, проверяющую сообщение на соответствие типу.
     */
    public static function GET_STRING_ERROR_AND_WARNING (): Closure
    {
        return fn (ActionStateMessageModel $message)
            => $message->MessageType === MessageType::Error
            || $message->MessageType === MessageType::Warning;
    }

    /**
     * При выводе GetStringMessages выводит все сообщения.
     *
     * @return Closure Возвращает функцию, проверяющую сообщение на соответствие типу.
     */
    public static function GET_STRING_ALL (): Closure
    {
        return fn (ActionStateMessageModel $message) => true;
    }

    /**
     * Добавляет данные из другого состояния.
     *
     * @param ActionState $state Другое состояние
     * @param bool $clearAllBefore Очищать сообщения перед добавлением (true) или просто добавить к текущим (false)
     *
     * @return void
     */
    public function AddState (ActionState $state, bool $clearAllBefore = false): void
    {
        // Если нужно очистить список сообщений
        if ($clearAllBefore)
            // - то очищаю список сообщений
            $this->Clear(fn (ActionStateMessageModel $message) => true);

        // Добавляю сообщения из другого состояния
        $this->AddRange($state->GetMessages(function (ActionStateMessageModel $message)
        {
            return true;
        }));

        // Добавляю значение
        $this->Value = $state->Value;
    }

    /**
     * Очищает список сообщений, согласно условию.
     *
     * @param callable $predicate Условие выборки
     *
     * @return void
     */
    public function Clear (callable $predicate): void
    {
        // Выбираю все сообщения, удовлетворяющие условию
        $list = $this->GetMessages($predicate);

        // Удаляю их из списка
        $this->Messages = array_diff($this->Messages, $list);
    }

    /**
     * Выбирает сообщения по условию predicate.
     *
     * @param callable $predicate Условие выборки
     *
     * @return array Список отобранных сообщений
     */
    public function GetMessages (callable $predicate): array
    {
        // Получаю список элементов
        $list = [];

        // Для каждого элемента
        foreach ($this->Messages as $actionStateMessage)
            // - если он подходит под условие
            if ($predicate($actionStateMessage))
                // - добавляю его в список
                $list[] = $actionStateMessage;

        // Возвращаю список
        return $list;
    }

    /**
     * Добавляет список
     *
     * @param array $messages Список сообщений
     *
     * @return void
     */
    public function AddRange (array $messages): void
    {
        $this->Messages = array_merge($this->Messages, $messages);
    }

    /**
     * Возвращает список сообщений (параметр Message у каждого сообщения).
     *
     * @param callable $predicate Условие выборки
     * @param string $separator Разделитель
     *
     * @return string Список сообщений
     */
    public function GetStringMessages (callable $predicate, string $separator = '\n'): string
    {
        // Делаю выборку
        $list = $this->GetMessages($predicate);

        // Формирую список сообщений
        $result = [];

        // Для каждого сообщения из выборки
        foreach ($list as $message) {
            // - если оно не нужного нам класса
            if (!$message instanceof ActionStateMessageModel)
                // -- то пропускаю
                continue;

            // - добавляю сообщение
            $result[] = $message->Message;
        }

        // Возвращаю список
        return implode($separator, $result);
    }

    /**
     * Добавляет сообщение о критической ошибке.
     *
     * @param string $message Сообщение
     *
     * @return void
     */
    public function AddCritical (string $message): void
    {
        $this->Add(new ActionStateMessageModel(MessageType::Error, true, $message));
    }

    /**
     * Добавление сообщения.
     *
     * @param ActionStateMessageModel $message Сообщение
     *
     * @return void
     */
    public function Add (ActionStateMessageModel $message): void
    {
        $this->Messages[] = $message;
    }

    /**
     * Добавляет сообщение об ошибке.
     *
     * @param string $message Сообщение
     *
     * @return void
     */
    public function AddError (string $message): void
    {
        $this->Add(new ActionStateMessageModel(MessageType::Error, false, $message));
    }

    /**
     * Добавляет предупреждение.
     *
     * @param string $message Сообщение.
     *
     * @return void
     */
    public function AddWarning (string $message): void
    {
        $this->Add(new ActionStateMessageModel(MessageType::Warning, false, $message));
    }

    /**
     * Добавляет информационное сообщение.
     *
     * @param string $message Сообщение.
     *
     * @return void
     */
    public function AddInfo (string $message): void
    {
        $this->Add(new ActionStateMessageModel(MessageType::Info, false, $message));
    }

    /**
     * Проверяет, есть ли информационные сообщения.
     *
     * @return bool Наличие сообщений
     */
    public function HasInfos (): bool
    {
        return $this->Count(fn (ActionStateMessageModel $message) => $message->MessageType == MessageType::Info) > 0;
    }

    /**
     * Количество сообщений, удовлетворяющих условию.
     *
     * @param callable $predicate Условие выборки
     *
     * @return int Число сообщений
     */
    public function Count (callable $predicate): int
    {
        // Получаю список сообщений
        $list = $this->GetMessages($predicate);

        // Возвращаю результат
        return count($list);
    }

    /**
     * Проверяет, успешно ли завершилась операция.
     *
     * @param bool $onlyCritical Игнорировать все некритические ошибки и предупреждения (не рекомендуется!)
     *
     * @return bool Успешно ли завершилась операция.
     */
    public function IsSuccess (bool $onlyCritical = false): bool
    {
        // Если нужно учитывать только критические ошибки
        if ($onlyCritical)
            // - то проверяю наличие критических ошибок
            return !$this->HasErrors($onlyCritical);

        // Возвращаю результат
        return !$this->HasErrors() && !$this->HasWarnings();
    }

    /**
     * Проверяет, есть ли ошибки.
     *
     * @param bool $onlyCritical Учитывать только критические ошибки.
     *
     * @return bool Наличие ошибок.
     */
    public function HasErrors (bool $onlyCritical = false): bool
    {
        return $this->Count(function (ActionStateMessageModel $message) use ($onlyCritical): bool
        {
            // - сравниваю тип сообщения
            if ($message->MessageType != MessageType::Error)
                // -- если не совпадает, то возвращаю false
                return false;

            // - если нужно выводить только критические ошибки, а сообщение не критическое
            if ($onlyCritical && !$message->IsCritical)
                // -- то возвращаю false
                return false;

            // Возвращаю true
            return true;
        });
    }

    /**
     * Проверяет, есть ли предупреждения.
     *
     * @return bool Наличие предупреждений
     */
    public function HasWarnings (): bool
    {
        return $this->Count(fn (ActionStateMessageModel $message) => $message->MessageType == MessageType::Warning) > 0;
    }

    /**
     * @inheritDoc
     */
    public function Serialize (): string
    {
        // Создаю список сообщений
        $list = [];

        // Для каждого сообщения
        foreach ($this->Messages as $message)
            // - сериализую его и добавляю в список
            $list[] = $message->Serialize();

        // Возвращаю результат
        return serialize($list);
    }

    /**
     * @inheritDoc
     */
    public function UnSerialize (string $serialized): void
    {
        // Очищаю список сообщений
        $this->Clear(fn (ActionStateMessageModel $message) => true);

        // Десериализую список сообщений
        $list = unserialize($serialized);

        // Для каждого сообщения
        foreach ($list as $messageSerialized) {
            // - создаю новое сообщение
            $message = new ActionStateMessageModel();
            // - десериализую его
            $message->UnSerialize($messageSerialized);
            // - добавляю в список
            $this->Add($message);
        }
    }
}