<?php

/**
 * @file
 * Activity Creator module file.
 *
 * @todo Move entity_type hooks (insert/update/delete) to services after
 *        Drupal core has event for entities.
 * @see https://www.drupal.org/project/drupal/issues/2551893
 */

use Drupal\activity_creator\ActivityInterface;
use Drupal\activity_creator\Service\ActivityCreatorBatchActivityDeletion;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\message\Entity\Message;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\activity_creator\Entity\Activity;
use Drupal\search_api\Task\TaskInterface;

/**
 * Activity statuses.
 *
 * We disable the constant naming convention because it would be a breaking
 * change for our notification system.
 * @phpcs:disable Drupal.Semantics.ConstantName.ConstConstantStart
 */
const ACTIVITY_STATUS_RECEIVED = 1;
const ACTIVITY_STATUS_SEEN = 2;

/**
 * Returns the allowed values for the activity status field.
 *
 * @return array
 *   Return an array of allowed values for status field.
 */
function activity_creator_field_activity_status_allowed_values(): array {

  $allowed_values = [
    ACTIVITY_STATUS_RECEIVED => 'Received',
    ACTIVITY_STATUS_SEEN => 'Seen',
  ];

  return $allowed_values;
}

/**
 * Returns the allowed values for the activity destinations field.
 *
 * @return array
 *   Return an array of allowed values for destinations field.
 */
