<?php

namespace Drupal\eb\Plugin\EbValidator;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\eb\Attribute\EbValidator;
use Drupal\eb\PluginBase\ValidatorBase;
use Drupal\eb\Result\ValidationResult;

/**
 * Validates that there are no circular dependencies in operations.
 */
#[EbValidator(
  id: 'circular_dependency',
  label: new TranslatableMarkup('Circular Dependency Validator'),
  description: new TranslatableMarkup('Detects circular dependencies between operations'),
)]
class CircularDependencyValidator extends ValidatorBase {

  /**
   * {@inheritdoc}
   */
  public function validate(array $data): ValidationResult {
    $result = new ValidationResult();

    // This validator needs batch context with multiple operations.
    if (!isset($data['operations']) || !is_array($data['operations'])) {
      return $result;
    }

    $operations = $data['operations'];

    // Build dependency graph.
    $graph = [];
    foreach ($operations as $index => $operation) {
      $graph[$index] = $operation['depends_on'] ?? [];
    }

    // Detect cycles using DFS.
    $visited = [];
    $recursion_stack = [];

    foreach (array_keys($graph) as $node) {
      if ($this->hasCycle($node, $graph, $visited, $recursion_stack)) {
        $result->addError(
          $this->t('Circular dependency detected in operations. Operation @index is part of a dependency cycle.', [
            '@index' => $node,
          ]),
          'circular_dependency'
        );
        break;
      }
    }

    return $result;
  }

  /**
   * Detects cycles in a directed graph using DFS.
   *
   * @param mixed $node
   *   Current node.
   * @param array<mixed, array<mixed>> $graph
   *   Adjacency list representation.
   * @param array<mixed, bool> $visited
   *   Visited nodes.
   * @param array<mixed, bool> $recursionStack
   *   Nodes in current recursion stack.
   *
   * @return bool
   *   TRUE if cycle detected.
   */
  protected function hasCycle(mixed $node, array $graph, array &$visited, array &$recursionStack): bool {
    if (isset($recursionStack[$node])) {
      return TRUE;
    }

    if (isset($visited[$node])) {
      return FALSE;
    }

    $visited[$node] = TRUE;
    $recursionStack[$node] = TRUE;

    if (isset($graph[$node])) {
      foreach ($graph[$node] as $neighbor) {
        if ($this->hasCycle($neighbor, $graph, $visited, $recursionStack)) {
          return TRUE;
        }
      }
    }

    unset($recursionStack[$node]);
    return FALSE;
  }

}
