<?php

namespace Drupal\acquia_contenthub_publisher\Form;

use Drupal\acquia_contenthub\Client\ClientMetaDataManagerInterface;
use Drupal\acquia_contenthub\Exception\ContentHubClientException;
use Drupal\acquia_contenthub_publisher\EventSubscriber\EnqueueEligibility\EntityTypeOrBundleExclude;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\depcalc\Cache\DepcalcCacheBackend;
use Drupal\depcalc\DependencyCalculatorEvents;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Implements a form to exclude entity types or bundles.
 */
class ExcludeSettingsForm extends ConfigFormBase {

  /**
   * Entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Entity type bundle info.
   *
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $entityTypeBundleInfo;

  /**
   * Depcalc cache.
   *
   * @var \Drupal\depcalc\Cache\DepcalcCacheBackend
   */
  protected DepcalcCacheBackend $depcalcCache;

  /**
   * Event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected EventDispatcherInterface $eventDispatcher;

  /**
   * Client Metadata Manager.
   *
   * @var \Drupal\acquia_contenthub\Client\ClientMetaDataManagerInterface
   */
  protected ClientMetaDataManagerInterface $metaDataManager;

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'acquia_contenthub_publisher_exclude_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return ['acquia_contenthub_publisher.exclude_settings'];
  }

  /**
   * {@inheritdoc}
   */
  public function __construct(ConfigFactoryInterface $config, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, DepcalcCacheBackend $depcalc_cache, EventDispatcherInterface $event_dispatcher, ClientMetaDataManagerInterface $metadata_manager, TypedConfigManagerInterface $typedConfigManager) {
    if (version_compare(\Drupal::VERSION, '10.2.0', '>=')) {
      parent::__construct($config, $typedConfigManager);
    }
    else {
      parent::__construct($config);
    }
    $this->entityTypeManager = $entity_type_manager;
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
    $this->depcalcCache = $depcalc_cache;
    $this->eventDispatcher = $event_dispatcher;
    $this->metaDataManager = $metadata_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.factory'),
      $container->get('entity_type.manager'),
      $container->get('entity_type.bundle.info'),
      $container->get('cache.depcalc'),
      $container->get('event_dispatcher'),
      $container->get('acquia_contenthub.client_metadata_manager'),
      $container->get('config.typed')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $options = $this->getTypeAndBundleOptions();
    $exclude_settings = $this->config('acquia_contenthub_publisher.exclude_settings');

    $form = parent::buildForm($form, $form_state);
    $form['config_entities'] = [
      '#type' => 'details',
      '#title' => $this->t('Config entities'),
      '#open' => TRUE,
    ];
    $form['config_entities']['exclude_config_entities'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Exclude config entities'),
      '#default_value' => $exclude_settings->get('exclude_config_entities'),
      '#description' => $this->t('Excluding config entities would mean that the subscriber sites that are not in the same fleet or are not managing config by code would not be able to import content properly because of missing configs.'),
    ];

    $form['exclude_content_entities'] = [
      '#type' => 'details',
      '#title' => $this->t('Content entities'),
      '#open' => TRUE,
    ];

    $form['exclude_content_entities']['message'] = [
      '#type' => '#item',
      '#markup' => $this->t('<strong>Excluding entity types or bundles from the export
       queue does not control entities that are dependencies of other entities.
        All dependencies are exported regardless of the configuration of this form.</strong>'),
    ];

    $form['exclude_content_entities']['exclude_entity_types'] = [
      '#type' => 'select',
      '#title' => $this->t('Entity types'),
      '#options' => $options['types'],
      '#multiple' => TRUE,
      '#size' => 5,
      '#default_value' => $exclude_settings->get('exclude_entity_types'),
    ];
    $form['exclude_content_entities']['exclude_bundles'] = [
      '#type' => 'select',
      '#title' => $this->t('Bundles'),
      '#options' => $options['bundles'],
      '#multiple' => TRUE,
      '#size' => 5,
      '#default_value' => $exclude_settings->get('exclude_bundles'),
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $exclude_settings = $this->config('acquia_contenthub_publisher.exclude_settings');
    $exclude_settings->set('exclude_entity_types', $form_state->getValue('exclude_entity_types', []))
      ->set('exclude_bundles', $form_state->getValue('exclude_bundles', []));
    if (((bool) $form_state->getValue('exclude_config_entities')) !== $exclude_settings->get('exclude_config_entities')) {
      $exclude_settings->set('exclude_config_entities', $form_state->getValue('exclude_config_entities'));
      $this->depcalcCache->invalidateAll();
      if ($form_state->getValue('exclude_config_entities') && $this->hasCustomDependencyCollector()) {
        $this->messenger()->addWarning($this->t('Any custom dependency collector will not able to import config entities.'));
      }
    }
    $exclude_settings->save();
    $this->messenger()->addMessage($this->t('The settings have been saved.'));
    try {
      $this->metaDataManager->updateClientMetadata();
    }
    catch (ContentHubClientException $e) {
      $this->logger('acquia_contenthub_publisher')->error($e->getMessage());
    }
    catch (\Exception $e) {
      $this->logger('acquia_contenthub_publisher')->error(
        'Something went wrong while sending metadata to service: {error}',
        [
          'error' => $e->getMessage(),
        ]
      );
    }
  }

  /**
   * Returns entity type and bundles options array.
   *
   * @return array
   *   Entity types and bundles options array.
   */
  protected function getTypeAndBundleOptions(): array {
    $entity_type_options = [];
    $bundle_options = [];

    $entity_types = $this->entityTypeManager->getDefinitions();
    foreach ($entity_types as $id => $type) {
      $entity_type_label = $type->getLabel()->render();
      $bundles = $this->entityTypeBundleInfo->getBundleInfo($id);
      $entity_type_options[$id] = $entity_type_label;

      // If bundle and entity type id are the same then entity does not have
      // bundles.
      if (count($bundles) === 1 && array_key_exists($id, $bundles)) {
        continue;
      }

      $bundle_options[$entity_type_label] = [];
      foreach ($bundles as $bundle_id => $bundle_label) {
        $bundle_options[$entity_type_label][EntityTypeOrBundleExclude::formatTypeBundle($id, $bundle_id)] =
          $bundle_label['label'];
      }
    }

    return [
      'types' => $entity_type_options,
      'bundles' => $bundle_options,
    ];
  }

  /**
   * Checks if custom dependency collector is implemented.
   *
   * @return bool
   *   True if custom dependency collector is implemented.
   */
  public function hasCustomDependencyCollector(): bool {
    $event_subscribers = $this->eventDispatcher->getListeners(DependencyCalculatorEvents::CALCULATE_DEPENDENCIES);
    foreach ($event_subscribers as $event_subscriber) {
      if (empty($event_subscriber[0])) {
        continue;
      }
      $class = get_class($event_subscriber[0]);
      if (!strpos($class, 'acquia_contenthub') &&
        !strpos($class, 'depcalc')
      ) {
        return TRUE;
      }
    }
    return FALSE;
  }

}
