<?php
declare(strict_types=1);

namespace App\Foundation;

use App\Foundation\Contracts\EventDispatcherInterface;

final class EventDispatcher implements EventDispatcherInterface
{
    /** @var array<string,array<int,list<callable>>> */
    private array $listeners = [];
    /** @var list<array{event:string,time:float,listeners:int}> */
    private array $history = [];

    public function listen(string $eventName, callable $listener, int $priority = 0): void
    {
        $this->listeners[$eventName][$priority][] = $listener;
    }

    public function dispatch(string $eventName, mixed $payload = null): mixed
    {
        $started = microtime(true);
        $listeners = $this->resolvedListeners($eventName);
        foreach ($listeners as $listener) {
            $result = $listener($payload, $eventName);
            if ($result !== null) {
                $payload = $result;
            }
        }
        $this->history[] = ['event' => $eventName, 'time' => microtime(true) - $started, 'listeners' => count($listeners)];
        if (count($this->history) > 100) {
            array_shift($this->history);
        }
        return $payload;
    }

    /** @return array<string,int> */
    public function summary(): array
    {
        $result = [];
        foreach ($this->listeners as $event => $priorityGroups) {
            $result[$event] = array_sum(array_map('count', $priorityGroups));
        }
        ksort($result);
        return $result;
    }

    /** @return list<array{event:string,time:float,listeners:int}> */
    public function history(): array { return $this->history; }

    /** @return list<callable> */
    private function resolvedListeners(string $eventName): array
    {
        $groups = $this->listeners[$eventName] ?? [];
        foreach ($this->listeners as $registered => $priorityGroups) {
            if (str_ends_with($registered, '.*') && str_starts_with($eventName, substr($registered, 0, -1))) {
                foreach ($priorityGroups as $priority => $listeners) {
                    foreach ($listeners as $listener) {
                        $groups[$priority][] = $listener;
                    }
                }
            }
        }
        krsort($groups);
        return array_merge(...array_values($groups ?: []));
    }
}
