<?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 Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Operation for reordering fields on display/form modes.
 */
#[EbOperation(
  id: 'reorder_fields',
  label: new TranslatableMarkup('Reorder Fields'),
  description: new TranslatableMarkup('Changes the display order of fields on a display or form mode'),
  operationType: 'update',
)]
class ReorderFieldsOperation extends OperationBase {

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

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

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

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

    $display_type = $this->getDataValue('display_type');
    $field_order = $this->getDataValue('field_order');

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

    // Validate field_order is an array.
    if (!is_array($field_order)) {
      $result->addError(
        $this->t('field_order must be an array of field names with weights.'),
        'invalid_field_order'
      );
    }

    return $result;
  }

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

    $preview = new PreviewResult();
    $preview->addWarning($this->t('Will reorder fields on @type mode "@mode"', [
      '@type' => $display_type,
      '@mode' => $display_mode,
    ]));
    $preview->addWarning($this->t('Entity: @entity_type:@bundle', [
      '@entity_type' => $entity_type,
      '@bundle' => $bundle,
    ]));

    if (!empty($field_order)) {
      $preview->addWarning($this->t('New field order (@count fields):', [
        '@count' => count($field_order),
      ]));
      asort($field_order);
      foreach ($field_order as $field_name => $weight) {
        $preview->addWarning("  {$weight}: {$field_name}");
      }
    }

    $details = [
      'Entity Type' => $entity_type,
      'Bundle' => $bundle,
      'Display Mode' => $display_mode,
      'Display Type' => $display_type,
      'Field Count' => count($field_order),
    ];

    $preview->addDetails($details);

    return $preview;
  }

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

    return $this->executeWithErrorHandling(function () use ($entity_type, $bundle, $display_mode, $display_type, $field_order) {
      // 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.");
      }

      // Store original order for rollback.
      $original_order = [];
      foreach ($field_order as $field_name => $weight) {
        $component = $display->getComponent($field_name);
        if ($component !== NULL) {
          $original_order[$field_name] = $component['weight'];
        }
      }

      // Update weights for each field.
      foreach ($field_order as $field_name => $weight) {
        $component = $display->getComponent($field_name);
        if ($component !== NULL) {
          $component['weight'] = (int) $weight;
          $display->setComponent($field_name, $component);
        }
      }

      $display->save();

      $result = new ExecutionResult(TRUE);
      $result->addMessage($this->t('Reordered @count fields on @type mode "@mode".', [
        '@count' => count($field_order),
        '@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,
        'display_mode' => $display_mode,
        'display_type' => $display_type,
        'original_order' => $original_order,
      ]);

      return $result;
    }, 'reorder fields');
  }

  /**
   * {@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'];
      $display_mode = $rollback_data['display_mode'];
      $display_type = $rollback_data['display_type'];
      $original_order = $rollback_data['original_order'] ?? [];

      // 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 original weights.
      if (!empty($original_order)) {
        foreach ($original_order as $field_name => $weight) {
          $component = $display->getComponent($field_name);
          if ($component !== NULL) {
            $component['weight'] = (int) $weight;
            $display->setComponent($field_name, $component);
          }
        }
        $display->save();

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

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

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

}
