<?php

namespace Drupal\eb\Service;

use Drupal\eb\PluginInterfaces\OperationInterface;
use Drupal\eb\PluginManager\EbValidatorPluginManager;
use Drupal\eb\Result\ValidationResult;

/**
 * Service for coordinating validation of operations.
 */
class ValidationManager implements ValidationManagerInterface {

  /**
   * Constructor.
   *
   * @param \Drupal\eb\PluginManager\EbValidatorPluginManager $validatorManager
   *   The validator plugin manager.
   */
  public function __construct(
    protected EbValidatorPluginManager $validatorManager,
  ) {}

  /**
   * Validate a single operation.
   *
   * Performs two-stage validation:
   * 1. Operation's own validation (specific to operation type)
   * 2. All applicable validator plugins (cross-cutting concerns)
   *
   * @param \Drupal\eb\PluginInterfaces\OperationInterface $operation
   *   The operation to validate.
   *
   * @return \Drupal\eb\Result\ValidationResult
   *   The validation result containing all errors and warnings.
   */
  public function validateOperation(OperationInterface $operation): ValidationResult {
    // Stage 1: Run the operation's own validation.
    // Each operation validates its own specific requirements
    // (e.g., CreateFieldOperation validates field type exists).
    $result = $operation->validate();

    // Stage 2: Run all validator plugins for cross-cutting validation.
    // These check general rules like required fields, dependencies, etc.
    $validators = $this->validatorManager->getDefinitions();
    foreach ($validators as $plugin_id => $definition) {
      try {
        // Create an instance of the validator plugin.
        $validator = $this->validatorManager->createInstance($plugin_id);

        // Pass operation data to validator plugin. Validators check things
        // like circular dependencies, unique names, etc.
        $plugin_result = $validator->validate($operation->getData());

        // Merge plugin validation results into main result.
        // Each error includes message, field path, and error code.
        foreach ($plugin_result->getErrors() as $error) {
          $result->addError(
            $error['message'],
            $error['field'] ?? '',
            $error['code'] ?? 'validation_error'
          );
        }

        // Merge warnings (non-blocking issues).
        foreach ($plugin_result->getWarnings() as $warning) {
          $result->addWarning(
            $warning['message'],
            $warning['field'] ?? '',
            $warning['code'] ?? ''
          );
        }
      }
      catch (\Exception $e) {
        // If a validator plugin fails, log it as a warning but continue.
        // This prevents one broken validator from blocking the entire process.
        $result->addWarning(
          "Validator plugin '{$plugin_id}' failed: " . $e->getMessage()
        );
      }
    }

    return $result;
  }

  /**
   * Validate multiple operations in a batch.
   *
   * Validates operations with awareness of the batch context, allowing
   * validators to check if dependencies will be satisfied by earlier
   * operations in the same batch.
   *
   * @param array<\Drupal\eb\PluginInterfaces\OperationInterface> $operations
   *   Array of operations to validate.
   *
   * @return \Drupal\eb\Result\ValidationResult
   *   Aggregated validation result.
   */
  public function validateBatch(array $operations): ValidationResult {
    $aggregate_result = new ValidationResult();

    // Build batch context: map of what will be created.
    $batch_context = $this->buildBatchContext($operations);

    foreach ($operations as $index => $operation) {
      $operation_result = $this->validateOperationWithContext($operation, $batch_context, $index);

      // Aggregate all errors and warnings with context.
      foreach ($operation_result->getErrors() as $error) {
        $context = $this->buildErrorContext($operation);
        $message = $error['message'];
        if ($context) {
          $message .= ' [' . $context . ']';
        }
        $aggregate_result->addError(
          $message,
          $error['field'] ?? '',
          $error['code'] ?? 'validation_error'
        );
      }

      foreach ($operation_result->getWarnings() as $warning) {
        $warning_message = $warning['message'] ?? 'Unknown warning';
        $context = $this->buildErrorContext($operation);
        if ($context) {
          $warning_message .= ' [' . $context . ']';
        }
        $aggregate_result->addWarning($warning_message);
      }
    }

    return $aggregate_result;
  }

