<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_node_type\Kernel;

use Drupal\flowdrop_node_type\Entity\FlowDropNodeType;
use Drupal\flowdrop_node_type\FlowDropNodeTypeInterface;
use Drupal\flowdrop_node_type\Service\NodeTypeGenerationResult;
use Drupal\flowdrop_node_type\Service\NodeTypeGeneratorInterface;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;

/**
 * Tests NodeTypeGenerator against the real calculator plugin.
 *
 * Verifies the CREATED path end to end: the authoritative parameter/output set
 * comes from the live plugin while the connectable/configurable/exposed flags
 * are reconstructed from the node's embedded metadata, the entity is loadable
 * by metadata.id (the runtime's resolution key), and generation is additive
 * (a second run never overwrites).
 *
 * @group flowdrop_node_type
 * @coversDefaultClass \Drupal\flowdrop_node_type\Service\NodeTypeGenerator
 */
class NodeTypeGeneratorKernelTest extends EntityKernelTestBase {

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

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig(['system']);
    $this->installEntitySchema('flowdrop_node_category');
    $this->installEntitySchema('flowdrop_node_type');
    $this->config('system.logging')->set('error_level', 'hide')->save();
  }

  /**
   * Returns the generator service.
   */
  private function generator(): NodeTypeGeneratorInterface {
    return $this->container->get('flowdrop_node_type.generator');
  }

  /**
   * A calculator node with full, hand-authored metadata.
   *
   * @return array<string, mixed>
   *   The workflow node.
   */
  private function calculatorNode(): array {
    return [
      'id' => 'calc.1',
      'data' => [
        'metadata' => [
          'node_type_id' => 'calculator',
          'executor_plugin' => 'flowdrop_node_processor:calculator',
          'label' => 'Calculator',
          'category' => 'processing',
          'icon' => 'mdi:calculator',
          // The values port is connectable; operation/precision are config.
          'inputs' => [['id' => 'values', 'name' => 'Values', 'dataType' => 'array']],
          'configSchema' => [
            'type' => 'object',
            'properties' => [
              'operation' => ['type' => 'string', 'default' => 'add'],
              'precision' => ['type' => 'integer', 'default' => 2],
            ],
            'required' => [],
          ],
          'config' => ['operation' => 'add', 'precision' => 2],
          // Only result is exposed.
          'outputs' => [['id' => 'result', 'name' => 'Result', 'dataType' => 'number']],
        ],
      ],
    ];
  }

  /**
   * Generation creates a node type whose flags invert the embedded metadata.
   */
  public function testGeneratesNodeTypeFromMetadata(): void {
    $result = $this->generator()->generate($this->calculatorNode());

    $this->assertSame(NodeTypeGenerationResult::CREATED, $result->status);

    // Loadable by metadata.id — the key NodeRuntimeService resolves at runtime.
    $entity = FlowDropNodeType::load('calculator');
    $this->assertInstanceOf(FlowDropNodeTypeInterface::class, $entity);
    $this->assertSame('flowdrop_node_processor:calculator', $entity->getExecutorPlugin());
    $this->assertSame('Calculator', $entity->label());

    // Parameters: authoritative set from the plugin, flags from the metadata.
    $parameters = $entity->getParameters();
    // The set of parameters mirrors the plugin (order is not significant —
    // config storage sorts mapping keys).
    $this->assertEqualsCanonicalizing(['values', 'operation', 'precision'], array_keys($parameters));

    $this->assertTrue($parameters['values']['connectable']);
    $this->assertFalse($parameters['values']['configurable']);

    $this->assertFalse($parameters['operation']['connectable']);
    $this->assertTrue($parameters['operation']['configurable']);
    $this->assertSame('add', $parameters['operation']['default']);

    $this->assertTrue($parameters['precision']['configurable']);
    $this->assertSame(2, $parameters['precision']['default']);

    // Outputs: full plugin set, only result exposed.
    $outputs = $entity->getOutputs();
    $this->assertTrue($outputs['result']['exposed']);
    $this->assertFalse($outputs['operation']['exposed']);
    $this->assertFalse($outputs['values']['exposed']);
  }

  /**
   * A second generation is a no-op skip and never overwrites.
   */
  public function testGenerationIsAdditive(): void {
    $this->generator()->generate($this->calculatorNode());

    // A human edits the generated node type afterwards.
    $entity = FlowDropNodeType::load('calculator');
    $this->assertInstanceOf(FlowDropNodeTypeInterface::class, $entity);
    $entity->setLabel('Hand-edited label')->save();

    // Re-importing the same node must not clobber the edit.
    $result = $this->generator()->generate($this->calculatorNode());
    $this->assertSame(NodeTypeGenerationResult::SKIPPED, $result->status);

    $reloaded = FlowDropNodeType::load('calculator');
    $this->assertSame('Hand-edited label', $reloaded->label());
  }

}
