<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Event;

use InvalidArgumentException;
use PpPhp\Core\Platform\Event\Contracts\EventBusInterface;
use PpPhp\Core\Platform\Event\Contracts\EventInterface;
use PpPhp\Core\Platform\Event\Contracts\EventListenerInterface;
use PpPhp\Core\Platform\Event\Listener\ListenerRepository;

final class EventBus implements EventBusInterface
{
    private ListenerRepository $listeners;
    private EventDispatcher $dispatcher;
    private array $history = [];

    public function __construct(?ListenerRepository $listeners = null, ?EventDispatcher $dispatcher = null)
    {
        $this->listeners = $listeners ?? new ListenerRepository();
        $this->dispatcher = $dispatcher ?? new EventDispatcher($this->listeners);
    }

    public function dispatch(EventInterface|string $event, array $payload = [], array $metadata = []): EventEnvelope
    {
        $event = is_string($event) ? new Event($event, $payload, $metadata) : $event;
        $envelope = $this->dispatcher->dispatch($event);
        $this->history[] = $envelope;
        return $envelope;
    }

    public function listen(string $eventName, mixed $listener, int $priority = 0): void
    {
        if (!$listener instanceof EventListenerInterface && !is_callable($listener)) {
            throw new InvalidArgumentException('Event listener must be callable or implement EventListenerInterface.');
        }
        $this->listeners->add($eventName, $listener, $priority);
    }

    public function listeners(string $eventName): array { return $this->listeners->get($eventName); }
    public function repository(): ListenerRepository { return $this->listeners; }
    public function history(): array { return $this->history; }

    public function toArray(): array
    {
        return [
            'listeners' => $this->listeners->toArray(),
            'history' => array_map(static fn (EventEnvelope $envelope): array => $envelope->toArray(), $this->history),
        ];
    }
}
