<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\EventDispatcher;

use InPost\International\EventDispatcher\Util\SubscriberUtil;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;

if (!defined('_PS_VERSION_')) {
    exit;
}

/**
 * @internal
 */
final class EventDispatcherFactory
{
    /**
     * @param ServiceProviderInterface $container service provider of {@see EventSubscriberInterface}
     */
    public static function create(ServiceProviderInterface $container): EventDispatcherInterface
    {
        $dispatcher = new EventDispatcher();

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

        if ($dispatcher instanceof EventDispatcherInterface) {
            return $dispatcher;
        }

        /* @phpstan-ignore deadCode.unreachable */
        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([SubscriberUtil::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);
    }
}
