<?php

namespace Drupal\eb\Plugin\EbOperation;

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

/**
 * Operation for creating entity bundles.
 */
#[EbOperation(
  id: 'create_bundle',
  label: new TranslatableMarkup('Create Bundle'),
  description: new TranslatableMarkup('Creates a new bundle (content type, vocabulary, media type, etc.)'),
  operationType: 'create',
)]
class CreateBundleOperation extends OperationBase {

  /**
   * 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\Core\Entity\EntityFieldManagerInterface $entityFieldManager
   *   Entity field manager.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    mixed $plugin_definition,
    EntityTypeManagerInterface $entityTypeManager,
    LoggerInterface $logger,
    ConfigFactoryInterface $configFactory,
    protected DiscoveryServiceInterface $discoveryService,
    protected EntityFieldManagerInterface $entityFieldManager,
  ) {
    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('entity_field.manager'),
    );
  }

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

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

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

    $entity_type = $this->getDataValue('entity_type');
    $bundle_id = $this->getDataValue('bundle_id');

    // Validate entity type exists.
    if (!$this->discoveryService->entityTypeExists($entity_type)) {
      $result->addError(
        $this->t('Entity type "@type" does not exist.', ['@type' => $entity_type]),
        'entity_type',
        'invalid_entity_type'
      );
      return $result;
    }

    // Validate entity type is bundleable.
    if (!$this->discoveryService->isBundleable($entity_type)) {
      $result->addError(
        $this->t('Entity type "@type" does not support bundles.', ['@type' => $entity_type]),
        'entity_type',
        'not_bundleable'
      );
      return $result;
    }

    // Check if bundle already exists.
    if ($this->discoveryService->bundleExists($entity_type, $bundle_id)) {
      $result->addError(
        $this->t('Bundle "@bundle" already exists for entity type "@type".', [
          '@bundle' => $bundle_id,
          '@type' => $entity_type,
        ]),
        'bundle_id',
        'bundle_already_exists'
      );
    }

    // Validate bundle ID format (machine name).
    if (!preg_match('/^[a-z][a-z0-9_]*$/', $bundle_id)) {
      $result->addError(
        $this->t('Bundle ID "@id" must contain only lowercase letters, numbers, and underscores, and must start with a letter.', [
          '@id' => $bundle_id,
        ]),
        'bundle_id',
        'invalid_bundle_id'
      );
    }

    return $result;
  }

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

    $entity_type = $this->getDataValue('entity_type');
    $bundle_id = $this->getDataValue('bundle_id');
    $label = $this->getDataValue('label');

    $preview->addOperation(
      'create',
      'bundle',
      $bundle_id,
      $this->t('Create bundle "@label" (@id) for @type', [
        '@label' => $label,
        '@id' => $bundle_id,
        '@type' => $entity_type,
      ])
    );

    $details = [
      'Entity Type' => $entity_type,
      'Bundle ID' => $bundle_id,
      'Label' => $label,
    ];

    if ($description = $this->getDataValue('description')) {
      $details['Description'] = $description;
    }

    if ($settings = $this->getDataValue('settings')) {
      $details['Additional Settings'] = count($settings) . ' setting(s)';
    }

    $preview->addDetails($details);

    return $preview;
  }

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

    return $this->executeWithErrorHandling(function () use ($entity_type, $bundle_id) {
      // Get the bundle entity type ID.
      $entity_type_definitions = $this->discoveryService->getAllEntityTypes();
      $bundle_entity_type = $entity_type_definitions[$entity_type]['bundle_entity_type'];

      if (!$bundle_entity_type) {
        throw new ExecutionException("Cannot determine bundle entity type for '{$entity_type}'");
      }

      // Prepare bundle configuration.
      $bundle_config = [
        'id' => $bundle_id,
        'label' => $this->getDataValue('label'),
      ];

      // Add description if provided.
      if ($description = $this->getDataValue('description')) {
        $bundle_config['description'] = $description;
      }

      // Add any additional settings.
      if ($settings = $this->getDataValue('settings')) {
        $bundle_config = array_merge($bundle_config, $settings);
      }

      // Handle entity-type-specific configurations.
      if ($entity_type === 'node') {
        // Node-specific defaults.
        $bundle_config += [
          'type' => $bundle_id,
          'name' => $this->getDataValue('label'),
          'display_submitted' => TRUE,
          'new_revision' => TRUE,
          'preview_mode' => 1,
        ];
      }
      elseif ($entity_type === 'taxonomy_term') {
        // Taxonomy-specific defaults.
        $bundle_config += [
          'vid' => $bundle_id,
          'name' => $this->getDataValue('label'),
          'hierarchy' => 0,
        ];
      }
      elseif ($entity_type === 'media') {
        // Media-specific configuration.
        if (!isset($bundle_config['source'])) {
          throw new ExecutionException('Media type requires a source plugin');
        }
      }

      // Create the bundle entity.
      $bundle_storage = $this->entityTypeManager->getStorage($bundle_entity_type);
      $bundle = $bundle_storage->create($bundle_config);
      $bundle->save();

      // Set default values for base fields (node-specific).
      if ($entity_type === 'node') {
        $settings = $this->getDataValue('settings', []);
        $base_field_defaults = [
          'status' => $settings['published_by_default'] ?? TRUE,
          'promote' => $settings['promoted_by_default'] ?? FALSE,
          'sticky' => $settings['sticky_by_default'] ?? FALSE,
        ];

        foreach ($base_field_defaults as $field_name => $default_value) {
          $field_definitions = $this->entityFieldManager->getFieldDefinitions('node', $bundle_id);
          if (isset($field_definitions[$field_name])) {
            $field_definitions[$field_name]->getConfig($bundle_id)->setDefaultValue($default_value)->save();
          }
        }
      }

      // Create default form and view displays.
      $form_display = $this->entityTypeManager
        ->getStorage('entity_form_display')
        ->create([
          'targetEntityType' => $entity_type,
          'bundle' => $bundle_id,
          'mode' => 'default',
          'status' => TRUE,
        ]);
      $form_display->save();

      $view_display = $this->entityTypeManager
        ->getStorage('entity_view_display')
        ->create([
          'targetEntityType' => $entity_type,
          'bundle' => $bundle_id,
          'mode' => 'default',
          'status' => TRUE,
        ]);
      $view_display->save();

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

      $result = new ExecutionResult(TRUE);
      $result->addAffectedEntityById($bundle_entity_type, $bundle->id());

      $result->setRollbackData([
        'bundle_entity_type' => $bundle_entity_type,
        'bundle_id' => $bundle->id(),
        'entity_type' => $entity_type,
      ]);

      if ($edit_url = $this->getEntityEditUrl($bundle_entity_type, $bundle->id())) {
        $result->addMessage($this->t('Bundle created. <a href="@url">Edit bundle</a>', [
          '@url' => $edit_url,
        ]));
      }

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

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

    return $this->rollbackWithErrorHandling(function () use ($rollback_data) {
      $bundle_entity_type = $rollback_data['bundle_entity_type'];
      $bundle_id = $rollback_data['bundle_id'];
      $entity_type = $rollback_data['entity_type'];

      // Load and delete the bundle entity.
      $bundle_storage = $this->entityTypeManager->getStorage($bundle_entity_type);
      $bundle = $bundle_storage->load($bundle_id);

      if ($bundle) {
        $bundle->delete();

        $this->logInfo('Rolled back bundle creation: @bundle', [
          '@bundle' => $bundle_id,
        ]);
      }

      // Delete form and view displays.
      $form_display = $this->entityTypeManager
        ->getStorage('entity_form_display')
        ->load($entity_type . '.' . $bundle_id . '.default');

      if ($form_display) {
        $form_display->delete();
      }

      $view_display = $this->entityTypeManager
        ->getStorage('entity_view_display')
        ->load($entity_type . '.' . $bundle_id . '.default');

      if ($view_display) {
        $view_display->delete();
      }

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

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

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

}
