<?php

/**
 * @file
 * Code for the mentions module.
 */

use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Contracts\EventDispatcher\Event;
use Drupal\mentions\Plugin\Filter\MentionsFilter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Link;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\profile\Entity\ProfileInterface;

/**
 * Implements hook_help().
 */
function mentions_help(string $route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'entity.mentions_type.list':
      $text_format_url = Url::fromRoute('filter.admin_overview');
      $text_format_link = Link::fromTextAndUrl(t('text formats'), $text_format_url)->toString();
      return t("You can enable 'Mentions Filter' for the mentions type below for the appropriate @text_format", ['@text_format' => $text_format_link]);
  }
}

/**
 * Implements hook_theme().
 */
function mentions_theme(): array {
  return [
    'mention_link' => [
      'variables' => [
        'mention_id' => NULL,
        'link' => NULL,
        'render_link' => FALSE,
        'render_value' => '',
        'render_plain' => FALSE,
      ],
    ],
  ];
}

/**
 * Implements hook_entity_insert().
 */
function mentions_entity_insert(EntityInterface $entity): void {
  if (PHP_SAPI !== 'cli') {
    mentions_insert_or_update($entity, FALSE);
  }
}

/**
 * Implements hook_entity_update().
 */
function mentions_entity_update(EntityInterface $entity): void {
  if (PHP_SAPI !== 'cli') {
    mentions_insert_or_update($entity, TRUE);
  }
}

/**
 * Handling mentions crud operations.
 */
function mentions_insert_or_update(EntityInterface $entity, bool $update): void {
  $config = \Drupal::config('mentions.settings');
  $supported_entities = $config->get('supported_entity_types');
  if (empty($supported_entities) || !in_array($entity->getEntityTypeId(), $supported_entities)) {
    return;
  }

  $type = $entity->getEntityTypeId();
  assert($entity instanceof FieldableEntityInterface);
  $field_definitions = $entity->getFieldDefinitions();
  $fields_with_text = [];
  $supported_field_types = ['text_with_summary', 'text_long', 'text'];
  foreach ($field_definitions as $field_definition) {
    $field_type = $field_definition->getType();
    if (!empty($field_type) && in_array($field_type, $supported_field_types)) {
      $fields_with_text[] = $field_definition->getName();
    }
  }

  foreach ($fields_with_text as $text_field) {
    $entity_text_field = $entity->get($text_field);
    $value = $entity_text_field->value;
    $format = !empty($entity_text_field->format) ? $entity_text_field->format : 'basic_html';

    $container = \Drupal::getContainer();
    $filter_mentions = MentionsFilter::create($container, [], 'filter_mentions', []);
    $filter_mentions->setTextFormat($format);
    if ($filter_mentions->shouldApplyFilter() && !empty($value)) {
      $mentions = $filter_mentions->getMentions($value);
      $auid = \Drupal::currentUser()->id();
      mentions_crud_update($type, $mentions, (int) $entity->id(), $auid);
    }
  }
}

/**
 * Implements hook_entity_delete().
 */
function mentions_entity_delete(EntityInterface $entity): void {
  $mention_ids = \Drupal::entityQuery('mentions')
    ->condition('entity_id', $entity->id())
    ->condition('entity_type', $entity->getEntityTypeId())
    ->accessCheck()
    ->execute();

  $storage_handler = \Drupal::entityTypeManager()->getStorage('mentions');
  $entities = $storage_handler->loadMultiple($mention_ids);
  $storage_handler->delete($entities);

  $event_dispatcher = \Drupal::service('event_dispatcher');
  $event_dispatcher->dispatch(new Event(), 'mentions.delete');
}

/**
 * Insert mentions into DB.
 */
function mentions_crud_update(string $type, array $mentions, int $id, int $author): void {
  $mentions_storage = \Drupal::entityTypeManager()->getStorage('mentions');

  // Build old array of mentions.
  $old_users = [];
  $old_mids = [];
  $mention_ids = \Drupal::entityQuery('mentions')
    ->condition('entity_type', $type)
    ->condition('entity_id', $id)
    ->accessCheck()
    ->execute();
  foreach ($mentions_storage->loadMultiple($mention_ids) as $entity) {
    // Make sure the uid value is available.
    if (isset($entity->get('uid')->getValue()[0]['value'])) {
      $old_user = $entity->get('uid')->getValue()[0]['value'];
      $old_users[] = $old_user;
      $old_mids[$old_user] = $entity->id();
    }
  }

  // Build array of new mentions.
  $new_users = [];
  foreach ($mentions as $mention) {
    if ($mention['target']['entity_type'] == 'profile') {
      $storage = \Drupal::entityTypeManager()
        ->getStorage($mention['target']['entity_type']);
      $profile = $storage->load($mention['target']['entity_id']);
      if ($profile instanceof ProfileInterface) {
        $new_users[] = $profile->getOwnerId();
      }
    }
    else {
      $new_users[] = $mention['target']['entity_id'];
    }
  }

  $event_dispatcher = \Drupal::service('event_dispatcher');

  // Add new mentions.
  foreach (array_diff($new_users, $old_users) as $uid) {
    $mention_attributes = [
      'entity_type' => $type,
      'entity_id' => $id,
      'uid' => $uid,
      'auid' => $author,
    ];
    $mention = $mentions_storage->create($mention_attributes);
    $mention->save();

    $event_dispatcher->dispatch(new Event(), 'mentions.insert');
  }

  // Update existing mentions.
  foreach (array_intersect($new_users, $old_users) as $uid) {
    $entity = $mentions_storage->load($old_mids[$uid]);
    // Entity was deleted elsewhere while we were processing.
    if ($entity === NULL) {
      continue;
    }

    $entity->set('created', \Drupal::time()->getRequestTime());
    $entity->save();
    $event_dispatcher->dispatch(new Event(), 'mentions.update');
  }

  // Delete old mentions.
  foreach (array_diff($old_users, $new_users) as $uid) {
    $entity = $mentions_storage->load($old_mids[$uid]);
    // Entity was deleted elsewhere while we were processing.
    if ($entity === NULL) {
      continue;
    }
    $entity->delete();
    $event_dispatcher->dispatch(new Event(), 'mentions.delete');
  }
}
