<?php

namespace Drupal\straker_translate\EventSubscriber;

use Drupal\config_translation\ConfigEntityMapper;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
use Drupal\straker_translate\StrakerTranslate;
use Drupal\straker_translate\StrakerTranslateConfigTranslationServiceInterface;
use Drupal\straker_translate\StrakerTranslateConfigurationServiceInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Installer\InstallerKernel;

/**
 * Updates config Straker translation status when saved.
 */
class StrakerTranslateConfigSubscriber implements EventSubscriberInterface {

  /**
   * The Straker Translate content translation service.
   *
   * @var \Drupal\straker_translate\StrakerTranslateConfigTranslationServiceInterface
   */
  protected $translationService;

  /**
   * The mapper manager.
   *
   * @var \Drupal\config_translation\ConfigMapperManagerInterface
   */
  protected $mapperManager;

  /**
   * A array of configuration mapper instances.
   *
   * @var \Drupal\config_translation\ConfigMapperInterface[]
   */
  protected $mappers;

  /**
   * The Straker Translate configuration service.
   *
   * @var \Drupal\straker_translate\StrakerTranslateConfigurationServiceInterface
   */
  protected $straker_translateConfiguration;

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

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The entity field listener.
   *
   * @var \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
   */
  protected $entityFieldListener;

