<?php

/**
 * Фасад для роботи з подіями
 *
 * @package Flowaxy\Support\Facades
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Support\Facades;

use Flowaxy\Core\Events\DomainEventAdapter;
use Flowaxy\Core\Events\EventDispatcher;
use Flowaxy\Core\Events\Event as EventInstance;
use Flowaxy\Core\Events\EventSubscriber;
use Flowaxy\Core\Events\EventListener;
use Flowaxy\Domain\Events\DomainEventInterface;

use function is_string;
use function class_exists;

final class Event extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return EventDispatcher::class;
    }

    /**
     * Отримати екземпляр EventDispatcher
     *
     * @return EventDispatcher
     */
    private static function dispatcher(): EventDispatcher
    {
        return static::getFacadeRoot();
    }

    /**
     * Створити об'єкт події з рядка або використати існуючий
     *
     * @param string|object $event
     * @param array<string, mixed> $payload
     * @return EventInstance
     */
    private static function createEvent(string|object $event, array $payload = []): EventInstance
    {
        // Если это DomainEventInterface, преобразуем через адаптер
        if ($event instanceof DomainEventInterface) {
            return DomainEventAdapter::adapt($event);
        }

        if (is_string($event)) {
            // Якщо передано рядок, намагаємося створити клас події
            if (class_exists($event) && is_subclass_of($event, EventInstance::class)) {
                return new $event($payload);
            }
            // Якщо клас не знайдено, створюємо базовий об'єкт події
            return new class($payload) extends EventInstance {
            };
        }

        // Якщо передано об'єкт, перевіряємо, що це Event
        if ($event instanceof EventInstance) {
            return $event;
        }

        // Якщо це не Event, обгортаємо в базовий Event
        return new class($payload) extends EventInstance {
        };
    }

    /**
     * Відправити подію
     *
     * @param string|object $event
     * @param array<string, mixed> $payload
     * @return EventInstance
     */
    public static function dispatch(string|object $event, array $payload = []): EventInstance
    {
        $eventInstance = static::createEvent($event, $payload);
        return static::dispatcher()->dispatch($eventInstance);
    }

    /**
     * Прослухати подію
     *
     * @param string $eventName Назва класу події
     * @param callable $listener
     * @param int $priority
     * @return void
     */
    public static function listen(string $eventName, callable $listener, int $priority = 10): void
    {
        static::dispatcher()->addListener($eventName, $listener, $priority);
    }

    /**
     * Підписатися на події
     *
     * @param EventSubscriber $subscriber
     * @return void
     */
    public static function subscribe(EventSubscriber $subscriber): void
    {
        static::dispatcher()->addSubscriber($subscriber);
    }

    /**
     * Забути прослухувач
     *
     * @param string $eventName
     * @param EventListener|null $listener
     * @return void
     */
    public static function forget(string $eventName, ?EventListener $listener = null): void
    {
        static::dispatcher()->removeListener($eventName, $listener);
    }

    /**
     * Перевірити, чи є прослухувачі
     *
     * @param string $eventName
     * @return bool
     */
    public static function hasListeners(string $eventName): bool
    {
        return static::dispatcher()->hasListeners($eventName);
    }

    /**
     * Відправити подію (alias для dispatch)
     *
     * @param string|object $event
     * @param array<string, mixed> $payload
     * @return EventInstance
     */
    public static function fire(string|object $event, array $payload = []): EventInstance
    {
        return static::dispatch($event, $payload);
    }

    /**
     * Відправити подію до першого обробника
     *
     * @param string|object $event
     * @param array<string, mixed> $payload
     * @return EventInstance
     */
    public static function until(string|object $event, array $payload = []): EventInstance
    {
        $eventInstance = static::createEvent($event, $payload);
        $result = static::dispatcher()->dispatch($eventInstance);

        // Якщо поширення зупинено, повертаємо результат
        if ($result->isPropagationStopped()) {
            return $result;
        }

        return $result;
    }
}
