<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_node_type\Kernel;

use Drupal\KernelTests\KernelTestBase;
use Drupal\flowdrop\Constants\ReservedName;
use Drupal\flowdrop_node_type\Controller\Api\NodesController;

/**
 * Pins the reserved `trigger` output port injected on node types.
 *
 * Every ordinary node type gets a `trigger` output port injected into its API
 * metadata so it can emit a flow-control signal downstream. Terminal nodes
 * (TerminalNodeProcessorInterface, e.g. Stop) end the run when reached — they
 * throw WorkflowStopException and abandon everything downstream — so the port
 * is meaningless there and must not be injected. This is the outbound dual of
 * suppressing the trigger input on start nodes (see TriggerInputInjectionTest).
 *
 * @group flowdrop_node_type
 * @coversDefaultClass \Drupal\flowdrop_node_type\Service\NodeMetadataResolver
 */
class TriggerOutputInjectionTest extends KernelTestBase {

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

  /**
   * {@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.
   *
   * @return array<int, array<string, mixed>>
   *   The output port arrays from the metadata response.
   */
  private function outputPortsFor(string $id, string $executorPlugin): array {
    $this->container->get('entity_type.manager')
      ->getStorage('flowdrop_node_type')
      ->create([
        'id' => $id,
        'label' => ucfirst($id),
        'enabled' => TRUE,
        'executor_plugin' => $executorPlugin,
      ])->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 trigger id.
   *
   * @param array<int, array<string, mixed>> $ports
   *   The output port arrays.
   *
   * @return array<int, array<string, mixed>>
   *   The trigger ports.
   */
  private function triggerPorts(array $ports): array {
    return array_values(array_filter(
      $ports,
      static fn (array $port): bool => ($port['id'] ?? '') === ReservedName::PORT_TRIGGER,
    ));
  }

  /**
   * An ordinary (non-terminal) node type gets the reserved trigger output port.
   *
   * @covers ::buildForNodeType
   */
  public function testTriggerOutputInjectedForOrdinaryNode(): void {
    $ports = $this->outputPortsFor('proc', 'flowdrop_node_processor:nop');
    $triggerPorts = $this->triggerPorts($ports);

    $this->assertCount(1, $triggerPorts, json_encode($ports));
    $this->assertSame('output', $triggerPorts[0]['type']);
  }

  /**
   * A terminal node (TerminalNodeProcessorInterface) has no trigger output.
   *
   * @covers ::buildForNodeType
   */
  public function testTriggerOutputNotInjectedForTerminalNode(): void {
    $ports = $this->outputPortsFor('terminalType', 'flowdrop_node_type_test:terminal_probe');

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

}