function activity_creator_field_activity_destinations_allowed_values(): array {

  $allowed_values = [
    'stream_profile' => 'Stream (profile)',
    'stream_home' => 'Stream (home)',
    'stream_group' => 'Stream (group)',
    'stream_explore' => 'Stream (explore)',
    'notifications' => 'Notifications',
  ];

  return $allowed_values;
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function activity_creator_message_insert(Message $entity) {
  // @todo Move all this logic to services (also in Queue).
  $mid = $entity->id();
  $message_template_id = $entity->getTemplate()->id();
  $actor = $entity->getOwner()->id();

  // Messages that are not part of the activity system may not have the fields
  // we need. Similarly for our activity system we always ensure they are all
  // filled thus if one of field_message_context, field_message_destination or
  // field_message_related_object is empty, don't create activities.
  if (
    !$entity->hasField('field_message_context') ||
    $entity->get('field_message_context')->isEmpty() ||
    !$entity->hasField('field_message_destination') ||
    $entity->get('field_message_destination')->isEmpty() ||
    !$entity->hasField('field_message_related_object') ||
    $entity->get('field_message_related_object')->isEmpty()
  ) {
    return;
  }

  $context = $entity->get('field_message_context')->getValue();
  $destinations = $entity->get('field_message_destination')->getValue();
  $related_object = $entity->get('field_message_related_object')->getValue();

  $data = [
    'mid' => $mid,
    'message_template' => $message_template_id,
    'actor' => $actor,
    'context' => $context[0]['value'],
    'destination' => $destinations,
    'related_object' => $related_object,
    'last_uid' => 0,
    'status' => NULL,
  ];

  $message_template = $entity->getTemplate();

  $activity_create_direct = $message_template->getThirdPartySetting('activity_logger', 'activity_create_direct', NULL);

  if ($activity_create_direct) {

    $context_plugin_manager = \Drupal::service('plugin.manager.activity_context.processor');

    if ($context_plugin_manager->hasDefinition($data['context'])) {
      /** @var \Drupal\activity_creator\Plugin\ActivityContextBase $plugin */
      $plugin = $context_plugin_manager->createInstance($data['context']);
      $recipients = $plugin->getRecipients($data, $data['last_uid'], 0);
      $user_recipients = [];
      $activity_factory = \Drupal::service('activity_creator.activity_factory');
      if (!empty($recipients)) {
        // Default activity creator template.
        $activity_creator_data = [
          'mid' => $data['mid'],
          'message_template' => $data['message_template'],
          'actor' => $data['actor'],
          'context' => $data['context'],
          'destination' => $data['destination'],
          'related_object' => $data['related_object'],
        ];

        // Get all the activity recipient types. Maintain target IDs as key.
        $activity_by_type = array_column($recipients, 'target_type');
        foreach ($activity_by_type as $recipients_key => $target_type) {
          // For all one to one target entity types we create an activity.
          if ($target_type !== 'user') {
            $activity_creator_data['recipient'] = $recipients[$recipients_key];
            $activity_factory->createActivities($activity_creator_data);
          }

          if ($target_type === 'user') {
            $user_recipients[] = $recipients[$recipients_key];
          }
        }

        // When the activity should be created for a one to many user entity
        // we like to group these.
        if (!empty($user_recipients)) {
          $activity_creator_data['recipient'] = $user_recipients;
          $activity_factory->createActivities($activity_creator_data);
        }
      }
      else {
        $activity_creator_data = [
          'mid' => $data['mid'],
          'message_template' => $data['message_template'],
          'actor' => $data['actor'],
          'context' => $data['context'],
          'destination' => $data['destination'],
          'related_object' => $data['related_object'],
        ];
        $activity_factory->createActivities($activity_creator_data);
      }
    }
  }
  else {
    $queue = \Drupal::queue('activity_creator_logger');
    $queue->createItem($data);
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function activity_creator_activity_insert(ActivityInterface $activity) {
  // We only care about status for activities when we're dealing with
  // notifications.
  if (in_array('notifications', $activity->getDestinations(), TRUE)) {
    // Get recipients.
    $recipients = $activity->getRecipient();

    if (is_array($recipients)) {
      $uids = [];

      // Loop through the recipients and track the ID if the target is a user.
      foreach ($recipients as $recipient) {
        if ($recipient['target_type'] === 'user') {
          $uids[] = $recipient['target_id'];
        }
      }

      if (!empty($uids)) {
        // If we have users, we insert a status for each user for the activity.
        $connection = \Drupal::database();
        $query = $connection->insert('activity_notification_status')->fields(['uid', 'aid']);
        foreach ($uids as $uid) {
          $query->values([
            'uid' => $uid,
            'aid' => $activity->id(),
          ]);
        }
        $query->execute();
      }
    }
  }
}

/**
 * Implements hook_views_data().
 */
function activity_creator_views_data() {
  $data['activity']['activity_visibility_access_filter'] = [
    'title' => t('Activity visibility access'),
    'filter' => [
      'title' => t('Activity visibility access'),
      'help' => t('Enable activity access control.'),
      'field' => 'field_visibility',
      'id' => 'activity_visibility_access',
    ],
  ];
  $data['activity']['activity_post_visibility_access_filter'] = [
    'title' => t('Activity post visibility access'),
    'filter' => [
      'title' => t('Activity post visibility access'),
      'help' => t('Enable activity access control for related post entities.'),
      'field' => 'field_visibility',
      'id' => 'activity_post_visibility_access',
    ],
  ];
  $data['activity']['activity_notification_visibility_access_filter'] = [
    'title' => t('Activity notification visibility access'),
    'filter' => [
      'title' => t('Activity notification visibility access'),
      'help' => t('Enable activity access control for notifications.'),
      'field' => 'field_visibility',
      'id' => 'activity_notification_visibility_access',
    ],
  ];
  $data['activity']['activity_profile_argument'] = [
    'title' => t('Activity profile argument'),
    'argument' => [
      'title' => t('Activity profile argument'),
      'help' => t('Activity profile context argument.'),
      'id' => 'activity_profile_argument',
    ],
  ];
  $data['activity']['activity_group_argument'] = [
    'title' => t('Activity group argument'),
    'argument' => [
      'title' => t('Activity group argument'),
      'help' => t('Activity group context argument.'),
      'id' => 'activity_group_argument',
    ],
  ];
  return $data;
}

/**
 * Implements hook_theme().
 */
function activity_creator_theme() {
  $items = [
    'activity' => [
      'render element' => 'elements',
      'file' => 'activity.page.inc',
      'template' => 'activity',
    ],
  ];
  return $items;
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function activity_creator_theme_suggestions_activity(array $variables) {
  $suggestions = [];
  $activity = $variables['elements']['#activity'];
  $sanitized_view_mode = str_replace('.', '_', $variables['elements']['#view_mode']);

  $suggestions[] = 'activity__' . $sanitized_view_mode;
  $suggestions[] = 'activity__' . $activity->id();
  $suggestions[] = 'activity__' . $activity->id() . '__' . $sanitized_view_mode;

  return $suggestions;
}

/**
 * Implements hook_entity_view().
 *
 * Mark notifications as read.
 */
function activity_creator_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  // Generate the output text for the user in it's selected language.
  if ($entity instanceof Activity && !empty($entity->get('field_activity_message')->target_id)) {
    $message = Message::load($entity->get('field_activity_message')->target_id);
    if ($message instanceof Message) {
      // Get the text in the users language.
      $output = \Drupal::service('activity_creator.activity_factory')->getMessageText($message, \Drupal::languageManager()->getCurrentLanguage()->getId());
      // Replace the old text with the correct one.
      $build['field_activity_output_text'][0]['#text'] = $output[0];
    }
  }
}

/**
 * Implements hook_entity_update().
 *
 * Clear cache tags for related activities.
 */
function activity_creator_entity_update(EntityInterface $entity) {
  _activity_creator_activity_for_entity_updater('update', $entity);
}

/**
 * Implements hook_entity_delete().
 *
 * Delete activity items when related entities are deleted.
 */
function activity_creator_entity_delete(EntityInterface $entity) {
  _activity_creator_activity_for_entity_updater('delete', $entity);
}

/**
 * Update and or delete activities for given entity.
 *
 * @param string $action
 *   A string containing the action. Can be delete or update.
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity object which is updated or deleted.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _activity_creator_activity_for_entity_updater($action, EntityInterface $entity) {
  if ($entity !== NULL) {
    // We never create activities for configuration entities but accessing
    // entity queries during configuration changes may cause errors.
    if ($entity instanceof ConfigEntityInterface) {
      return;
    }

    // Ignore Task entity from search api module.
    if (\Drupal::moduleHandler()->moduleExists('search_api') && $entity instanceof TaskInterface) {
      return;
    }

    /** @var \Drupal\activity_creator\ActivityNotifications $activity_notification_service */
    $activity_notification_service = \Drupal::service('activity_creator.activity_notifications');
    $ids = $activity_notification_service->getActivityIdsByEntity($entity);
    if (empty($ids)) {
      return;
    }

    if ($action === 'update') {
      $tags = [];
      foreach ($ids as $id) {
        $tags[] = 'activity:' . $id;
      }
      Cache::invalidateTags($tags);
    }
    elseif ($action === 'delete') {
      // Delete all activities.
      ActivityCreatorBatchActivityDeletion::bulkDeleteActivities($ids);
    }
  }
}

/**
 * Implements hook_cron().
 */
function activity_creator_cron() {
  // Empty activity stream queues.
  activity_creator_empty_queue();
}

/**
 * Function that empties the activity queues.
 */
function activity_creator_empty_queue() {
  // Activity queues.
  $queues = [
    'activity_creator_activities',
    'activity_creator_logger',
    'activity_logger_message',
    'activity_send_email_worker',
  ];

  // The workermanager.
  $workerManager = \Drupal::service('plugin.manager.queue_worker');

  /** @var Drupal\Core\Queue\QueueFactory $queue */
  $queue = \Drupal::service('queue');

  for ($i = 0; $i < 5; $i++) {
    foreach ($workerManager->getDefinitions() as $name => $info) {
      if (in_array($name, $queues)) {
        /** @var Drupal\Core\Queue\QueueInterface $worker */
        $worker = $queue->get($name);
        /** @var \Drupal\Core\Queue\QueueWorkerInterface $queue_worker */
        $queue_worker = $workerManager->createInstance($name);
        // Must contain items.
        if ($worker->numberOfItems() > 0) {
          while ($item = $worker->claimItem()) {
            if (is_object($item) && !empty($item->data)) {
              $queue_worker->processItem($item->data);
              $worker->deleteItem($item);
            }
          }
        }
      }
    }
  }
}

/**
 * Set the view mode for the activity entity reference for an entity type.
 *
 * Provides no validation on the validity of the entity type id or the view mode
 * id.
 *
 * @param string $entity_type_id
 *   The entity type for which to change the view mode.
 * @param string $view_mode_id
 *   The view mode to use in the activity entity reference field.
 */
function activity_creator_set_entity_view_mode(string $entity_type_id, string $view_mode_id) {
  /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $activity_display */
  $activity_display = \Drupal::service('entity_type.manager')
    ->getStorage('entity_view_display')
    ->load('activity.activity.default');
  $field = $activity_display->getComponent('field_activity_entity');

  $field['settings'][$entity_type_id]['view_mode'] = $view_mode_id;

  $activity_display
    ->setComponent('field_activity_entity', $field)
    ->save();
}
