<?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\Exception\InvalidNodeConfigurationException;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Executor for If/Else Logic nodes.
 *
 * If/Else nodes provide simple conditional logic with text input,
 * match text, and operator fields for basic string comparisons.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "if_else",
  label: new \Drupal\Core\StringTranslation\TranslatableMarkup("If/Else"),
  description: "Simple boolean gateway with text input, match text, and operator",
  version: "1.0.0"
)]
class IfElse extends AbstractFlowDropNodeProcessor {

  /**
   * Allowed comparison operators.
   *
   * @var string[]
   */
  private const ALLOWED_OPERATORS = [
    'equals', 'not_equals', 'contains', 'starts_with', 'ends_with', 'regex',
  ];

  /**
   * Maximum allowed regex pattern length.
   */
  private const MAX_REGEX_LENGTH = 500;

  /**
   * {@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 {
    $operator = $params["operator"] ?? "equals";
    if (!in_array($operator, self::ALLOWED_OPERATORS, TRUE)) {
      return ValidationResult::error(
        "operator",
        "Invalid operator: " . $operator . ". Allowed: " . implode(", ", self::ALLOWED_OPERATORS),
        "INVALID_OPERATOR"
      );
    }

    // Validate regex pattern when operator is "regex".
    if ($operator === "regex") {
      $matchText = $params["matchText"] ?? "";
      if (!empty($matchText)) {
        if (strlen($matchText) > self::MAX_REGEX_LENGTH) {
          return ValidationResult::error(
            "matchText",
            "Regex pattern exceeds maximum length of " . self::MAX_REGEX_LENGTH . " characters.",
            "PATTERN_TOO_LONG"
          );
        }
        if (@preg_match($matchText, "") === FALSE) {
          return ValidationResult::error(
            "matchText",
            "Invalid regex pattern: the pattern could not be compiled.",
            "INVALID_PATTERN"
          );
        }
      }
    }

    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $matchText = $params->getString("matchText", "");
    $operator = $params->getString("operator", "equals");
    $caseSensitive = $params->getBool("caseSensitive", FALSE);
    $strictMode = $params->getBool("strictMode", TRUE);
    $rawInput = $params->get("data", "");

    // Normalize input to string based on strict mode setting.
    $textInput = $this->normalizeToString($rawInput, $strictMode);

    $conditionResult = $this->evaluateCondition($textInput, $operator, $matchText, $caseSensitive);
    $activeBranch = $conditionResult ? "true" : "false";

    return [
      "active_branches" => $activeBranch,
      "input_value" => $textInput,
      "execution_metadata" => [
        "timestamp" => time(),
        "gateway_type" => "branch",
        "flow_control" => TRUE,
        "strict_mode" => $strictMode,
      ],
    ];
  }

  /**
   * Normalizes input to a string for comparison.
   *
   * In strict mode, arrays and objects will throw an exception.
   * In non-strict mode, they will be converted to JSON for comparison.
   *
   * @param mixed $value
   *   The value to normalize.
   * @param bool $strictMode
   *   If TRUE, throws exception for arrays/objects. If FALSE, converts to JSON.
   *
   * @return string
   *   The string representation of the value.
   *
   * @throws \InvalidArgumentException
   *   When strict mode is enabled and input is an array or object.
   */
  protected function normalizeToString(mixed $value, bool $strictMode): string {
    if (is_string($value)) {
      return $value;
    }

    if (is_array($value) || is_object($value)) {
      if ($strictMode) {
        $type = is_array($value) ? "array" : get_class($value);
        throw new \InvalidArgumentException(
          "If/Else processor expects text input, but received {$type}. " .
          "Set 'Strict Mode' to OFF to convert complex types to JSON for comparison, " .
          "or use a JSON processor to extract a specific value first."
        );
      }

      // Non-strict mode: convert to JSON for comparison.
      $encoded = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
      return $encoded !== FALSE ? $encoded : "";
    }

    if (is_bool($value)) {
      return $value ? "true" : "false";
    }

    if (is_null($value)) {
      return "";
    }

    // For int, float, and other scalar types - safe to convert.
    return (string) $value;
  }

  /**
   * Evaluate a condition.
   *
   * @param string $textInput
   *   The text input to evaluate.
   * @param string $operator
   *   The comparison operator.
   * @param string $matchText
   *   The text to match against.
   * @param bool $caseSensitive
   *   Whether the comparison is case-sensitive.
   *
   * @return bool
   *   Whether the condition is met.
   */
  protected function evaluateCondition(string $textInput, string $operator, string $matchText, bool $caseSensitive): bool {
    $inputStr = $textInput;
    $matchStr = $matchText;

    if (!$caseSensitive) {
      $inputStr = strtolower($inputStr);
      $matchStr = strtolower($matchStr);
    }

    switch ($operator) {
      case "equals":
        return $inputStr === $matchStr;

      case "not_equals":
        return $inputStr !== $matchStr;

      case "contains":
        return strpos($inputStr, $matchStr) !== FALSE;

      case "starts_with":
        return strpos($inputStr, $matchStr) === 0;

      case "ends_with":
        return strpos($inputStr, $matchStr) === (strlen($inputStr) - strlen($matchStr));

      case "regex":
        $result = @preg_match($matchStr, $inputStr);
        if ($result === FALSE) {
          throw new InvalidNodeConfigurationException("Regex execution failed for pattern: " . $matchText);
        }
        return $result === 1;

      default:
        throw new InvalidNodeConfigurationException("Unknown operator: {$operator}");
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "mixed",
          "title" => "Input Data",
          "description" => "Data to be evaluated",
          "required" => FALSE,
        ],
        "matchText" => [
          "type" => "string",
          "title" => "Match Text",
          "description" => "The text to match against",
          "default" => "",
        ],
        "operator" => [
          "type" => "string",
          "title" => "Operator",
          "description" => "The comparison operator to use",
          "default" => "equals",
          "enum" => [
            "equals",
            "not_equals",
            "contains",
            "starts_with",
            "ends_with",
            "regex",
          ],
        ],
        "caseSensitive" => [
          "type" => "boolean",
          "title" => "Case Sensitive",
          "description" => "Whether string comparisons are case sensitive",
          "default" => FALSE,
        ],
        "strictMode" => [
          "type" => "boolean",
          "title" => "Strict Mode",
          "description" => "When enabled, throws an error if input is an array or object. When disabled, converts complex types to JSON for comparison.",
          "default" => TRUE,
        ],
        "branches" => [
          "type" => "array",
          "description" => "The active branches",
          "readOnly" => TRUE,
          "default" => [
            [
              "name" => "True",
              "value" => TRUE,
            ],
            [
              "name" => "False",
              "value" => FALSE,
            ],
          ],
          "items" => [
            "type" => "object",
            "properties" => [
              "name" => [
                "type" => "string",
                "description" => "The name of the branch",
              ],
              "value" => [
                "type" => "boolean",
                "description" => "The value of the branch",
              ],
            ],
            "description" => "The active branch",
          ],
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [];
  }

}