  /**
   * Constructs a StrakerTranslateConfigSubscriber.
   *
   * @param \Drupal\straker_translate\StrakerTranslateConfigTranslationServiceInterface $translation_service
   *   The Straker Translate config translation service.
   * @param \Drupal\config_translation\ConfigMapperManagerInterface $mapper_manager
   *   The configuration mapper manager.
   * @param \Drupal\straker_translate\StrakerTranslateConfigurationServiceInterface $straker_translate_configuration
   *   The Straker Translate configuration service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity manager service.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   * @param \Drupal\Core\Field\FieldStorageDefinitionListenerInterface $entity_field_listener
   *   The entity field listener.   .*/
  public function __construct(StrakerTranslateConfigTranslationServiceInterface $translation_service, ConfigMapperManagerInterface $mapper_manager, StrakerTranslateConfigurationServiceInterface $straker_translate_configuration, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, FieldStorageDefinitionListenerInterface $entity_field_listener) {
    $this->translationService = $translation_service;
    $this->mapperManager = $mapper_manager;
    $this->mappers = $mapper_manager->getMappers();
    $this->straker_translateConfiguration = $straker_translate_configuration;
    $this->entityTypeManager = $entity_type_manager;
    $this->entityFieldManager = $entity_field_manager;
    $this->entityFieldListener = $entity_field_listener;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      ConfigEvents::SAVE => 'onConfigSave',
    ];
  }

  /**
   * Updates the configuration translation status when a configuration is saved.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   The configuration event.
   */
  public function onConfigSave(ConfigCrudEvent $event) {
    if (!InstallerKernel::installationAttempted()) {
      $config = $event->getConfig();
      if (!$config instanceof ConfigEntityInterface) {
        $name = $config->getName();
        $mapper = $this->getMapperFromConfigName($name);
        if ($mapper !== NULL) {
          if ($mapper instanceof ConfigEntityMapper) {
            $entity = $mapper->getEntity();
            if ($this->translationService->getDocumentId($entity)) {
              // We are already set edited status on hook_entity_update
              // $this->translationService->setSourceStatus($entity, StrakerTranslate::STATUS_EDITED);
              // $this->translationService->markTranslationsAsDirty($entity);
            }
          }
          else {
            if ($this->translationService->getConfigDocumentId($mapper)) {
              $this->translationService->setConfigSourceStatus($mapper, StrakerTranslate::STATUS_EDITED);
              $this->translationService->markConfigTranslationsAsDirty($mapper);
            }
          }
          /** @var \Drupal\straker_translate\StrakerTranslateConfigurationServiceInterface $straker_translate_config */
          $straker_translate_config = $this->straker_translateConfiguration;
          $profile = $straker_translate_config->getConfigProfile($mapper->getPluginId());
          if ($profile !== NULL && $profile->id() === StrakerTranslate::PROFILE_DISABLED) {
            $this->translationService->setConfigSourceStatus($mapper, StrakerTranslate::STATUS_DISABLED);
            $this->translationService->setConfigTargetStatuses($mapper, StrakerTranslate::STATUS_DISABLED);
          }
        }
      }

      // If there are changes on content translation settings, we need to react to
      // them in case the entity was enabled for Straker translation.
      if (0 === strpos($config->getName(), 'language.content_settings.') && $event->isChanged('third_party_settings.content_translation.enabled')) {
        $id = $config->get('id');
        [$entity_type_id, $bundle] = explode('.', $id);
        if (!$config->get('third_party_settings.content_translation.enabled')) {
          if ($this->straker_translateConfiguration->isEnabled($entity_type_id, $bundle)) {
            $this->straker_translateConfiguration->setEnabled($entity_type_id, $bundle, FALSE);
            $fields = $this->straker_translateConfiguration->getFieldsStrakerTranslateEnabled($entity_type_id, $bundle);
            foreach ($fields as $field_name) {
              $this->straker_translateConfiguration->setFieldStrakerTranslateEnabled($entity_type_id, $bundle, $field_name, FALSE);
            }
          }
        }
      }
      if (0 === strpos($config->getName(), 'field.field.') && $event->isChanged('translatable')) {
        $id = $config->get('id');
        [$entity_type_id, $bundle, $field_name] = explode('.', $id);
        if (!$config->get('translatable')) {
          /** @var \Drupal\straker_translate\StrakerTranslateConfigurationServiceInterface $straker_translate_config */
          $straker_translate_config = $this->straker_translateConfiguration;
          $field_definition = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
          // We need to make an exception for hosted entities. The field
          // reference may not be translatable, but we want to translate the
          // hosted entity. See https://www.drupal.org/node/2735121.
          $exceptionsThanCanBeTranslatedWithoutBeingEnabled = ['cohesion_entity_reference_revisions', 'entity_reference_revisions'];
          if (isset($field_definition[$field_name]) && !in_array($field_definition[$field_name]->getType(), $exceptionsThanCanBeTranslatedWithoutBeingEnabled) &&
              $straker_translate_config->isFieldStrakerTranslateEnabled($entity_type_id, $bundle, $field_name)) {
            $straker_translate_config->setFieldStrakerTranslateEnabled($entity_type_id, $bundle, $field_name, FALSE);
          }
        }
      }
    }

    if ($event->getConfig()->getName() === 'straker_translate.settings' && $event->isChanged('translate.entity')) {
      $this->entityFieldManager->clearCachedFieldDefinitions();
      $this->entityTypeManager->clearCachedDefinitions();
      \Drupal::service('router.builder')->setRebuildNeeded();

      if (\Drupal::service('entity.definition_update_manager')->needsUpdates()) {
        $entity_types = $this->straker_translateConfiguration->getEnabledEntityTypes();
        foreach ($entity_types as $entity_type_id => $entity_type) {
          $storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
          $installed_storage_definitions = \Drupal::service('entity.last_installed_schema.repository')->getLastInstalledFieldStorageDefinitions($entity_type_id);

          foreach (array_diff_key($storage_definitions, $installed_storage_definitions) as $storage_definition) {
            /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition */
            if ($storage_definition->getProvider() == 'straker_translate') {
              $this->entityFieldListener->onFieldStorageDefinitionCreate($storage_definition);
            }
          }
        }
      }
    }
  }

  /**
   *
   */
  protected function getMapperFromConfigName($name) {
    // @todo This is inefficient.
    $result = NULL;
    foreach ($this->mappers as $mapper) {
      $names = $mapper->getConfigNames();
      foreach ($names as $the_name) {
        if ($the_name === $name) {
          $result = $mapper;
          break;
        }
      }
    }
    if (!$result) {
      // It may not be config, but config entity.
      /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
      $config_manager = \Drupal::service('config.manager');
      $entity_type_id = $config_manager->getEntityTypeIdByName($name);

      if ($entity_type_id === 'field_config') {
        [$field1, $field2, $field_entity_type_id, $field_bundle, $field_id] = explode('.', $name);
        $entity_type_id = $field_entity_type_id . '_fields';
      }
      if (isset($this->mappers[$entity_type_id])) {
        $entity = $config_manager->loadConfigEntityByName($name);
        // Maybe the entity is null because we are deleting also the original
        // entity, e.g. uninstalling a module.
        if ($entity instanceof ConfigEntityInterface) {
          /** @var \Drupal\config_translation\ConfigEntityMapper $mapper */
          $mapper = clone $this->mappers[$entity_type_id];
          $mapper->setEntity($entity);
          $result = $mapper;
        }
      }
    }
    return $result;
  }

}
