<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor;

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

/**
 * Executor for Data Extractor nodes.
 *
 * This processor takes input data and extracts values using a configurable
 * expression engine. Site builders choose the engine per node instance.
 *
 * Supported engines:
 * - jsonpath (default): $.users[*].name, $.items[?(@.active)]
 * - property_path: [users][0][name] or users.0.name
 * - expression_language: data["users"][0]["name"]
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "data_extractor",
  label: new \Drupal\Core\StringTranslation\TranslatableMarkup("Data Extractor"),
  description: "Extract data using configurable expression engines (JSONPath, property path, etc.)",
  version: "2.1.0"
)]
class DataExtractor extends AbstractFlowDropNodeProcessor {

  use ExpressionEvaluatorAwareTrait;

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

  /**
   * Constructs a DataExtractor 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 {
    if (!isset($params["data"])) {
      return ValidationResult::error(
        "data",
        "Data Extractor requires input data",
        "MISSING_DATA"
      );
    }
    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $path = $params->getString("path", "");
    $dataInput = $params->get("data");
    $defaultValue = $params->get("default");
    $extractAll = $params->getBool("extract_all", FALSE);
    $engineId = $params->getString(self::ENGINE_PARAM, self::DEFAULT_ENGINE);

    $evaluator = $this->getEvaluator($engineId);
    $isJsonPath = str_starts_with($path, '$');

    if ($path === "") {
      return [
        "data" => $dataInput,
        "path" => $path,
        "success" => TRUE,
        "is_jsonpath" => FALSE,
        "match_count" => 0,
        self::ENGINE_PARAM => $engineId,
      ];
    }

    try {
      $extractedData = $evaluator->evaluate($path, $dataInput);
      $success = $extractedData !== NULL;

      // Support extract_all: when false and result is an array from
      // JSONPath, return only the first match for backward compatibility.
      $matchCount = 0;
      if ($extractAll && is_array($extractedData)) {
        $matchCount = count($extractedData);
        $success = $matchCount > 0;
        if (!$success) {
          $extractedData = $defaultValue;
        }
      }
      elseif (!$extractAll && is_array($extractedData) && $isJsonPath) {
        $matchCount = count($extractedData);
        $extractedData = !empty($extractedData) ? reset($extractedData) : $defaultValue;
        $success = $matchCount > 0;
      }
      else {
        $matchCount = $success ? 1 : 0;
        if (!$success) {
          $extractedData = $defaultValue;
        }
      }

      return [
        "data" => $extractedData,
        "path" => $path,
        "success" => $success,
        "is_jsonpath" => $isJsonPath,
        "match_count" => $matchCount,
        self::ENGINE_PARAM => $engineId,
      ];
    }
    catch (\RuntimeException $e) {
      return [
        "data" => $defaultValue,
        "path" => $path,
        "success" => FALSE,
        "is_jsonpath" => $isJsonPath,
        "match_count" => 0,
        self::ENGINE_PARAM => $engineId,
        "error" => $e->getMessage(),
      ];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "mixed",
          "title" => "Input Data",
          "description" => "Input data to extract from (array, object, or JSON string)",
          "required" => TRUE,
        ],
        "path" => [
          "type" => "string",
          "title" => "Path Expression",
          "description" => "Expression to evaluate. Syntax depends on the selected engine.",
          "default" => "",
        ],
        "default" => [
          "type" => "mixed",
          "title" => "Default Value",
          "description" => "Value to return if path doesn't match anything",
          "default" => NULL,
        ],
        "extract_all" => [
          "type" => "boolean",
          "title" => "Extract All Matches",
          "description" => "When true, returns all matching values as an array. When false, returns only the first match.",
          "default" => FALSE,
        ],
        self::ENGINE_PARAM => [
          "type" => "string",
          "title" => "Expression Engine",
          "description" => "The expression language to use for path expressions.",
          "default" => self::DEFAULT_ENGINE,
          "enum" => $this->getAvailableEngineIds(),
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "mixed",
          "description" => "The extracted data (single value or array depending on extract_all)",
        ],
        "path" => [
          "type" => "string",
          "description" => "The path expression that was used",
        ],
        "success" => [
          "type" => "boolean",
          "description" => "Whether the extraction found any matches",
        ],
        "is_jsonpath" => [
          "type" => "boolean",
          "description" => "Whether a JSONPath expression was used (starts with $)",
        ],
        "match_count" => [
          "type" => "integer",
          "description" => "Number of values matched by the path",
        ],
        self::ENGINE_PARAM => [
          "type" => "string",
          "description" => "The expression engine used",
        ],
      ],
    ];
  }

}
