<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\DTO\ValidationResult;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\DynamicPortTrait;
use Drupal\flowdrop\Service\ExpressionEvaluatorPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Data Mapper node processor.
 *
 * Maps multiple dynamic inputs to multiple dynamic outputs using a
 * configurable expression engine. Users define input ports to collect
 * named values from upstream nodes, then define output ports with
 * expressions that reference those input values.
 *
 * Supported engines (configurable per node instance):
 * - expression_language (default): first_name ~ " " ~ last_name
 * - twig: {{ first_name }} {{ last_name }}
 * - jsonpath: $.first_name
 * - property_path: [first_name]
 *
 * @internal This class is not part of the public API and may change without
 *   notice.
 */
#[FlowDropNodeProcessor(
  id: "data_mapper",
  label: new TranslatableMarkup("Data Mapper"),
  description: "Map multiple inputs to multiple outputs using configurable expressions",
  version: "1.0.0"
)]
class DataMapper extends AbstractFlowDropNodeProcessor {

  use DynamicPortTrait;
  use ExpressionEvaluatorAwareTrait;

  /**
   * Default expression engine.
   */
  protected const DEFAULT_ENGINE = "expression_language";

  /**
   * Constructs a DataMapper object.
   *
   * @param array<string, mixed> $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin ID.
   * @param mixed $plugin_definition
   *   The plugin definition.
   * @param \Drupal\flowdrop\Service\ExpressionEvaluatorPluginManager $evaluatorManager
   *   The expression evaluator plugin manager.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    protected readonly ExpressionEvaluatorPluginManager $evaluatorManager,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get("flowdrop.expression_evaluator_plugin_manager"),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function validateParams(array $params): ValidationResult {
    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   *
   * @return array<string, mixed>
   *   Output values keyed by dynamic output port name. Each value is
   *   the evaluated expression result or NULL if the expression is empty.
   */
  public function process(ParameterBagInterface $params): array {
    [$inputDefs, $outputDefs] = $this->extractDynamicPortDefs($params);
    $this->validatePortNames($inputDefs, $outputDefs);
    $inputValues = $this->collectDynamicInputValues($params);

    $engineId = $params->getString(self::ENGINE_PARAM, self::DEFAULT_ENGINE);
    $evaluator = $this->getEvaluator($engineId);

    // Evaluate each dynamic output expression.
    $result = [];
    foreach ($outputDefs as $def) {
      if (!is_array($def)) {
        continue;
      }
      $name = $def["name"] ?? "";
      $expression = $def["value"] ?? "";
      if ($name === "") {
        continue;
      }

      if ($expression === "") {
        $result[$name] = NULL;
        continue;
      }

      try {
        $result[$name] = $evaluator->evaluate($expression, $inputValues);
      }
      catch (\RuntimeException $e) {
        throw new \RuntimeException(
          "Expression error in dynamic output '{$name}': {$e->getMessage()}",
          0,
          $e
        );
      }
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        self::ENGINE_PARAM => [
          "type" => "string",
          "title" => "Expression Engine",
          "description" => "The expression language to use for output expressions.",
          "default" => self::DEFAULT_ENGINE,
          "enum" => $this->getAvailableEngineIds(),
        ],
        ...$this->buildDynamicInputsSchema(),
        ...$this->buildDynamicOutputsSchema([
          "value" => [
            "type" => "string",
            "title" => "Expression",
            "description" => "Expression to evaluate. Syntax depends on the selected engine.",
            "format" => "template",
          ],
        ]),
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [],
    ];
  }

}
