<?php

namespace Drupal\acquia_contenthub_publisher\EventSubscriber\Config;

use Drupal\acquia_contenthub_publisher\Libs\ExcludeSettingsInterface;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Class FieldConfigSubscriber.
 *
 * Removes excluded fields from settings when field is deleted
 * or marked as required.
 */
class FieldConfigSubscriber implements EventSubscriberInterface {

  /**
   * The exclude settings.
   *
   * @var \Drupal\acquia_contenthub_publisher\Libs\ExcludeSettingsInterface
   */
  protected ExcludeSettingsInterface $excludeSettings;

  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;

  /**
   * FieldConfigSubscriber constructor.
   *
   * @param \Drupal\acquia_contenthub_publisher\Libs\ExcludeSettingsInterface $excludeSettings
   *   The exclude settings.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
   */
  public function __construct(ExcludeSettingsInterface $excludeSettings, LoggerInterface $logger) {
    $this->excludeSettings = $excludeSettings;
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    $events[ConfigEvents::DELETE][] = 'onConfigDelete';
    $events[ConfigEvents::SAVE][] = 'onConfigSave';
    return $events;
  }

  /**
   * Removes excluded fields from settings when field config is deleted.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   The config crud event.
   */
  public function onConfigDelete(ConfigCrudEvent $event): void {
    $config_name = $event->getConfig()->getName();
    if (!$config_name || !$this->isFieldConfig($config_name)) {
      return;
    }
    $field_info = $this->getEntityTypeAndBundleFromFieldConfig($config_name);
    if (!$field_info) {
      return;
    }
    $this->removeFieldFromExcludeSettings($field_info);
  }

  /**
   * Removes excluded fields from settings when field is required.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   The config crud event.
   */
  public function onConfigSave(ConfigCrudEvent $event): void {
    $config_name = $event->getConfig()->getName();
    if (!$config_name || !$this->isFieldConfig($config_name)) {
      return;
    }
    if ($this->shouldRemoveFromExcludeFieldSettings($event->getConfig())) {
      $field_info = $this->getEntityTypeAndBundleFromFieldConfig($config_name);
      if (!$field_info) {
        return;
      }
      $this->removeFieldFromExcludeSettings($field_info);
    }
  }

  /**
   * Removes field from exclude settings.
   *
   * @param array $field_info
   *   The entity type, bundle and field name.
   */
  protected function removeFieldFromExcludeSettings(array $field_info): void {
    if ($this->excludeSettings->removeExcludedFields($field_info['entity_type'], $field_info['bundle'], [$field_info['field_name']])) {
      $this->logger->info('Field: {field_name} of Entity type: {entity_type} and Bundle: {bundle} was removed from excluded settings.', [
        'field_name' => $field_info['field_name'],
        'entity_type' => $field_info['entity_type'],
        'bundle' => $field_info['bundle'],
      ]);
    }
  }

  /**
   * Extracts entity type, bundle and field name from field config name.
   *
   * @param string $field_config_name
   *   The field config name.
   *
   * @return array
   *   The entity type, bundle and field name.
   */
  protected function getEntityTypeAndBundleFromFieldConfig(string $field_config_name): array {
    [,, $entity_type_id, $bundle, $field_name] = explode('.', $field_config_name);
    return [
      'entity_type' => $entity_type_id,
      'bundle' => $bundle,
      'field_name' => $field_name,
    ];
  }

  /**
   * Checks if the config name is a field definition config.
   *
   * @param string $config_name
   *   The config name.
   *
   * @return bool
   *   TRUE if the config name is a field config, FALSE otherwise.
   */
  protected function isFieldConfig(string $config_name): bool {
    $prefix = 'field.field.';
    $parts = explode('.', $config_name);
    return count($parts) === 5 && strpos($config_name, $prefix) === 0;
  }

  /**
   * Checks if the field should be removed from exclude field settings.
   *
   * @param \Drupal\Core\Config\Config $field_config
   *   The field config.
   *
   * @return bool
   *   TRUE if the field should be removed from settings, FALSE otherwise.
   */
  protected function shouldRemoveFromExcludeFieldSettings(Config $field_config): bool {
    $field_definition = $field_config->get();
    return !empty($field_definition['required']);
  }

}
