<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_node_type\Kernel;

use Drupal\KernelTests\KernelTestBase;
use Drupal\flowdrop_node_type\Controller\Api\NodesController;

/**
 * Pins the tool output port injection driven by the tool_exposed flag.
 *
 * A node type with tool_exposed enabled must present a "tool" output port in
 * its API metadata (so the editor renders a tool source handle), without the
 * processor declaring one. Disabled types stay untouched, and processors that
 * already declare a tool output (ToolBox) must not get a duplicate.
 *
 * @group flowdrop_node_type
 * @coversDefaultClass \Drupal\flowdrop_node_type\Controller\Api\NodesController
 */
class NodesControllerToolPortTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'flowdrop',
    'flowdrop_node_category',
    'flowdrop_node_type',
    'flowdrop_node_processor',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('flowdrop_node_category');
    $this->installEntitySchema('flowdrop_node_type');
  }

  /**
   * Creates a node type and returns its API metadata output ports.
   *
   * @param string $id
   *   The node type ID.
   * @param string $executorPlugin
   *   The executor plugin ID.
   * @param bool $toolExposed
   *   Whether the type is exposed as a tool.
   *
   * @return array<int, array<string, mixed>>
   *   The output port arrays from the metadata response.
   */
  private function outputPortsFor(string $id, string $executorPlugin, bool $toolExposed): array {
    $this->container->get('entity_type.manager')
      ->getStorage('flowdrop_node_type')
      ->create([
        'id' => $id,
        'label' => ucfirst($id),
        'enabled' => TRUE,
        'executor_plugin' => $executorPlugin,
        'tool_exposed' => $toolExposed,
      ])->save();

    $controller = NodesController::create($this->container);
    $response = $controller->getNodeMetadata($id);
    $this->assertSame(200, $response->getStatusCode(), (string) $response->getContent());

    $payload = json_decode((string) $response->getContent(), TRUE);
    return $payload['data']['metadata']['outputs'] ?? [];
  }

  /**
   * Filters ports down to those with the reserved tool id.
   *
   * @param array<int, array<string, mixed>> $ports
   *   The output port arrays.
   *
   * @return array<int, array<string, mixed>>
   *   The tool ports.
   */
  private function toolPorts(array $ports): array {
    return array_values(array_filter(
      $ports,
      static fn (array $port): bool => ($port['id'] ?? '') === 'tool',
    ));
  }

  /**
   * Enabling tool_exposed injects a tool output the plugin never declared.
   *
   * @covers ::getNodeMetadata
   */
  public function testToolPortInjectedWhenExposed(): void {
    $ports = $this->outputPortsFor('calc_exposed', 'flowdrop_node_processor:calculator', TRUE);
    $toolPorts = $this->toolPorts($ports);

    $this->assertCount(1, $toolPorts, json_encode($ports));
    $this->assertSame('output', $toolPorts[0]['type']);
    $this->assertSame('tool', $toolPorts[0]['dataType'],
      'The injected port must carry the tool dataType so the editor applies tool handle styling and tool↔tool connection validation.');
  }

  /**
   * Without the flag, no tool port appears (default unchanged behavior).
   *
   * @covers ::getNodeMetadata
   */
  public function testNoToolPortByDefault(): void {
    $ports = $this->outputPortsFor('calc_plain', 'flowdrop_node_processor:calculator', FALSE);

    $this->assertSame([], $this->toolPorts($ports), json_encode($ports));
  }

  /**
   * A processor declaring its own tool output never gets a duplicate.
   *
   * @covers ::getNodeMetadata
   */
  public function testNoDuplicateForProcessorDeclaredToolPort(): void {
    // ToolBox declares a tool output in its own output schema.
    $ports = $this->outputPortsFor('box_exposed', 'flowdrop_node_processor:toolbox', TRUE);
    $toolPorts = $this->toolPorts($ports);

    $this->assertCount(1, $toolPorts, json_encode($ports));
    // The plugin's own description wins — injection must have been skipped.
    $this->assertSame('The bundled tools, offered as a set to consumers.', $toolPorts[0]['description']);
  }

}
