<?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 Calculator nodes.
 *
 * Performs mathematical operations on numeric values.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "calculator",
  label: new \Drupal\Core\StringTranslation\TranslatableMarkup("Calculator"),
  description: "Perform mathematical operations on numeric values",
  version: "1.0.0"
)]
class Calculator extends AbstractFlowDropNodeProcessor {

  /**
   * Allowed mathematical operations.
   *
   * @var string[]
   */
  private const ALLOWED_OPERATIONS = [
    'add', 'subtract', 'multiply', 'divide', 'power', 'sqrt',
    'average', 'min', 'max', 'median', 'mode',
  ];

  /**
   * {@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 {
    $operation = $params["operation"] ?? "add";
    if (!in_array($operation, self::ALLOWED_OPERATIONS, TRUE)) {
      return ValidationResult::error(
        "operation",
        "Invalid operation: " . $operation . ". Allowed: " . implode(", ", self::ALLOWED_OPERATIONS),
        "INVALID_OPERATION"
      );
    }
    $values = $params["values"] ?? [];
    if (empty($values) || !is_array($values)) {
      return ValidationResult::error(
        "values",
        "Calculator requires an array of numeric values",
        "MISSING_VALUES"
      );
    }
    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $operation = $params->get("operation", "add");
    $precision = $params->get("precision", 2);
    $values = $params->get("values", []);

    // Ensure we have at least 2 values for most operations.
    if (count($values) < 2) {
      $values = [0, 0];
    }

    $result = 0;
    switch ($operation) {
      case "add":
        $result = array_sum($values);
        break;

      case "subtract":
        $result = $values[0];
        for ($i = 1; $i < count($values); $i++) {
          $result -= $values[$i];
        }
        break;

      case "multiply":
        $result = array_product($values);
        break;

      case "divide":
        $result = $values[0];
        for ($i = 1; $i < count($values); $i++) {
          if ($values[$i] != 0) {
            $result /= $values[$i];
          }
          else {
            $result = INF;
            break;
          }
        }
        break;

      case "power":
        $result = pow($values[0], $values[1]);
        break;

      case "sqrt":
        $result = sqrt($values[0]);
        break;

      case "average":
        $result = array_sum($values) / count($values);
        break;

      case "min":
        $result = min($values);
        break;

      case "max":
        $result = max($values);
        break;

      case "median":
        $result = $this->calculateMedian($values);
        break;

      case "mode":
        $result = $this->calculateMode($values);
        break;

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

    $result = round($result, $precision);

    return [
      "result" => $result,
      "operation" => $operation,
      "values" => $values,
      "precision" => $precision,
    ];
  }

  /**
   * Calculate the median of an array of values.
   *
   * @param array<int, float|int> $values
   *   The array of numeric values.
   *
   * @return float
   *   The median value.
   */
  private function calculateMedian(array $values): float {
    if (empty($values)) {
      return 0.0;
    }

    sort($values);
    $count = count($values);

    if ($count % 2 === 0) {
      $middle1 = $values[($count / 2) - 1];
      $middle2 = $values[$count / 2];
      return ($middle1 + $middle2) / 2;
    }
    else {
      return $values[($count - 1) / 2];
    }
  }

  /**
   * Calculate the mode of an array of values.
   *
   * @param array<int, float|int> $values
   *   The array of numeric values.
   *
   * @return float
   *   The mode value (most frequent value).
   */
  private function calculateMode(array $values): float {
    if (empty($values)) {
      return 0.0;
    }

    $counts = array_count_values($values);
    $maxCount = max($counts);
    $modes = array_keys($counts, $maxCount, TRUE);

    return (float) $modes[0];
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "values" => [
          "type" => "array",
          "title" => "Values",
          "description" => "Numeric values for calculation",
          "required" => FALSE,
        ],
        "operation" => [
          "type" => "string",
          "title" => "Operation",
          "description" => "Mathematical operation to perform",
          "default" => "add",
          "enum" => [
            "add",
            "subtract",
            "multiply",
            "divide",
            "power",
            "sqrt",
            "average",
            "min",
            "max",
            "median",
            "mode",
          ],
        ],
        "precision" => [
          "type" => "integer",
          "title" => "Precision",
          "description" => "Number of decimal places",
          "default" => 2,
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "result" => [
          "type" => "number",
          "description" => "The calculation result",
        ],
        "operation" => [
          "type" => "string",
          "description" => "The operation performed",
        ],
        "values" => [
          "type" => "array",
          "description" => "The input values used",
        ],
        "precision" => [
          "type" => "integer",
          "description" => "The precision used",
        ],
      ],
    ];
  }

}
