<?php

/**
 * @file
 * Contains social_follow_taxonomy.module.
 */

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Link;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flag\Entity\Flag;
use Drupal\flag\FlagInterface;
use Drupal\message\Entity\Message;
use Drupal\message\MessageInterface;
use Drupal\node\NodeInterface;
use Drupal\social_post\Entity\PostInterface;
use Drupal\taxonomy\TermInterface;

/**
 * Implements hook_theme().
 */
function social_follow_taxonomy_theme($existing, $type, $theme, $path) {
  return [
    'flag__follow_term' => [
      'base hook' => 'flag',
    ],
    'activity__followed' => [
      'base hook' => 'activity',
    ],
    'activity__node__followed' => [
      'base hook' => 'activity',
    ],
  ];
}

/**
 * Implements hook_social_user_account_header_account_links().
 *
 * Adds the "Following tags" link to the user menu.
 */
function social_follow_taxonomy_social_user_account_header_account_links(array $context) {
  $link = Link::createFromRoute((new TranslatableMarkup('Following tags')), 'view.following_tags.following_tags');

  return [
    'my_tags' => [
      '#type' => 'link',
      '#attributes' => [
        'title' => new TranslatableMarkup("View tags I'm following"),
      ],
      '#weight' => 1001,
      '#access' => $link->getUrl()->access(),
    ] + $link->toRenderable(),
  ];
}

/**
 * Implements hook_page_attachments().
 */
