<?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 Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

/**
 * Executor for Data Extractor nodes.
 *
 * This processor takes input and extracts data
 * using a property path (e.g., "[users][0][name]" or "data.user.email").
 */
#[FlowDropNodeProcessor(
  id: "data_extractor",
  label: new \Drupal\Core\StringTranslation\TranslatableMarkup("Data Extractor"),
  description: "Extract data from JSON using property path notation.",
  version: "1.0.0"
)]
class DataExtractor extends AbstractFlowDropNodeProcessor {

  /**
   * The property accessor service.
   *
   * @var \Symfony\Component\PropertyAccess\PropertyAccessorInterface
   */
  protected PropertyAccessorInterface $propertyAccessor;

  /**
   * 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.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ): static {
    return new static($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@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}
   */
  protected function process(ParameterBagInterface $params): array {
    $propertyPath = $params->get("path", "");
    $dataInput = $params->get("data");

    // Extract data using the property path.
    $extractedData = $this->extractDataByPath($dataInput, $propertyPath);

    return [
      "data" => $extractedData,
      "path" => $propertyPath,
      "success" => $extractedData !== NULL,
    ];
  }

  /**
   * Extracts data from the decoded data using a property path.
   *
   * @param mixed $data
   *   The decoded data structure (array or object).
   * @param string $path
   *   The property path (e.g., "[users][0][name]" or "data.user.email").
   *
   * @return mixed
   *   The extracted data or NULL if the path doesn't exist.
   */
  private function extractDataByPath(mixed $data, string $path): mixed {
    // If no path is provided, return the entire data.
    if (empty($path)) {
      return $data;
    }

    try {
      // Use Symfony PropertyAccess to extract data.
      if ($this->propertyAccessor->isReadable($data, $path)) {
        return $this->propertyAccessor->getValue($data, $path);
      }
      return NULL;
    }
    catch (\Exception $e) {
      return NULL;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "mixed",
          "title" => "Input Data",
          "description" => "Input value from which data needs to be extracted from",
          "required" => TRUE,
        ],
        "path" => [
          "type" => "string",
          "description" => "The property path using a simple string path (like user.address.city or items[0].name).",
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "mixed",
          "description" => "The extracted data from the JSON using the property path",
        ],
        "path" => [
          "type" => "string",
          "description" => "The property path used for extraction",
        ],
        "success" => [
          "type" => "boolean",
          "description" => "Whether the extraction was successful",
        ],
      ],
    ];
  }

}
