<?php

namespace Drupal\eb\Plugin\EbOperation;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
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\Traits\FieldValidationTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Operation for hiding fields on display/form modes.
 */
#[EbOperation(
  id: 'hide_field',
  label: new TranslatableMarkup('Hide Field'),
  description: new TranslatableMarkup('Hides a field on a specific display or form mode'),
  operationType: 'update',
)]
class HideFieldOperation extends OperationBase {

  use FieldValidationTrait;

  /**
   * Constructor.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    mixed $plugin_definition,
    EntityTypeManagerInterface $entityTypeManager,
    LoggerInterface $logger,
    ConfigFactoryInterface $configFactory,
    protected EntityDisplayRepositoryInterface $displayRepository,
    protected DiscoveryServiceInterface $discoveryService,
  ) {
    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('entity_display.repository'),
      $container->get('eb.discovery_service'),
    );
  }

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

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

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

    $entity_type = $this->getDataValue('entity_type');
    $bundle = $this->getDataValue('bundle');
    $field_name = $this->getDataValue('field_name');
    $display_type = $this->getDataValue('display_type');

    // Validate display type.
    if (!in_array($display_type, ['view', 'form'])) {
      $result->addError(
        $this->t('Display type must be "view" or "form".'),
        'display_type',
        'invalid_display_type'
      );
    }

    // Validate entity type and bundle using trait method.
    if (!$this->validateEntityBundle($entity_type, $bundle, $result)) {
      return $result;
    }

    // Validate field exists using trait method.
    $this->validateFieldExists($entity_type, $bundle, $field_name, $result);

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function preview(): PreviewResult {
    $entity_type = $this->getDataValue('entity_type');
    $bundle = $this->getDataValue('bundle');
    $field_name = $this->getDataValue('field_name');
    $display_mode = $this->getDataValue('display_mode');
    $display_type = $this->getDataValue('display_type');

    $preview = new PreviewResult();
    $preview->addOperation(
      'update',
      $display_type === 'view' ? 'entity_view_display' : 'entity_form_display',
      "{$entity_type}.{$bundle}.{$display_mode}",
      $this->t('Hide field "@field" on @type mode "@mode"', [
        '@field' => $field_name,
        '@type' => $display_type,
        '@mode' => $display_mode,
      ])
    );

    $details = [
      'Entity Type' => $entity_type,
      'Bundle' => $bundle,
      'Field Name' => $field_name,
      'Display Mode' => $display_mode,
      'Display Type' => $display_type,
    ];

    $preview->addDetails($details);

    return $preview;
  }

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

    return $this->executeWithErrorHandling(function () use ($entity_type, $bundle, $field_name, $display_mode, $display_type) {
      // Load the display.
      $storage_type = $display_type === 'view'
        ? 'entity_view_display'
        : 'entity_form_display';

      $display_id = "{$entity_type}.{$bundle}.{$display_mode}";
      $display = $this->entityTypeManager->getStorage($storage_type)->load($display_id);

      if (!$display) {
        throw new ExecutionException("Display '{$display_id}' not found.");
      }

      // Get original component config for rollback.
      $original_component = $display->getComponent($field_name);

      // Remove the component (hides the field).
      $display->removeComponent($field_name);
      $display->save();

      $result = new ExecutionResult(TRUE);
      $result->addMessage($this->t('Field "@field" hidden on @type mode "@mode".', [
        '@field' => $field_name,
        '@type' => $display_type,
        '@mode' => $display_mode,
      ]));

      $result->addAffectedEntity([
        'type' => $storage_type,
        'id' => $display_id,
        'label' => "{$bundle} - {$display_mode}",
      ]);

      $result->setRollbackData([
        'entity_type' => $entity_type,
        'bundle' => $bundle,
        'field_name' => $field_name,
        'display_mode' => $display_mode,
        'display_type' => $display_type,
        'original_component' => $original_component,
      ]);

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

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

    return $this->rollbackWithErrorHandling(function () use ($rollback_data) {
      $entity_type = $rollback_data['entity_type'];
      $bundle = $rollback_data['bundle'];
      $field_name = $rollback_data['field_name'];
      $display_mode = $rollback_data['display_mode'];
      $display_type = $rollback_data['display_type'];
      $original_component = $rollback_data['original_component'] ?? [];

      // Load the display.
      $storage_type = $display_type === 'view'
        ? 'entity_view_display'
        : 'entity_form_display';

      $display_id = "{$entity_type}.{$bundle}.{$display_mode}";
      $display = $this->entityTypeManager->getStorage($storage_type)->load($display_id);

      if (!$display) {
        $result = new RollbackResult(FALSE);
        $result->addMessage($this->t('Display "@display" not found.', [
          '@display' => $display_id,
        ]));
        return $result;
      }

      // Restore the component if original config exists.
      if (!empty($original_component)) {
        $display->setComponent($field_name, $original_component);
        $display->save();

        $result = new RollbackResult(TRUE);
        $result->addMessage($this->t('Restored field "@field" on @type mode "@mode".', [
          '@field' => $field_name,
          '@type' => $display_type,
          '@mode' => $display_mode,
        ]));
        $result->addRestoredEntity([
          'type' => $storage_type,
          'id' => $display_id,
        ]);
      }
      else {
        $result = new RollbackResult(TRUE);
        $result->addMessage($this->t('Field "@field" was already hidden, nothing to restore.', [
          '@field' => $field_name,
        ]));
      }

      $this->logInfo('Rolled back hide field operation for @field on @display', [
        '@field' => $field_name,
        '@display' => $display_id,
      ]);

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

}
