<?php

namespace Drupal\islandora\Plugin\search_api\processor;

use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\islandora\Plugin\search_api\processor\Property\EntityReferenceWithUriProperty;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\islandora\IslandoraUtils;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Add a filterable search api field for entity reference fields.
 *
 * @SearchApiProcessor(
 *   id = "entity_reference_with_term",
 *   label = @Translation("Entity Reference, where the target has a given taxonomy term"),
 *   description = @Translation("Indexes the titles of Entity Reference field targets, where the targets have a taxonomy term or terms. Does not apply to Taxonomy Reference fields."),
 *   stages = {
 *     "add_properties" = 0,
 *   },
 *   locked = false,
 *   hidden = false,
 * )
 */
class EntityReferenceWithUri extends ProcessorPluginBase {

  /**
   * Islandora utils.
   *
   * @var \Drupal\islandora\IslandoraUtils
   */
  protected IslandoraUtils $utils;

  /**
   * Entity Type Manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManager
   */
  protected EntityTypeManager $entityTypeManager;

  /**
   * Constructor.
   *
   * @param array $configuration
   *   The plugin configuration, i.e. an array with configuration values keyed
   *   by configuration option name. The special key 'context' may be used to
   *   initialize the defined contexts by setting it to an array of context
   *   values keyed by context names.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\islandora\IslandoraUtils $utils
   *   Islandora utils.
   * @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
   *   Drupal Entity Type Manager.
   */
  public function __construct(
    array $configuration,
          $plugin_id,
          $plugin_definition,
    IslandoraUtils $utils,
    EntityTypeManager $entityTypeManager
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->utils = $utils;
    $this->entityTypeManager = $entityTypeManager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('islandora.utils'),
      $container->get('entity_type.manager'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
    $properties = [];
    if (!$datasource || !$datasource->getEntityTypeId()) {
      return $properties;
    }

    $entity_type = $datasource->getEntityTypeId();

    // Get all configured Entity Relation fields for this entity type.
    $fields = $this->entityTypeManager->getStorage('field_config')->loadByProperties([
      'entity_type' => $entity_type,
      'field_type' => 'entity_reference',
    ]);

    foreach ($fields as $field) {
      // Skip over fields that point to taxonomy term fields themselves.
      if ($field->getSetting('target_type') == 'taxonomy_term') {
        continue;
      }

      $definition = [
        'label' => $this->t('@label (by term URI) [@bundle]', [
          '@label' => $field->label(),
          '@bundle' => $field->getTargetBundle(),
        ]),
        'description' => $this->t('Index the related entity, but only if the target entity has a taxonomy term.'),
        'type' => 'string',
        'processor_id' => $this->getPluginId(),
        'settings' => [
          'filter_terms' => [],
          'target_type' => $field->getSetting('target_type'),
        ],
        'is_list' => TRUE,
      ];
      $fieldname = 'entity_reference_with_term__' . str_replace('.', '__', $field->id());
      $properties[$fieldname] = new EntityReferenceWithUriProperty($definition);
    }
    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public function addFieldValues(ItemInterface $item) {
    // Skip if no Entity Reference with URI fields are configured.
    $relevant_search_api_fields = [];
    $search_api_fields = $item->getFields(FALSE);
    foreach ($search_api_fields as $field) {
      if (substr($field->getPropertyPath(), 0, 28) == 'entity_reference_with_term__') {
        $relevant_search_api_fields[] = $field;
      }
    }
    if (empty($relevant_search_api_fields)) {
      return;
    }

    $content_entity = $item->getOriginalObject()->getValue();
    $type_and_bundle_prefix = $content_entity->getEntityTypeId() . '__' . $content_entity->bundle() . '__';
    foreach ($relevant_search_api_fields as $search_api_field) {
      $entity_type = $search_api_field->getConfiguration()['target_type'];
      $filter_terms = $search_api_field->getConfiguration()['filter_terms'];
      $filter_tids = array_map(function ($element) {
        return $element['target_id'];
      }, $filter_terms);
      $require_all = $search_api_field->getConfiguration()['require_all'];
      $field_name = substr($search_api_field->getPropertyPath(), 28);
      $field_name = str_replace($type_and_bundle_prefix, '', $field_name);
      if ($content_entity->hasField($field_name)) {
        $referenced_terms = [];
        foreach ($content_entity->get($field_name)->getValue() as $values) {
          foreach ($values as $value) {
            // Load the entity stored in our field.
            try {
              $target_entity = $this->entityTypeManager
                ->getStorage($entity_type)
                ->load($value);
            }
            catch (InvalidPluginDefinitionException $e) {
            }
            catch (PluginNotFoundException $e) {
            }
            // Load the taxonomy terms on the entity stored in our field.
            $referenced_terms = array_merge($referenced_terms, array_filter($target_entity->referencedEntities(), function ($entity) {
              if ($entity->getEntityTypeId() != 'taxonomy_term') {
                return FALSE;
              }
              else {
                return TRUE;
              }
            }));
          }

          $referenced_tids = array_map(function ($element) {
            return $element->id();
          },
            $referenced_terms
          );
          if ($require_all) {
            if (count(array_intersect($referenced_tids, $filter_tids)) == count($filter_tids)) {
              // "All" and all the terms specified are present.
              $label = $target_entity->label();
              $search_api_field->addValue($label);
            }
          }
          else {
            if (count(array_intersect($referenced_tids, $filter_tids)) > 0) {
              // "Any" and at least one term specified is present.
              $label = $target_entity->label();
              $search_api_field->addValue($label);
            }
          }
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function requiresReindexing(array $old_settings = NULL, array $new_settings = NULL) {
    if ($new_settings != $old_settings) {
      return TRUE;
    }
    return FALSE;
  }

}