<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor;

use Drupal\Component\Serialization\Json;
use Drupal\Component\Serialization\Yaml;
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;

/**
 * Executor for Message to Data nodes.
 */
#[FlowDropNodeProcessor(
  id: "message_to_data",
  label: new \Drupal\Core\StringTranslation\TranslatableMarkup("Message to Data"),
  description: "Convert messages to structured data",
  version: "1.0.0"
)]
class MessageToData extends AbstractFlowDropNodeProcessor {

  /**
   * {@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 {
    // Message to data nodes can accept any inputs or none.
    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $format = $params->get("format", "json");
    $extractFields = $params->get("extractFields", []);
    $message = $params->get("message") ?: "";

    $data = [];
    switch ($format) {
      case "json":
        $data = JSON::decode($message, TRUE) ?: [];
        break;

      case "csv":
        $data = $this->parseCsv($message);
        break;

      case "xml":
        $data = $this->parseXml($message);
        break;

      case "yaml":
        $data = $this->parseYaml($message);
        break;

      case "key_value":
        $data = $this->parseKeyValue($message);
        break;
    }

    // Extract specific fields if configured.
    if (!empty($extractFields) && is_array($data)) {
      $extracted = [];
      foreach ($extractFields as $field) {
        if (isset($data[$field])) {
          $extracted[$field] = $data[$field];
        }
      }
      $data = $extracted;
    }

    return [
      "data" => $data,
      "format" => $format,
      "extracted_fields" => $extractFields,
      "original_message" => $message,
    ];
  }

  /**
   * Parse CSV data.
   *
   * @param string $csv
   *   The CSV string.
   *
   * @return array<int, array<string, string>>
   *   The parsed data.
   */
  private function parseCsv(string $csv): array {
    $lines = explode("\n", trim($csv));
    $headers = str_getcsv(array_shift($lines));
    $data = [];
    foreach ($lines as $line) {
      $row = str_getcsv($line);
      if (count($row) === count($headers)) {
        $data[] = array_combine($headers, $row);
      }
    }
    return $data;
  }

  /**
   * Parse XML data.
   *
   * @param string $xml
   *   The XML string.
   *
   * @return array<string, mixed>
   *   The parsed data.
   */
  private function parseXml(string $xml): array {
    $data = [];
    try {
      $xmlObj = simplexml_load_string($xml);
      if ($xmlObj) {
        $data = json_decode(json_encode($xmlObj), TRUE);
      }
    }
    catch (\Exception $e) {
      // Return empty array on parse error.
    }
    return $data;
  }

  /**
   * Parse YAML data.
   *
   * @param string $yaml
   *   The YAML string.
   *
   * @return array<string, mixed>
   *   The parsed data.
   */
  private function parseYaml(string $yaml): array {
    $data = [];
    try {
      if (function_exists("yaml_parse")) {
        $data = Yaml::decode($yaml) ?: [];
      }
    }
    catch (\Exception $e) {
      // Return empty array on parse error.
    }
    return $data;
  }

  /**
   * Parse key-value pairs.
   *
   * @param string $text
   *   The text with key-value pairs.
   *
   * @return array<string, string>
   *   The parsed data.
   */
  private function parseKeyValue(string $text): array {
    $data = [];
    $lines = explode("\n", $text);

    foreach ($lines as $line) {
      $line = trim($line);
      if (empty($line) || strpos($line, "=") === FALSE) {
        continue;
      }

      $parts = explode("=", $line, 2);
      if (count($parts) === 2) {
        $data[trim($parts[0])] = trim($parts[1]);
      }
    }

    return $data;
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "message" => [
          "type" => "string",
          "title" => "Message",
          "description" => "The message to convert to data",
          "required" => FALSE,
        ],
        "format" => [
          "type" => "string",
          "title" => "Format",
          "description" => "Data format to parse",
          "default" => "json",
          "enum" => ["json", "csv", "xml", "yaml", "key_value"],
        ],
        "extractFields" => [
          "type" => "array",
          "title" => "Extract Fields",
          "description" => "Specific fields to extract from the data",
          "default" => [],
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "object",
          "description" => "The parsed data",
        ],
        "format" => [
          "type" => "string",
          "description" => "The format used for parsing",
        ],
        "extracted_fields" => [
          "type" => "array",
          "description" => "The fields that were extracted",
        ],
        "original_message" => [
          "type" => "string",
          "description" => "The original message",
        ],
      ],
    ];
  }

}
