<?php

declare(strict_types=1);

namespace Drupal\flowdrop\DTO\Tool;

/**
 * Immutable {@see ToolCollectionInterface} value object.
 *
 * Indexes bindings by their model-facing name. Names must be unique within a
 * consumer's tool set — uniqueness is the site builder's responsibility and is
 * enforced at compile time, but the constructor also guards defensively: a
 * collision throws rather than silently dropping a tool (a silently missing
 * tool is the worst failure mode, since the model simply cannot see it).
 *
 * @api
 */
final class ToolCollection implements ToolCollectionInterface {

  /**
   * Bindings keyed by model-facing name.
   *
   * @var array<string, \Drupal\flowdrop\DTO\Tool\ToolBindingInterface>
   */
  private readonly array $byName;

  /**
   * Constructs a ToolCollection.
   *
   * @param array<int, \Drupal\flowdrop\DTO\Tool\ToolBindingInterface> $bindings
   *   The tool bindings.
   *
   * @throws \InvalidArgumentException
   *   If two bindings share the same name.
   */
  public function __construct(array $bindings = []) {
    $byName = [];
    foreach ($bindings as $binding) {
      $name = $binding->getName();
      if (isset($byName[$name])) {
        throw new \InvalidArgumentException(sprintf(
          'Duplicate tool name "%s" for nodes "%s" and "%s"; tool names must be unique within a consumer\'s tool set.',
          $name,
          $byName[$name]->getNodeId(),
          $binding->getNodeId(),
        ));
      }
      $byName[$name] = $binding;
    }
    $this->byName = $byName;
  }

  /**
   * {@inheritdoc}
   */
  public function all(): array {
    return array_values($this->byName);
  }

  /**
   * {@inheritdoc}
   */
  public function get(string $name): ?ToolBindingInterface {
    return $this->byName[$name] ?? NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function has(string $name): bool {
    return isset($this->byName[$name]);
  }

  /**
   * {@inheritdoc}
   */
  public function getDefinitions(): array {
    return array_map(
      static fn (ToolBindingInterface $binding): array => $binding->toDefinition(),
      $this->all(),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getIterator(): \ArrayIterator {
    return new \ArrayIterator($this->all());
  }

  /**
   * {@inheritdoc}
   */
  public function count(): int {
    return count($this->byName);
  }

}
