<?php

declare(strict_types=1);

namespace Drupal\flowdrop_interrupt\DependencyInjection\Compiler;

use Drupal\flowdrop_interrupt\Service\InterruptMessageHandlerInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Validates flowdrop_interrupt.message_handler tagged services at build time.
 *
 * Two invariants:
 *  - Every tagged service must implement InterruptMessageHandlerInterface.
 *  - No two handlers may claim the same message class via `supports()`.
 *
 * Violations fail container compilation instead of surfacing at first dispatch
 * — debugging a build error pointing at the offending service.yml is much
 * cheaper than tracing a runtime LogicException out of the bus.
 *
 * @internal
 */
final class InterruptMessageHandlerPass implements CompilerPassInterface {

  public const TAG = 'flowdrop_interrupt.message_handler';

  /**
   * {@inheritdoc}
   */
  public function process(ContainerBuilder $container): void {
    $claims = [];
    foreach ($container->findTaggedServiceIds(self::TAG) as $serviceId => $tags) {
      $definition = $container->getDefinition($serviceId);
      $class = $definition->getClass();
      if ($class === NULL) {
        throw new \LogicException(sprintf(
          'Service "%s" is tagged "%s" but has no class.',
          $serviceId,
          self::TAG,
        ));
      }
      if (!is_subclass_of($class, InterruptMessageHandlerInterface::class)) {
        throw new \LogicException(sprintf(
          'Service "%s" (%s) is tagged "%s" but does not implement %s.',
          $serviceId,
          $class,
          self::TAG,
          InterruptMessageHandlerInterface::class,
        ));
      }

      /** @var class-string<InterruptMessageHandlerInterface> $class */
      $supports = $class::supports();
      if (isset($claims[$supports])) {
        throw new \LogicException(sprintf(
          'Duplicate %s handler for %s: "%s" and "%s" both claim it. '
          . 'Each message class must have exactly one handler.',
          self::TAG,
          $supports,
          $claims[$supports],
          $serviceId,
        ));
      }
      $claims[$supports] = $serviceId;
    }
  }

}
