<?php

declare(strict_types=1);

namespace Drupal\flowdrop_stategraph\Plugin\FlowDropNodeProcessor;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\DTO\ValidationError;
use Drupal\flowdrop\DTO\ValidationResult;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop_orchestration\DTO\Snapshot\IteratorState;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Loop Control node processor for managing loop flow.
 *
 * This node provides control flow mechanisms within loops:
 * - continue: Skip to the next iteration.
 * - break: Exit the loop entirely.
 * - reset: Restart the loop from the beginning.
 *
 * The node evaluates a condition and outputs routing signals that
 * can be used with conditional edges to control loop flow.
 *
 * Inputs:
 * - action: The control action (continue, break, reset).
 * - condition: Optional condition to evaluate before taking action.
 * - __iterator__: Iterator state injected from graph state.
 *
 * Outputs:
 * - action_taken: The action that was performed.
 * - should_continue: TRUE if loop should continue to next item.
 * - should_break: TRUE if loop should exit.
 * - should_reset: TRUE if loop should restart.
 * - loop_active: TRUE if loop is still active.
 * - state_update: State update to apply to graph state.
 *
 * @see \Drupal\flowdrop_orchestration\DTO\Snapshot\IteratorState
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "loop_control",
  label: new TranslatableMarkup("Loop Control"),
  description: "Control loop flow with continue, break, or reset actions",
  version: "1.0.0"
)]
class LoopControlNode extends AbstractFlowDropNodeProcessor {

  /**
   * Valid control actions.
   *
   * @var array<string>
   */
  private const VALID_ACTIONS = [
    "continue",
    "break",
    "reset",
    "check",
  ];

  /**
   * {@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 {
    $errors = [];

    // Action is required.
    if (!isset($params["action"])) {
      $errors[] = new ValidationError('action', "Missing required parameter: action");
    }
    elseif (!in_array($params["action"], self::VALID_ACTIONS, TRUE)) {
      $validActions = implode(", ", self::VALID_ACTIONS);
      $errors[] = new ValidationError('action', "Invalid action '{$params["action"]}'. Valid actions are: {$validActions}");
    }

    if (!empty($errors)) {
      return ValidationResult::failure($errors);
    }

    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $action = $params->get("action", "check");
    $condition = $params->get("condition", TRUE);
    $iteratorData = $params->get("__iterator__");

    // Evaluate condition if provided as string expression.
    $conditionMet = $this->evaluateCondition($condition);

    // If condition not met, don't take action - just pass through.
    if (!$conditionMet) {
      return $this->createPassthroughOutput($iteratorData);
    }

    // Reconstruct iterator state if available.
    $iterator = $iteratorData !== NULL ? IteratorState::fromArray($iteratorData) : NULL;

    return match ($action) {
      "continue" => $this->handleContinue($iterator),
      "break" => $this->handleBreak($iterator),
      "reset" => $this->handleReset($iterator),
      "check" => $this->handleCheck($iterator),
      default => $this->handleCheck($iterator),
    };
  }

  /**
   * Evaluates a condition.
   *
   * @param mixed $condition
   *   The condition to evaluate (bool or truthy value).
   *
   * @return bool
   *   The evaluation result.
   */
  private function evaluateCondition(mixed $condition): bool {
    // Boolean values are used directly.
    if (is_bool($condition)) {
      return $condition;
    }

    // Null or empty string is false.
    if ($condition === NULL || $condition === "") {
      return FALSE;
    }

    // Cast to boolean.
    return (bool) $condition;
  }

  /**
   * Creates passthrough output when condition is not met.
   *
   * @param array<string, mixed>|null $iteratorData
   *   Current iterator data.
   *
   * @return array<string, mixed>
   *   The output array.
   */
  private function createPassthroughOutput(?array $iteratorData): array {
    $loopActive = $iteratorData !== NULL && !empty($iteratorData);

    return [
      "action_taken" => "none",
      "condition_met" => FALSE,
      "should_continue" => FALSE,
      "should_break" => FALSE,
      "should_reset" => FALSE,
      "loop_active" => $loopActive,
      // No state update - pass through.
    ];
  }

  /**
   * Handles the 'continue' action.
   *
   * @param \Drupal\flowdrop_orchestration\DTO\Snapshot\IteratorState|null $iterator
   *   Current iterator state.
   *
   * @return array<string, mixed>
   *   The output array.
   */
  private function handleContinue(?IteratorState $iterator): array {
    // Continue signals to skip current processing and move to next item.
    // The ForEach node will handle the actual advancement.
    $hasMore = $iterator !== NULL && $iterator->hasMore;

    return [
      "action_taken" => "continue",
      "condition_met" => TRUE,
      "should_continue" => TRUE,
      "should_break" => FALSE,
      "should_reset" => FALSE,
      "loop_active" => $hasMore,
      "has_more_items" => $hasMore,
      // No state update - continue uses existing state.
    ];
  }