  /**
   * Build a human-readable context string for an operation error.
   *
   * @param \Drupal\eb\PluginInterfaces\OperationInterface $operation
   *   The operation.
   *
   * @return string
   *   Context string describing the operation.
   */
  protected function buildErrorContext(OperationInterface $operation): string {
    $data = $operation->getData();
    $parts = [];

    // Get operation type.
    $operationType = $data['operation'] ?? $data['operation_type'] ?? '';
    if ($operationType) {
      $parts[] = $this->formatOperationType($operationType);
    }

    // Add identifying information based on operation type.
    if (!empty($data['bundle_id'])) {
      $parts[] = 'bundle: ' . $data['bundle_id'];
    }
    elseif (!empty($data['field_name'])) {
      $identifier = $data['field_name'];
      if (!empty($data['bundle'])) {
        $identifier = $data['bundle'] . '.' . $identifier;
      }
      $parts[] = 'field: ' . $identifier;
    }
    elseif (!empty($data['group_name'])) {
      $identifier = $data['group_name'];
      if (!empty($data['bundle'])) {
        $identifier = $data['bundle'] . '.' . $identifier;
      }
      $parts[] = 'group: ' . $identifier;
    }
    elseif (!empty($data['role_id'])) {
      $parts[] = 'role: ' . $data['role_id'];
    }
    elseif (!empty($data['menu_id'])) {
      $parts[] = 'menu: ' . $data['menu_id'];
    }
    elseif (!empty($data['label'])) {
      $parts[] = 'label: "' . $data['label'] . '"';
    }

    // Add entity type context if available.
    if (!empty($data['entity_type']) && count($parts) < 3) {
      array_unshift($parts, $data['entity_type']);
    }

    return implode(', ', $parts);
  }

  /**
   * Format operation type for display.
   *
   * @param string $operationType
   *   The operation type ID.
   *
   * @return string
   *   Formatted operation type.
   */
  protected function formatOperationType(string $operationType): string {
    $map = [
      'create_bundle' => 'Bundle',
      'create_field' => 'Field',
      'create_field_group' => 'Field Group',
      'configure_form_mode' => 'Form Display',
      'configure_view_mode' => 'View Display',
      'create_menu' => 'Menu',
      'create_menu_link' => 'Menu Link',
    ];

    return $map[$operationType] ?? ucwords(str_replace('_', ' ', $operationType));
  }

  /**
   * Build context about what will be created in this batch.
   *
   * @param array<\Drupal\eb\PluginInterfaces\OperationInterface> $operations
   *   Array of operations.
   *
   * @return array<string, mixed>
   *   Batch context with information about entities being created.
   */
  protected function buildBatchContext(array $operations): array {
    $context = [
      'bundle_definitions' => [],
      'field_definitions' => [],
      'menu_definitions' => [],
    ];

    foreach ($operations as $index => $operation) {
      $data = $operation->getData();
      $operation_type = $data['operation'] ?? $data['operation_type'] ?? '';

      // Track bundle creation.
      if ($operation_type === 'create_bundle') {
        $entity_type = $data['entity_type'] ?? '';
        $bundle_id = $data['bundle_id'] ?? '';
        if ($entity_type && $bundle_id) {
          $context['bundle_definitions'][$entity_type][$bundle_id] = $index;
        }
      }

      // Track field creation.
      if ($operation_type === 'create_field') {
        $entity_type = $data['entity_type'] ?? '';
        $bundle = $data['bundle'] ?? '';
        $field_name = $data['field_name'] ?? '';
        if ($entity_type && $bundle && $field_name) {
          $context['field_definitions'][$entity_type][$bundle][$field_name] = $index;
        }
      }

      // Track menu creation.
      if ($operation_type === 'create_menu') {
        $menu_id = $data['menu_id'] ?? '';
        if ($menu_id) {
          $context['menu_definitions'][$menu_id] = $index;
        }
      }
    }

    return $context;
  }

  /**
   * Validate an operation with batch context.
   *
   * @param \Drupal\eb\PluginInterfaces\OperationInterface $operation
   *   The operation to validate.
   * @param array<string, mixed> $batch_context
   *   Context about what will be created in the batch.
   * @param int $current_index
   *   Current operation index in the batch.
   *
   * @return \Drupal\eb\Result\ValidationResult
   *   The validation result.
   */
  protected function validateOperationWithContext(OperationInterface $operation, array $batch_context, int $current_index): ValidationResult {
    // Stage 1: Run the operation's own validation.
    $result = $operation->validate();

    // Stage 2: Run validator plugins with batch context.
    $validators = $this->validatorManager->getDefinitions();
    foreach ($validators as $plugin_id => $definition) {
      try {
        $validator = $this->validatorManager->createInstance($plugin_id);

        // Pass both operation data and batch context.
        $operation_data = $operation->getData();
        $operation_data['_batch_context'] = $batch_context;
        $operation_data['_batch_index'] = $current_index;

        $plugin_result = $validator->validate($operation_data);

        foreach ($plugin_result->getErrors() as $error) {
          $result->addError(
            $error['message'],
            $error['field'] ?? '',
            $error['code'] ?? 'validation_error'
          );
        }

        foreach ($plugin_result->getWarnings() as $warning) {
          $result->addWarning(
            $warning['message'],
            $warning['field'] ?? '',
            $warning['code'] ?? 'validation_warning'
          );
        }
      }
      catch (\Exception $e) {
        $result->addWarning(
          "Validator plugin '{$plugin_id}' failed: " . $e->getMessage()
        );
      }
    }

    return $result;
  }

}
