<?php

namespace Drupal\eb\Plugin\EbOperation;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\eb\Attribute\EbOperation;
use Drupal\eb\Exception\ExecutionException;
use Drupal\eb\PluginBase\OperationBase;
use Drupal\eb\Result\ExecutionResult;
use Drupal\eb\Result\PreviewResult;
use Drupal\eb\Result\RollbackResult;
use Drupal\eb\Result\ValidationResult;
use Drupal\eb\Service\DiscoveryServiceInterface;
use Drupal\eb\Service\DisplayConfigurationService;
use Drupal\eb\Service\FieldManagementServiceInterface;
use Drupal\eb\Service\Traits\FieldValidationTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Operation for creating fields on entity bundles.
 */
#[EbOperation(
  id: 'create_field',
  label: new TranslatableMarkup('Create Field'),
  description: new TranslatableMarkup('Creates a new field with storage and instance configuration'),
  operationType: 'create',
)]
class CreateFieldOperation extends OperationBase {

  use FieldValidationTrait;

  /**
   * 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 \Psr\Log\LoggerInterface $logger
   *   Logger channel.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config factory.
   * @param \Drupal\eb\Service\DiscoveryServiceInterface $discoveryService
   *   Discovery service.
   * @param \Drupal\eb\Service\FieldManagementServiceInterface $fieldManagementService
   *   Field management service.
   * @param \Drupal\eb\Service\DisplayConfigurationService $displayConfigurationService
   *   Display configuration service.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    mixed $plugin_definition,
    EntityTypeManagerInterface $entityTypeManager,
    LoggerInterface $logger,
    ConfigFactoryInterface $configFactory,
    protected DiscoveryServiceInterface $discoveryService,
    protected FieldManagementServiceInterface $fieldManagementService,
    protected DisplayConfigurationService $displayConfigurationService,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $entityTypeManager, $logger, $configFactory);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    /** @var \Psr\Log\LoggerInterface $logger */
    $logger = $container->get('logger.channel.eb');
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager'),
      $logger,
      $container->get('config.factory'),
      $container->get('eb.discovery_service'),
      $container->get('eb.field_management'),
      $container->get('eb.display_configuration'),
    );
  }

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

    // Validate required fields.
    $this->validateRequiredFields(
      ['field_name', 'field_type', 'entity_type', 'bundle', 'label'],
      $result
    );

    if (!$result->isValid()) {
      return $result;
    }

    $field_name = $this->getDataValue('field_name');
    $field_type = $this->getDataValue('field_type');
    $entity_type = $this->getDataValue('entity_type');
    $bundle = $this->getDataValue('bundle');
    $widget_data = $this->getDataValue('widget');
    $formatter_data = $this->getDataValue('formatter');

    // Extract widget/formatter type for validation.
    $widget = $widget_data ? $this->extractComponentType($widget_data, '') : NULL;
    $formatter = $formatter_data ? $this->extractComponentType($formatter_data, '') : NULL;

    // Validate field name format.
    if (!preg_match('/^field_[a-z0-9_]+$/', $field_name)) {
      $result->addError(
        $this->t('Field name "@name" must start with "field_" and contain only lowercase letters, numbers, and underscores.', [
          '@name' => $field_name,
        ]),
        'field_name',
        'invalid_field_name'
      );
    }

    // Note: Bundle existence is validated by DependencyValidator plugin,
    // which has batch context awareness to handle dependencies within
    // the same batch. We don't validate it here to avoid duplication.
    // Check if field already exists.
    if ($this->fieldManagementService->fieldConfigExists($entity_type, $bundle, $field_name)) {
      $result->addError(
        $this->t('Field "@name" already exists on bundle "@bundle".', [
          '@name' => $field_name,
          '@bundle' => $bundle,
        ]),
        'field_name',
        'field_already_exists'
      );
    }

    // Validate field type and compatibility.
    $field_validation = $this->discoveryService->validateFieldConfiguration(
      $field_type,
      $widget,
      $formatter
    );

    if (!$field_validation['valid']) {
      foreach ($field_validation['errors'] as $error) {
        $result->addError($error, 'field_type', 'invalid_field_configuration');
      }
    }

    // Add warnings if present.
    foreach ($field_validation['warnings'] ?? [] as $warning) {
      $result->addWarning($warning);
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function preview(): PreviewResult {
    $preview = new PreviewResult();

    $field_name = $this->getDataValue('field_name');
    $field_type = $this->getDataValue('field_type');
    $entity_type = $this->getDataValue('entity_type');
    $bundle = $this->getDataValue('bundle');
    $label = $this->getDataValue('label');
    $required = (bool) $this->getDataValue('required', FALSE);
    $cardinality = (int) $this->getDataValue('cardinality', 1);

    // Check if field storage already exists.
    $storage_exists = $this->fieldManagementService->fieldStorageExists($entity_type, $field_name);
    $storage_action = $storage_exists ? 'Reuse existing' : 'Create new';

    $preview->addOperation(
      'create',
      'field',
      $field_name,
      $this->t('Create field "@label" (@name) on @type:@bundle', [
        '@label' => $label,
        '@name' => $field_name,
        '@type' => $entity_type,
        '@bundle' => $bundle,
      ])
    );

    $details = [
      'Field Name' => $field_name,
      'Field Type' => $field_type,
      'Entity Type' => $entity_type,
      'Bundle' => $bundle,
      'Label' => $label,
      'Required' => $required ? 'Yes' : 'No',
      'Cardinality' => $cardinality === $this->configFactory->get('eb.settings')->get('cardinality_unlimited') ? 'Unlimited' : (string) $cardinality,
      'Storage Action' => $storage_action,
    ];

    if ($widget = $this->getDataValue('widget')) {
      $details['Widget'] = $this->extractComponentType($widget);
    }
    if ($formatter = $this->getDataValue('formatter')) {
      $details['Formatter'] = $this->extractComponentType($formatter);
    }

    $preview->addDetails($details);

    return $preview;
  }

  /**
   * {@inheritdoc}
   */
  public function execute(): ExecutionResult {
    $field_name = $this->getDataValue('field_name');
    $field_type = $this->getDataValue('field_type');
    $entity_type = $this->getDataValue('entity_type');
    $bundle = $this->getDataValue('bundle');

    return $this->executeWithErrorHandling(function () use ($field_name, $field_type, $entity_type, $bundle) {
      // Step 1: Create or reuse field storage.
      $field_storage = $this->fieldManagementService->loadFieldStorage($entity_type, $field_name);
      $storage_created = FALSE;

      if (!$field_storage) {
        $storage_config = [
          'field_name' => $field_name,
          'entity_type' => $entity_type,
          'type' => $field_type,
          'cardinality' => (int) $this->getDataValue('cardinality', 1),
          'translatable' => (bool) $this->getDataValue('translatable', FALSE),
          'settings' => $this->getDataValue('field_storage_settings', []),
        ];

        $field_storage = $this->fieldManagementService->createFieldStorage($storage_config);
        $storage_created = TRUE;
      }
      else {
        // Field storage exists, verify field type compatibility.
        if ($field_storage->getType() !== $field_type) {
          $existing_type = $field_storage->getType();
          throw new ExecutionException(
            "Field storage '{$field_name}' already exists with type '{$existing_type}', but you are trying to create a field with type '{$field_type}'. Field storage cannot be reused with different field types."
          );
        }
        // Message will be added to result below for centralized logging.
      }

      // Step 2: Create field instance.
      $field_config = [
        'field_name' => $field_name,
        'entity_type' => $entity_type,
        'bundle' => $bundle,
        'label' => $this->getDataValue('label'),
        'description' => $this->getDataValue('description', ''),
        'required' => (bool) $this->getDataValue('required', FALSE),
      ];

      // Only set default_value if provided.
      if ($default_value = $this->getDataValue('default_value')) {
        $field_config['default_value'] = $default_value;
      }

      // Add field config settings.
      // For entity_reference: handler, handler_settings go in field config.
      // For other field types: field config settings are typically empty.
      $config_settings = $this->getDataValue('field_config_settings', []);
      if (!empty($config_settings)) {
        // For entity_reference, ensure handler_settings has required structure.
        if ($field_type === 'entity_reference' && isset($config_settings['handler_settings'])) {
          $handler_settings = $config_settings['handler_settings'];

          // Ensure 'sort' is set with proper structure.
          if (!isset($handler_settings['sort'])) {
            $handler_settings['sort'] = [
              'field' => 'name',
              'direction' => 'asc',
            ];
          }

          // Ensure 'auto_create' and 'auto_create_bundle' are present.
          if (!isset($handler_settings['auto_create'])) {
            $handler_settings['auto_create'] = NULL;
          }
          if (!isset($handler_settings['auto_create_bundle'])) {
            $handler_settings['auto_create_bundle'] = NULL;
          }

          $config_settings['handler_settings'] = $handler_settings;
        }

        $field_config['settings'] = $config_settings;
      }

      $field = $this->fieldManagementService->createFieldConfig($field_config);

      // Step 3: Configure form display (widget).
      if ($widget_data = $this->getDataValue('widget')) {
        $widget_config = $this->extractComponentConfig($widget_data, 'widget_settings');

        if ($widget_config['type']) {
          $this->displayConfigurationService->configureFormDisplay(
            $entity_type,
            $bundle,
            $field_name,
            $widget_config['type'],
            $widget_config['settings'],
            (int) $this->getDataValue('weight', 0)
          );
        }
      }

      // Step 4: Configure view display (formatter).
      if ($formatter_data = $this->getDataValue('formatter')) {
        $formatter_config = $this->extractComponentConfig($formatter_data, 'formatter_settings');

        if ($formatter_config['type']) {
          $this->displayConfigurationService->configureViewDisplay(
            $entity_type,
            $bundle,
            $field_name,
            $formatter_config['type'],
            $formatter_config['settings'],
            (int) $this->getDataValue('weight', 0),
            $this->getDataValue('label_display')
          );
        }
      }

      // Clear caches.
      $this->fieldManagementService->clearCachedFieldDefinitions();

      $result = new ExecutionResult(TRUE);
      $result->addAffectedEntityById('field_config', $field->id());
      if ($storage_created) {
        $result->addAffectedEntityById('field_storage_config', $field_storage->id());
      }
      else {
        // Add message about reusing existing storage for audit trail.
        $result->addMessage((string) $this->t('Reused existing field storage'));
      }

      // Store rollback data.
      $result->setRollbackData([
        'field_config_id' => $field->id(),
        'storage_created' => $storage_created,
        'storage_config_id' => $field_storage->id(),
      ]);

      $this->logInfo('Created field: @id', ['@id' => $field->id()]);

      return $result;
    }, 'create field');
  }

  /**
   * {@inheritdoc}
   */
  public function rollback(): RollbackResult {
    $rollback_data = $this->getRequiredRollbackData();

    return $this->rollbackWithErrorHandling(function () use ($rollback_data) {
      // Delete field instance.
      if (isset($rollback_data['field_config_id'])) {
        $field = $this->fieldManagementService->loadFieldConfigById($rollback_data['field_config_id']);
        if ($field) {
          $this->fieldManagementService->deleteFieldConfig($field);
        }
      }

      // Delete field storage if we created it.
      if (!empty($rollback_data['storage_created']) && isset($rollback_data['storage_config_id'])) {
        $storage = $this->fieldManagementService->loadFieldStorageById($rollback_data['storage_config_id']);
        if ($storage) {
          $this->fieldManagementService->deleteFieldStorage($storage);
        }
      }

      // Clear caches.
      $this->fieldManagementService->clearCachedFieldDefinitions();

      $field_config_id = $rollback_data['field_config_id'] ?? 'unknown';
      $this->logInfo('Rolled back field creation: @id', ['@id' => $field_config_id]);

      $result = new RollbackResult(TRUE);
      $result->addMessage($this->t('Successfully rolled back field creation'));

      return $result;
    }, 'field creation');
  }

  /**
   * Extract component type and settings from data.
   *
   * Handles both array format (with 'type' and 'settings') and string format.
   *
   * @param mixed $data
   *   The component data (widget or formatter).
   * @param string $settingsKey
   *   The fallback settings key to check in operation data.
   * @param string $defaultType
   *   The default type to use if not specified.
   *
   * @return array{type: string|null, settings: array<string, mixed>}
   *   Array with 'type' and 'settings' keys.
   */
  protected function extractComponentConfig(mixed $data, string $settingsKey, string $defaultType = 'default'): array {
    if (is_array($data)) {
      return [
        'type' => $data['type'] ?? NULL,
        'settings' => $data['settings'] ?? [],
      ];
    }

    return [
      'type' => $data ?: NULL,
      'settings' => $this->getDataValue($settingsKey, []),
    ];
  }

  /**
   * Extract component type for display.
   *
   * @param mixed $data
   *   The component data.
   * @param string $default
   *   Default value if type not found.
   *
   * @return string
   *   The component type.
   */
  protected function extractComponentType(mixed $data, string $default = 'default'): string {
    if (is_array($data)) {
      return $data['type'] ?? $default;
    }
    return $data ?: $default;
  }

}
