<?php

namespace Drupal\eb\Plugin\EbValidator;

use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\eb\Attribute\EbValidator;
use Drupal\eb\PluginBase\ValidatorBase;
use Drupal\eb\Result\ValidationResult;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Validates operation dependencies.
 */
#[EbValidator(
  id: 'dependency',
  label: new TranslatableMarkup('Dependency Validator'),
  description: new TranslatableMarkup('Validates that operation dependencies are satisfied'),
)]
class DependencyValidator extends ValidatorBase implements ContainerFactoryPluginInterface {

  /**
   * Constructor.
   *
   * @param array<string, mixed> $configuration
   *   Plugin configuration.
   * @param string $plugin_id
   *   Plugin ID.
   * @param mixed $plugin_definition
   *   Plugin definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
   *   Entity field manager.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    mixed $plugin_definition,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected EntityFieldManagerInterface $entityFieldManager,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    // @phpstan-ignore-next-line
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager'),
      $container->get('entity_field.manager'),
    );
  }

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

    // Validate entity type exists.
    if (isset($data['entity_type'])) {
      $entityType = $data['entity_type'];
      try {
        $this->entityTypeManager->getDefinition($entityType);
      }
      catch (\Exception $e) {
        $result->addError(
          $this->t('Entity type "@type" does not exist.', ['@type' => $entityType]),
          'entity_type',
          'entity_type_not_found'
        );
        return $result;
      }
    }

    // Validate bundle exists for field operations.
    if (isset($data['entity_type']) && isset($data['bundle']) && isset($data['field_name'])) {
      $entityType = $data['entity_type'];
      $bundle = $data['bundle'];
      $batchContext = $data['_batch_context'] ?? NULL;
      $currentIndex = $data['_batch_index'] ?? -1;

      // Check if bundle will be created in the same batch.
      $bundleInBatch = FALSE;
      if ($batchContext && isset($batchContext['bundle_definitions'][$entityType][$bundle])) {
        $bundleCreationIndex = $batchContext['bundle_definitions'][$entityType][$bundle];
        // Bundle is in batch and will be created before this operation.
        $bundleInBatch = $bundleCreationIndex < $currentIndex;
      }

      // Only validate bundle existence if it's not being created in this batch.
      if (!$bundleInBatch) {
        try {
          $entityTypeDefinition = $this->entityTypeManager->getDefinition($entityType);
          $bundleEntityType = $entityTypeDefinition->getBundleEntityType();

          if ($bundleEntityType) {
            $bundleStorage = $this->entityTypeManager->getStorage($bundleEntityType);
            $existingBundle = $bundleStorage->load($bundle);

            if (!$existingBundle) {
              $result->addError(
                $this->t('Bundle "@bundle" does not exist for entity type "@type".', [
                  '@bundle' => $bundle,
                  '@type' => $entityType,
                ]),
                'bundle',
                'bundle_not_found'
              );
            }
          }
        }
        catch (\Exception $e) {
          $result->addError(
            $this->t('Cannot validate bundle dependency: @message', [
              '@message' => $e->getMessage(),
            ]),
            'bundle',
            'dependency_validation_failed'
          );
        }
      }
    }

    // Validate referenced field exists for update/delete operations.
    if (isset($data['field_name']) && isset($data['entity_type']) && isset($data['bundle'])) {
      if ($this->isOperationType($data, ['update_field', 'delete_field'])) {
        $fieldName = $data['field_name'];
        $entityType = $data['entity_type'];
        $bundle = $data['bundle'];

        $fieldMap = $this->entityFieldManager->getFieldMap();
        $fieldExists = isset($fieldMap[$entityType][$fieldName]) &&
          in_array($bundle, $fieldMap[$entityType][$fieldName]['bundles'], TRUE);

        if (!$fieldExists) {
          $result->addError(
            $this->t('Field "@field" does not exist on bundle "@bundle".', [
              '@field' => $fieldName,
              '@bundle' => $bundle,
            ]),
            'field_name',
            'field_not_found'
          );
        }
      }
    }

    // Validate menu exists for menu link operations.
    if (isset($data['menu_id'])) {
      $menuId = $data['menu_id'];
      $batchContext = $data['_batch_context'] ?? NULL;
      $currentIndex = $data['_batch_index'] ?? -1;

      // Only validate for menu link operations.
      if ($this->isOperationType($data, ['create_menu_link'])) {
        // Check if menu will be created in the same batch.
        $menuInBatch = FALSE;
        if ($batchContext && isset($batchContext['menu_definitions'][$menuId])) {
          $menuCreationIndex = $batchContext['menu_definitions'][$menuId];
          // Menu is in batch and will be created before this operation.
          $menuInBatch = $menuCreationIndex < $currentIndex;
        }

        // Only validate menu existence if it's not being created in this batch.
        if (!$menuInBatch) {
          $menu = $this->entityTypeManager->getStorage('menu')->load($menuId);
          if (!$menu) {
            $result->addError(
              $this->t('Menu "@menu" does not exist.', ['@menu' => $menuId]),
              'menu_id',
              'menu_not_found'
            );
          }
        }
      }
    }

    return $result;
  }

}
