<?php

declare(strict_types=1);

namespace Gls\GlsPoland\EventDispatcher;

use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;

/**
 * @internal
 */
final class EventDispatcherFactory
{
    /**
     * @param ServiceProviderInterface<EventSubscriberInterface> $container
     */
    public static function create(ServiceProviderInterface $container): EventDispatcherInterface
    {
        $dispatcher = new EventDispatcher();

        foreach ($container->getProvidedServices() as $id => $type) {
            self::registerServiceSubscriber($dispatcher, $container, (string) $id, $type);
        }

        return new Adapter\EventDispatcher($dispatcher);
    }

    private static function registerServiceSubscriber(EventDispatcher $dispatcher, ServiceProviderInterface $container, string $id, string $type): void
    {
        if ('?' === $type) {
            $class = $id;
        } else {
            $class = '?' === $type[0] ? substr($type, 1) : $type;
        }

        if (!is_subclass_of($class, EventSubscriberInterface::class)) {
            throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
        }

        $subscribedEvents = self::getSubscribedEvents($class);
        $listenersByEvent = array_map([self::class, 'normalizeListeners'], $subscribedEvents);

        foreach ($listenersByEvent as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                [$method, $priority] = $listener;

                $listener = static function ($event) use ($container, $id, $method) {
                    return $container->get($id)->{$method}($event);
                };

                $dispatcher->addListener($eventName, $listener, $priority);
            }
        }
    }

    /**
     * @param class-string<EventSubscriberInterface> $class
     */
    private static function getSubscribedEvents(string $class): array
    {
        /** @var iterable $subscribedEvents */
        $subscribedEvents = $class::getSubscribedEvents();

        return is_array($subscribedEvents)
            ? $subscribedEvents
            : iterator_to_array($subscribedEvents);
    }

    /**
     * @param string|array $parameters
     *
     * @return array<array{0: string, 1: int}>
     */
    private static function normalizeListeners($parameters): array
    {
        if (is_string($parameters)) {
            return [[$parameters, 0]];
        }

        if (is_string($parameters[0])) {
            return [[$parameters[0], $parameters[1] ?? 0]];
        }

        return array_map(static function ($listener) {
            return [$listener[0], $listener[1] ?? 0];
        }, $parameters);
    }
}