function social_follow_taxonomy_page_attachments(array &$page) {
  $page['#attached']['library'][] = 'social_follow_taxonomy/social_follow_taxonomy';
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 *
 * Add new activity stream message template for nodes and posts with a term
 * followed by the user to make it possible to override the default template.
 */
function social_follow_taxonomy_theme_suggestions_activity_alter(
  array &$suggestions,
  array $variables
): void {
  $activity = $variables['elements']['#activity'];

  if (
    !isset(
      $activity->view,
      $activity->field_activity_entity,
      $activity->field_activity_message
    ) ||
    $activity->view->current_display !== 'block_stream_homepage'
  ) {
    return;
  }

  $entity_type = $activity->field_activity_entity->first()->target_type;

  if ($entity_type !== 'node' && $entity_type !== 'post') {
    return;
  }

  // Get a message entity from the activity.
  $message = \Drupal::entityTypeManager()->getStorage('message')
    ->load($activity->field_activity_message->target_id);

  if (!$message instanceof MessageInterface) {
    return;
  }

  $message_template = [
    'create_discussion',
    'create_event_community',
    'create_topic_community',
    'create_discussion_group',
    'create_event_group',
    'create_idea_group',
    'create_topic_group',
    'update_node_following_tag',
    'create_post_group',
    'update_post_following_tag',
  ];

  // Get the name of the template from the data of the message entity.
  if (in_array($message->getTemplate()->id(), $message_template)) {
    $suggestions[] = 'activity__followed';
    $suggestions[] = 'activity__' . $entity_type . '__followed';
  }
}

/**
 * Extends variables for activity template.
 *
 * Implements hook_preprocess_activity().
 * {@inheritdoc}
 */
function social_follow_taxonomy_preprocess_activity(&$variables) {
  $activity = $variables['elements']['#activity'];
  $message_template = [
    'create_discussion',
    'create_event_community',
    'create_topic_community',
    'create_discussion_group',
    'create_event_group',
    'create_idea_group',
    'create_topic_group',
    'update_node_following_tag',
    'create_post_group',
    'update_post_following_tag',
  ];

  $message = Message::load($activity->field_activity_message->target_id);
  // Return early if message is empty.
  if (empty($message)) {
    return;
  }
  $message_template_id = $message->getTemplate()->id();

  if (in_array($message_template_id, $message_template)) {
    $entity = $activity->getRelatedEntity();
    // Checking if entity exist to avoid errors.
    if (empty($entity)) {
      return;
    }

    if ($entity->getEntityTypeId() === 'node') {
      $storage = \Drupal::entityTypeManager()->getStorage('flagging');

      if ($entity instanceof NodeInterface) {
        $term_ids = social_follow_taxonomy_terms_list($entity);

        foreach ($term_ids as $term_id) {
          $flag = $storage->loadByProperties([
            'flag_id' => 'follow_term',
            'entity_type' => 'taxonomy_term',
            'entity_id' => $term_id,
            'uid' => \Drupal::currentUser()->id(),
          ]);
          $flag = reset($flag);

          if (!empty($flag)) {
            /** @var \Drupal\taxonomy\TermInterface $term */
            $term = \Drupal::entityTypeManager()
              ->getStorage('taxonomy_term')
              ->load($term_id);
            $variables['content_type'] = $entity->type->entity->label();
            $variables['followed_tags'][$term_id] = [
              'name' => $term->getName(),
              'flag' => social_follow_taxonomy_flag_link($term),
            ];
          }
        }
      }
    }
    if ($entity->getEntityTypeId() === 'post') {
      $storage = \Drupal::entityTypeManager()->getStorage('flagging');

      if ($entity instanceof PostInterface) {
        $term_ids = social_follow_taxonomy_terms_list($entity);

        foreach ($term_ids as $term_id) {
          $flag = $storage->loadByProperties([
            'flag_id' => 'follow_term',
            'entity_type' => 'taxonomy_term',
            'entity_id' => $term_id,
            'uid' => \Drupal::currentUser()->id(),
          ]);
          $flag = reset($flag);

          if (!empty($flag)) {
            /** @var \Drupal\taxonomy\TermInterface $term */
            $term = \Drupal::entityTypeManager()
              ->getStorage('taxonomy_term')
              ->load($term_id);
            $variables['content_type'] = $entity->getEntityType()->getLabel();
            $variables['followed_tags'][$term_id] = [
              'name' => $term->getName(),
              'flag' => social_follow_taxonomy_flag_link($term),
            ];
          }
        }
      }
    }
  }
}

/**
 * Function to check if term is followed.
 *
 * @param \Drupal\taxonomy\TermInterface $term
 *   Term entity.
 *
 * @return bool
 *   Follow result.
 */
function social_follow_taxonomy_term_followed(TermInterface $term) {
  $follow = FALSE;

  if (!\Drupal::currentUser()->isAnonymous()) {
    $flag = Flag::load('follow_term');
    if ($flag instanceof FlagInterface) {
      /** @var \Drupal\flag\FlagService $service */
      $service = \Drupal::service('flag');
      if (!empty($service->getFlagging($flag, $term, \Drupal::currentUser()))) {
        $follow = TRUE;
      }
    }
  }

  return $follow;
}

/**
 * Function for counting the number of followers of the term.
 *
 * @param \Drupal\taxonomy\TermInterface $term
 *   Term entity.
 *
 * @return int
 *   Count of followers.
 */
function social_follow_taxonomy_term_followers_count(TermInterface $term) {
  $count = 0;
  /** @var \Drupal\flag\FlagCountManagerInterface $flag_count_manager */
  $flag_count_manager = \Drupal::service('flag.count');
  $term_followers_count = $flag_count_manager->getEntityFlagCounts($term);

  if (isset($term_followers_count['follow_term'])) {
    $count = $term_followers_count['follow_term'];
  }

  return $count;
}

/**
 * A function that prepares a flag link for a taxonomy term.
 *
 * @param \Drupal\taxonomy\TermInterface $term
 *   Term entity.
 *
 * @return string
 *   Link button to flag/unflag current term.
 */
function social_follow_taxonomy_flag_link(TermInterface $term) {
  $flag_link = '';

  if (!\Drupal::currentUser()->isAnonymous()) {
    $flag_link_service = \Drupal::service('flag.link_builder');
    $flag_link = $flag_link_service->build($term->getEntityTypeId(), $term->id(), 'follow_term');
  }

  return $flag_link;
}

/**
 * Function for counting the number of nodes related to the term.
 *
 * @param \Drupal\taxonomy\TermInterface $term
 *   Term entity.
 * @param string $field_id
 *   Taxonomy term reference field id.
 * @param string $entity_type
 *   Entity type ID.
 *
 * @return int
 *   Count of related nodes.
 */
function social_follow_taxonomy_related_entity_count(TermInterface $term, $field_id, $entity_type = 'node') {
  switch ($entity_type) {
    case 'node':
      $items = \Drupal::entityTypeManager()
        ->getStorage('node')
        ->getQuery()
        ->condition($field_id, $term->id())
        ->addTag('node_access')
        ->addMetaData('base_table', 'node')
        ->addMetaData('op', 'view')
        ->accessCheck(TRUE)
        ->execute();
      break;

    case 'group':
      $items = \Drupal::entityTypeManager()
        ->getStorage('group')
        ->getQuery()
        ->condition($field_id, $term->id())
        ->accessCheck(TRUE)
        ->execute();
      break;

    default:
      break;
  }

  \Drupal::moduleHandler()
    ->alter('social_follow_taxonomy_related_items', $items, $term);

  return count($items);
}

/**
 * Provide an array of terms related to entity.
 *
 * @param Drupal\Core\Entity\EntityInterface $entity
 *   Related entity.
 *
 * @return array
 *   List of term ids.
 */
function social_follow_taxonomy_terms_list(EntityInterface $entity) {
  $term_ids = [];

  \Drupal::moduleHandler()
    ->alter('social_follow_taxonomy_terms_list', $term_ids, $entity);

  return $term_ids;
}

/**
 * Implements hook_activity_send_email_notifications_alter().
 */
function social_follow_tag_activity_send_email_notifications_alter(array &$items, array $email_message_templates) {
  if (isset($email_message_templates['create_node_following_tag'])) {
    $items['what_follow']['templates'][] = 'create_node_following_tag';
  }
  if (isset($email_message_templates['update_node_following_tag'])) {
    $items['what_follow']['templates'][] = 'update_node_following_tag';
  }
}

/**
 * Implements hook_entity_presave().
 */
function social_follow_taxonomy_entity_presave(EntityInterface $entity) {
  _social_follow_taxonomy_invalidate_follow_tag_cache($entity);
}

/**
 * Implements hook_entity_delete().
 */
function social_follow_taxonomy_entity_delete(EntityInterface $entity) {
  _social_follow_taxonomy_invalidate_follow_tag_cache($entity);
}

/**
 * Invalidates cache for added/removed tags to entity.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity.
 */
function _social_follow_taxonomy_invalidate_follow_tag_cache(EntityInterface $entity) {
  if (
    !$entity instanceof ContentEntityInterface ||
    !$entity->hasField('social_tagging')
  ) {
    return;
  }

  if ($entity->isNew()) {
    if ($entity->get('social_tagging')->isEmpty()) {
      return;
    }

    // Added tags.
    $tags = array_column($entity->get('social_tagging')->getValue(), 'target_id');
  }
  else {
    $tags = [];
    if (isset($entity->original)) {
      $old_entity = $entity->original;
      // Tags before save.
      $old_tags = $old_entity->get('social_tagging')->getValue();
      // Tags after save.
      $new_tags = $entity->get('social_tagging')->getValue();
      // Get removed/added tags.
      $tags_removed = array_diff(array_column($old_tags, 'target_id'), array_column($new_tags, 'target_id'));
      $tags_added = array_diff(array_column($new_tags, 'target_id'), array_column($old_tags, 'target_id'));
      if (!empty($tags_removed)) {
        $tags = array_merge($tags, $tags_removed);
      }
      if (!empty($tags_added)) {
        $tags = array_merge($tags, $tags_added);
      }
    }
    else {
      $tags = array_column($entity->get('social_tagging')->getValue(), 'target_id');
    }
  }

  // Get entity type.
  $entity_type = $entity->getEntityTypeId();

  // Invalidate cache for specific tags.
  if (!empty($tags)) {
    foreach ($tags as $tag) {
      $invalidate_tag[] = "follow_tag_$entity_type:$tag";
    }

    \Drupal::service('cache_tags.invalidator')->invalidateTags($invalidate_tag);
  }
}
