<?php

/**
 * Класс управления событиями
 * @doc https://docs.instantcms.ru/dev/controllers/hooks
 */
class cmsEventsManager {

    /**
     * Список всех слушателей и событий
     * @var array
     */
    private static $structure = null;

    /**
     * Оповещает слушателей о произошедшем событии
     * Входящие данные $data передаются каждому слушателю по очереди,
     * на выходе возвращается измененный слушателями параметр $data
     *
     * @param array|string $event_name Название события/массив событий
     * @param mixed $data Параметр события
     * @param ?mixed $default_return Значение, возвращаемое по умолчанию если у события нет слушателей
     * @param ?cmsRequest $_request Объект запроса
     * @return mixed Обработанные данные
     */
    public static function hook($event_name, $data = false, $default_return = null, $_request = null) {

        // Используйте массив событий, если они с разным названиями,
        // но с одинаковыми параметрами
        if (is_array($event_name)) {

            foreach ($event_name as $_event_name) {
                $data = self::hook($_event_name, $data, $default_return, $_request);
            }

            return $data;
        }

        //получаем все активные контроллеры, привязанные к указанному событию
        $listeners = self::getEventListeners($event_name);

        //если активных контроллеров нет, возвращаем данные без изменений
        if (!$listeners) {

            cmsDebugging::pointProcess('events_empty', [
                'data' => 'hook => ' . $event_name,
                'context' => [
                    'target' => null,
                    'subject' => $event_name
                ]
            ], 1);

            return is_null($default_return) ? $data : $default_return;
        }

        $request = $_request ?? new cmsRequest([], cmsRequest::CTX_INTERNAL);

        //перебираем контроллеры и вызываем каждый из них, передавая $data
        foreach ($listeners as $listener) {

            $result = self::runHook($listener, $event_name, $data, $request);

            if ($result !== null) {
                $data = $result;
            }
        }

        return $data;
    }

    /**
     * Оповещает слушателей о произошедшем событии
     * Входящие данные $data передаются каждому слушателю в изначальном виде,
     * на выходе возвращается массив с ответами от каждого слушателя
     *
     * @param string $event_name Название события
     * @param mixed $data Параметр события
     * @param mixed $default_return Значение, возвращаемое по умолчанию если у события нет слушателей
     * @param ?cmsRequest $_request Объект запроса
     * @return array Обработанный массив данных
     */
    public static function hookAll($event_name, $data = false, $default_return = null, $_request = null) {

        //получаем все активные контроллеры, привязанные к указанному событию
        $listeners = self::getEventListeners($event_name);

        //если активных контроллеров нет, возвращаем данные без изменений
        if (!$listeners) {

            cmsDebugging::pointProcess('events_empty', [
                'data' => 'hookAll => ' . $event_name,
                'context' => [
                    'target' => null,
                    'subject' => $event_name
                ]
            ], 1);

            return is_null($default_return) ? false : $default_return;
        }

        $results = [];

        $request = $_request ?? new cmsRequest([], cmsRequest::CTX_INTERNAL);

        //перебираем контроллеры и вызываем каждый из них, передавая $data
        foreach ($listeners as $listener) {

            $result = self::runHook($listener, $event_name, $data, $request, 'hookAll');

            if ($result !== false && $result !== null) {
                $results[$listener] = $result;
            }
        }

        return $results;
    }

    /**
     * Запускает слушателя
     *
     * @param string $listener Имя контроллера
     * @param string $event_name Название события
     * @param mixed $data Параметр события
     * @param ?cmsRequest $request Объект запроса
     * @param string $debug_type Отладочный тип
     * @return mixed
     */
    public static function runHook($listener, $event_name, $data = false, $request = null, $debug_type = 'hook') {

        if (!cmsController::enabled($listener)) {
            return null;
        }

        $controller = cmsCore::getController($listener, $request, false);

        if (!$controller || ($controller->mb_installed && !$controller->isControllerInstalled($listener))) {
            return null;
        }

        cmsDebugging::pointStart('events');

        $data = $controller->runHook($event_name, [$data]);

        cmsDebugging::pointProcess('events', [
            'data' => $debug_type . ' :: ' . $listener . ' => ' . $event_name,
            'context' => [
                'target' => $listener,
                'subject' => $event_name
            ]
        ], 2);

        return $data;
    }

    /**
     * Возвращает список всех слушателей указанного события
     * @param string $event_name Название события
     * @return array Список слушателей
     */
    public static function getEventListeners(string $event_name) {

        if (self::$structure === null) {
            self::$structure = self::getAllListeners();
        }

        return self::$structure[$event_name] ?? [];
    }

    /**
     * Возвращает список всех слушателей для всех событий
     * @return array
     */
    public static function getAllListeners() {

        $cache     = cmsCache::getInstance();
        $cache_key = 'events';

        if (false !== ($structure = $cache->get($cache_key))) {
            return $structure;
        }

        $events = cmsCore::getControllersEvents();
        if (!$events) {
            return [];
        }

        $structure = [];

        foreach ($events as $controller_name => $hooks) {
            foreach ($hooks as $ordering => $event_name) {
                $structure[$event_name][$ordering] = $controller_name;
            }
        }

        foreach ($structure as $event_name => $controllers) {
            ksort($structure[$event_name]);
        }

        $cache->set($cache_key, $structure, 86400);

        return $structure;
    }

}
