<?php
declare(strict_types=1);

namespace App\Framework\Core\Events;

use App\Framework\DI\DefaultContainer;

/**
 * Der EventDispatcher ist verantwortlich für die Verarbeitung von Events
 * und das Aufrufen der entsprechenden Event-Handler
 */
final class EventDispatcher
{
    /** @var array<string, array> Mapping von Event-Typen zu Handlern */
    private array $handlers = [];

    /** @var bool Gibt an, ob der EventDispatcher initialisiert wurde */
    private bool $initialized = false;

    /**
     * @param DefaultContainer $container Der DI-Container
     * @param array|null $eventHandlers Array mit Event-Handlern aus der Autodiscovery
     */
    public function __construct(
        private readonly DefaultContainer $container,
        private readonly ?array           $eventHandlers = null
    ) {}

    /**
     * Initialisiert den EventDispatcher mit den Event-Handlern
     */
    private function initialize(): void
    {
        if ($this->initialized) {
            return;
        }

        $this->initialized = true;

        if ($this->eventHandlers === null) {
            return;
        }

        foreach ($this->eventHandlers as $handler) {
            $eventClass = $handler['event_class'];
            $this->handlers[$eventClass][] = $handler;
        }
    }

    /**
     * Dispatcht ein Event an alle registrierten Handler
     *
     * @param object $event Das zu dispatchende Event-Objekt
     * @return array<mixed> Ergebnisse der Event-Handler
     */
    public function dispatch(object $event): array
    {
        $this->initialize();

        $eventClass = get_class($event);
        $results = [];

        // Exakte Übereinstimmung prüfen
        if (isset($this->handlers[$eventClass])) {
            foreach ($this->handlers[$eventClass] as $handler) {
                $result = $this->invokeHandler($handler, $event);
                $results[] = $result;

                // Wenn ein Handler die Propagation stoppt, abbrechen
                if (isset($handler['attribute_data']) && $handler['attribute_data']['stopPropagation'] ?? false) {
                    return $results;
                }
            }
        }

        // Prüfen auf Handler für Basisklassen/Interfaces
        foreach ($this->handlers as $handledEventClass => $handlersList) {
            if ($handledEventClass === $eventClass) {
                continue; // Bereits verarbeitet
            }

            if (is_subclass_of($event, $handledEventClass) ||
                ($event instanceof $handledEventClass)) {
                foreach ($handlersList as $handler) {
                    $result = $this->invokeHandler($handler, $event);
                    $results[] = $result;

                    // Wenn ein Handler die Propagation stoppt, abbrechen
                    if (isset($handler['attribute_data']) && $handler['attribute_data']['stopPropagation'] ?? false) {
                        return $results;
                    }
                }
            }
        }

        return $results;
    }

    public function __invoke(object $event): array
    {
        return $this->dispatch($event);
    }

    /**
     * Ruft einen Event-Handler auf
     *
     * @param array $handler Handler-Definition
     * @param object $event Event-Objekt
     * @return mixed Ergebnis des Handler-Aufrufs
     */
    private function invokeHandler(array $handler, object $event): mixed
    {
        if (isset($handler['callable'])) {
            // Callable direkt aufrufen
            return ($handler['callable'])($event);
        }

        $handlerClass = $handler['class'];
        $methodName = $handler['method'];

        // Handler-Instanz aus dem Container holen oder erstellen
        $handlerInstance = $this->container->get($handlerClass);

        // Handler-Methode aufrufen
        return $handlerInstance->$methodName($event);
    }

    /**
     * Registriert einen Event-Handler manuell
     *
     * @param string $eventClass Vollqualifizierter Klassenname des Events
     * @param callable $handler Der Handler, der aufgerufen werden soll
     * @param int|null $priority Priorität des Handlers (höhere Werte werden zuerst ausgeführt)
     * @param bool $stopPropagation Gibt an, ob die Event-Propagation nach diesem Handler gestoppt werden soll
     * @return self
     */
    public function addHandler(
        string $eventClass,
        callable $handler,
        ?int $priority = null,
        bool $stopPropagation = false
    ): self {
        $this->initialize();

        // OnEvent-Attribut für die Priorität und Propagation erstellen
        $attribute = new OnEvent($priority, $stopPropagation);

        $this->handlers[$eventClass][] = [
            'callable' => $handler,
            'attribute_data' => [
                'priority' => $priority ?? 0,
                'stopPropagation' => $stopPropagation
            ]

        ];

        // Handlers nach Priorität sortieren
        if (isset($this->handlers[$eventClass]) && count($this->handlers[$eventClass]) > 1) {
            usort($this->handlers[$eventClass], function ($a, $b) {
                $priorityA = isset($a['attribute_data']) ? ($a['attribute_data']['priority'] ?? 0) : 0;
                $priorityB = isset($b['attribute_data']) ? ($b['attribute_data']['priority'] ?? 0) : 0;

                return $priorityB <=> $priorityA;
            });
        }

        return $this;
    }

    /**
     * Prüft, ob Handler für den angegebenen Event-Typ registriert sind
     *
     * @param string $eventClass Event-Klasse
     * @return bool True, wenn Handler existieren
     */
    public function hasHandlersFor(string $eventClass): bool
    {
        $this->initialize();

        if (isset($this->handlers[$eventClass]) && !empty($this->handlers[$eventClass])) {
            return true;
        }

        // Prüfen auf Handler für Basisklassen/Interfaces
        return array_any(array_keys($this->handlers), fn($handledEventClass) => is_subclass_of($eventClass, $handledEventClass));

    }
}
