<?php

namespace Drupal\acquia_contenthub\EventSubscriber\SerializeContentField;

use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\Event\SerializeCdfEntityFieldEvent;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\link\Plugin\Field\FieldType\LinkItem;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Link Field Serializer.
 *
 * This class handles the serialization of menu_link entities.
 *
 * @package Drupal\acquia_contenthub\EventSubscriber\SerializeContentField
 */
class LinkFieldSerializer implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    $events[AcquiaContentHubEvents::SERIALIZE_CONTENT_ENTITY_FIELD][] =
      ['onSerializeContentField', 15];
    return $events;
  }

  /**
   * On serialize content field event function.
   *
   * Extracts entity uuids from link fields and serializes them.
   *
   * @param \Drupal\acquia_contenthub\Event\SerializeCdfEntityFieldEvent $event
   *   The content entity field serialization event.
   *
   * @throws \Exception
   */
  public function onSerializeContentField(SerializeCdfEntityFieldEvent $event) {
    // Return if the type of field is not a link.
    if ($event->getField()->getFieldDefinition()->getType() !== 'link') {
      return;
    }

    // Get main entity.
    $entity = $event->getEntity();

    // Confirm the entity is an instance of ContentEntityInterface.
    if (!$entity instanceof ContentEntityInterface) {
      return;
    }

    $field_translations = $this->getFieldTranslations($entity, $event);
    if (!$field_translations) {
      return;
    }

    $cdf = $event->getCdf();
    $metadata = $cdf->getMetadata();
    // Init data arr.
    $data = [];

    // Loop through field translations.
    foreach ($field_translations as $field) {
      $langcode = $field->getLangcode();

      // Set type in metadata.
      $metadata['field'][$event->getFieldName()] = [
        'type' => $event->getField()->getFieldDefinition()->getType(),
      ];

      // Set the translation value to represent null field data.
      if (empty(count($field))) {
        $data['value'][$langcode][] = NULL;
        continue;
      }
      $this->processField($field, $langcode, $data);
    }

    // Set data before continuing.
    $event->setFieldData($data);
    // Set the meta data.
    $cdf->setMetadata($metadata);
    // Stop event propagation.
    $event->stopPropagation();
  }

  /**
   * Extracts all translations of field.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   Entity.
   * @param \Drupal\acquia_contenthub\Event\SerializeCdfEntityFieldEvent $event
   *   The content entity field serialization event.
   *
   * @return \Drupal\Core\Field\FieldItemListInterface[]
   *   List of fields.
   */
  protected function getFieldTranslations(ContentEntityInterface $entity, SerializeCdfEntityFieldEvent $event) {
    $fields = [];
    $languages = $entity->getTranslationLanguages();
    $field = $event->getField();
    if ($field->getFieldDefinition()->isTranslatable()) {
      foreach ($languages as $language) {
        $translated = $entity->getTranslation($language->getId());
        $fields[] = $translated->get($event->getFieldName());
      }
    }
    else {
      $fields[] = $field;
    }
    return $fields;
  }

  /**
   * Returns entity uuid for an entity reference link.
   *
   * @param string $uri_reference
   *   Uri reference string containing entity info.
   *
   * @return string
   *   Entity uuid if available.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getUriForEntityLink(string $uri_reference): string {
    // Explode entity to get the type and id.
    [$entity_type, $entity_id] = explode('/', $uri_reference, 2);

    // Load the entity to be added as a dependency.
    $uri_entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id);

    // If the entity is missing, skip this field.
    return !is_null($uri_entity) ? $uri_entity->uuid() : '';
  }

  /**
   * Handles internal links and sets the values for entities if found.
   *
   * @param \Drupal\link\Plugin\Field\FieldType\LinkItem $item
   *   Field link item.
   * @param string $uri_reference
   *   Uri reference.
   * @param array $values
   *   Values array to update.
   */
  protected function processInternalLink(LinkItem $item, string $uri_reference, array &$values): void {
    $url = $item->getUrl();
    if ($url && $url->isRouted()) {
      $route_params = $url->getRouteParameters();
      if (!empty($route_params)) {
        try {
          $uri_storage = \Drupal::entityTypeManager()->getStorage(key($route_params));
        }
        catch (\Exception $e) {
          $uri_storage = NULL;
        }
        $uri_entity = !is_null($uri_storage) ? $uri_storage->load(current($route_params)) : NULL;
        if (!is_null($uri_entity)) {
          $internal_path = $url->getInternalPath();
          // This only needs to be done in case internal link is
          // internal:/<ENT_TYPE>/<ENT_ID>. Not required for path aliases
          // and internal routes.
          if ($internal_path === ltrim($uri_reference, '/')) {
            $values['internal_type'] = 'internal_entity';
            $values['uri'] = $uri_entity->uuid();
          }
        }
      }
    }
  }

  /**
   * Process the field values for entities.
   *
   * @param \Drupal\Core\Field\FieldItemListInterface $field
   *   Field array.
   * @param string $langcode
   *   Langcode.
   * @param array $data
   *   Data array containing the serialized field values.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function processField(FieldItemListInterface $field, string $langcode, array &$data): void {
    // Loop through fields to get link values to serialize.
    /** @var \Drupal\link\Plugin\Field\FieldType\LinkItem $item */
    foreach ($field as $item) {
      // Get values.
      $values = $item->getValue();

      // If values are empty, continue to next menu_link item.
      if (empty($values['uri'])) {
        continue;
      }

      // Explode the uri first by a colon to retrieve the link type.
      [$uri_type, $uri_reference] = explode(':', $values['uri'], 2);

      // Set uri type in meta data.
      $values['uri_type'] = $item->isExternal() ? 'external' : $uri_type;
      if ($uri_type === 'entity') {
        $entity_uri = $this->getUriForEntityLink($uri_reference);

        // If the entity uri is empty, skip this field.
        if (empty($entity_uri)) {
          continue;
        }

        // Place the entity's uuid into the value position.
        $values['uri'] = $entity_uri;
      }
      elseif ($uri_type === 'internal') {
        $this->processInternalLink($item, $uri_reference, $values);
      }
      $data['value'][$langcode][] = $values;
    }
  }

}