  /**
   * Handles the 'break' action.
   *
   * @param \Drupal\flowdrop_orchestration\DTO\Snapshot\IteratorState|null $iterator
   *   Current iterator state.
   *
   * @return array<string, mixed>
   *   The output array.
   */
  private function handleBreak(?IteratorState $iterator): array {
    // Break signals to exit the loop entirely.
    // Collect any results accumulated so far.
    $results = $iterator !== NULL ? $iterator->results : [];

    return [
      "action_taken" => "break",
      "condition_met" => TRUE,
      "should_continue" => FALSE,
      "should_break" => TRUE,
      "should_reset" => FALSE,
      "loop_active" => FALSE,
      "collected_results" => $results,
      "state_update" => [
        "iterator" => NULL,
        "data" => [
          "loop_broken" => TRUE,
          "break_results" => $results,
        ],
      ],
    ];
  }

  /**
   * Handles the 'reset' action.
   *
   * @param \Drupal\flowdrop_orchestration\DTO\Snapshot\IteratorState|null $iterator
   *   Current iterator state.
   *
   * @return array<string, mixed>
   *   The output array.
   */
  private function handleReset(?IteratorState $iterator): array {
    // Reset returns to the beginning of the loop.
    if ($iterator === NULL) {
      return [
        "action_taken" => "reset",
        "condition_met" => TRUE,
        "should_continue" => FALSE,
        "should_break" => FALSE,
        "should_reset" => TRUE,
        "loop_active" => FALSE,
        "error" => "Cannot reset: no active iterator",
      ];
    }

    // Create new iterator from same items, optionally keeping results.
    $newIterator = IteratorState::fromItems($iterator->items);

    return [
      "action_taken" => "reset",
      "condition_met" => TRUE,
      "should_continue" => FALSE,
      "should_break" => FALSE,
      "should_reset" => TRUE,
      "loop_active" => TRUE,
      "current_item" => $newIterator->currentItem,
      "current_index" => $newIterator->currentIndex,
      "state_update" => [
        "iterator" => $newIterator->toArray(),
        "data" => [
          "loop_reset" => TRUE,
          "reset_count" => 1,
        ],
      ],
    ];
  }

  /**
   * Handles the 'check' action.
   *
   * @param \Drupal\flowdrop_orchestration\DTO\Snapshot\IteratorState|null $iterator
   *   Current iterator state.
   *
   * @return array<string, mixed>
   *   The output array.
   */
  private function handleCheck(?IteratorState $iterator): array {
    // Check simply reports the current loop state.
    $loopActive = $iterator !== NULL && !$iterator->isExhausted();
    $hasMore = $iterator !== NULL && $iterator->hasMore;

    return [
      "action_taken" => "check",
      "condition_met" => TRUE,
      "should_continue" => $loopActive,
      "should_break" => FALSE,
      "should_reset" => FALSE,
      "loop_active" => $loopActive,
      "has_more_items" => $hasMore,
      "current_index" => $iterator !== NULL ? $iterator->currentIndex : 0,
      "total_count" => $iterator !== NULL ? $iterator->getTotalCount() : 0,
      "remaining_count" => $iterator !== NULL ? $iterator->getRemainingCount() : 0,
      // No state update for check.
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "action" => [
          "type" => "string",
          "enum" => self::VALID_ACTIONS,
          "description" => "The loop control action to perform",
        ],
        "condition" => [
          "type" => "mixed",
          "description" => "Optional condition - action only taken if condition is truthy",
        ],
        "__iterator__" => [
          "type" => "object",
          "description" => "Iterator state (automatically injected from graph state)",
        ],
      ],
      "required" => ["action"],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "action_taken" => [
          "type" => "string",
          "description" => "The action that was performed",
        ],
        "condition_met" => [
          "type" => "boolean",
          "description" => "Whether the condition was met",
        ],
        "should_continue" => [
          "type" => "boolean",
          "description" => "TRUE if loop should continue to next item",
        ],
        "should_break" => [
          "type" => "boolean",
          "description" => "TRUE if loop should exit",
        ],
        "should_reset" => [
          "type" => "boolean",
          "description" => "TRUE if loop should restart",
        ],
        "loop_active" => [
          "type" => "boolean",
          "description" => "TRUE if loop is still active",
        ],
        "state_update" => [
          "type" => "object",
          "description" => "State update to apply to graph state",
        ],
      ],
    ];
  }

}
