<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_node_type\Unit\Service;

use Drupal\flowdrop_node_type\Service\NodeMetadataResolver;
use PHPUnit\Framework\TestCase;

/**
 * Tests deriveSchemas() gating of reserved config-schema-only params.
 *
 * A reserved config-schema-only param (dynamicInputs, dynamicOutputs, branches)
 * must be surfaced in configSchema ONLY when the node type entity opts in by
 * declaring the param configurable — so the editor stays in step with
 * ParameterResolver, which resolves those definitions only when the entity
 * marks them configurable. A plugin merely offering the capability in its
 * getParameterSchema() is not enough.
 *
 * deriveSchemas() uses no injected services, so it is exercised via reflection
 * on an instance built without the constructor.
 *
 * @coversDefaultClass \Drupal\flowdrop_node_type\Service\NodeMetadataResolver
 * @group flowdrop_node_type
 */
class NodeMetadataResolverDeriveSchemasTest extends TestCase {

  /**
   * Invokes the protected deriveSchemas() on a constructor-less instance.
   *
   * @param array<string, mixed> $parameterSchema
   *   The plugin parameter schema.
   * @param array<string, array<string, mixed>> $entityParameters
   *   The entity parameter configuration.
   *
   * @return array<string, mixed>
   *   The derived schemas.
   */
  private function derive(array $parameterSchema, array $entityParameters): array {
    $resolver = (new \ReflectionClass(NodeMetadataResolver::class))
      ->newInstanceWithoutConstructor();
    $method = new \ReflectionMethod($resolver, 'deriveSchemas');
    $method->setAccessible(TRUE);
    return $method->invoke($resolver, $parameterSchema, $entityParameters);
  }

  /**
   * A plugin schema offering dynamicInputs. Mirrors DynamicPortTrait output.
   *
   * @return array<string, mixed>
   *   A parameter schema with a dynamicInputs property.
   */
  private function schemaWithDynamicInputs(): array {
    return [
      'type' => 'object',
      'properties' => [
        'template' => ['type' => 'string', 'default' => ''],
        'dynamicInputs' => [
          'type' => 'array',
          'title' => 'Input Ports',
          'default' => [],
        ],
      ],
    ];
  }

  /**
   * The dynamicInputs param is not surfaced when the entity does not declare it.
   *
   * @covers ::deriveSchemas
   */
  public function testDynamicInputsHiddenWhenEntityDoesNotOptIn(): void {
    $derived = $this->derive($this->schemaWithDynamicInputs(), [
      'template' => ['configurable' => TRUE, 'connectable' => FALSE],
      // No dynamicInputs entry: node type has not opted in.
    ]);

    $this->assertArrayNotHasKey(
      'dynamicInputs',
      $derived['configSchema']['properties'],
    );
    $this->assertArrayNotHasKey('dynamicInputs', $derived['configDefaults']);
    // A non-reserved param still resolves normally.
    $this->assertArrayHasKey('template', $derived['configSchema']['properties']);
  }

  /**
   * The dynamicInputs param is surfaced when the entity declares it configurable.
   *
   * @covers ::deriveSchemas
   */
  public function testDynamicInputsShownWhenEntityOptsIn(): void {
    $derived = $this->derive($this->schemaWithDynamicInputs(), [
      'template' => ['configurable' => TRUE, 'connectable' => FALSE],
      'dynamicInputs' => ['configurable' => TRUE, 'connectable' => FALSE, 'default' => []],
    ]);

    $this->assertArrayHasKey(
      'dynamicInputs',
      $derived['configSchema']['properties'],
    );
    // Reserved config-schema-only params never become input ports.
    $this->assertArrayNotHasKey(
      'dynamicInputs',
      $derived['inputSchema']['properties'],
    );
  }

  /**
   * A declared-but-not-configurable reserved param stays hidden.
   *
   * @covers ::deriveSchemas
   */
  public function testDynamicInputsHiddenWhenDeclaredNotConfigurable(): void {
    $derived = $this->derive($this->schemaWithDynamicInputs(), [
      'dynamicInputs' => ['configurable' => FALSE, 'connectable' => FALSE],
    ]);

    $this->assertArrayNotHasKey(
      'dynamicInputs',
      $derived['configSchema']['properties'],
    );
  }

  /**
   * Regression guard: gateway `branches` still surface when opted in.
   *
   * @covers ::deriveSchemas
   */
  public function testBranchesShownForGatewayNodeType(): void {
    $schema = [
      'type' => 'object',
      'properties' => [
        'branches' => ['type' => 'array', 'title' => 'Branches', 'default' => []],
      ],
    ];
    $derived = $this->derive($schema, [
      'branches' => ['configurable' => TRUE, 'connectable' => FALSE, 'default' => []],
    ]);

    $this->assertArrayHasKey('branches', $derived['configSchema']['properties']);
  }

}
