<?php

/**
 * @file
 * The Social Privagte Message module.
 */

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\private_message\Entity\PrivateMessageInterface;
use Drupal\private_message\Entity\PrivateMessageThreadInterface;
use Drupal\social_private_message\DeletedUser;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewExecutable;

/**
 * Implements hook_element_info_alter().
 */
function social_private_message_element_info_alter(array &$info) {
  if (isset($info['text_format']['#process'])) {
    $info['text_format']['#process'][] = 'social_private_message_filter_process_format';
  }
}

/**
 * Remove ability of selecting format on private message (use plain_text only).
 */
function social_private_message_filter_process_format($element) {
  // Fields listed here will have plain_text format only.
  $plain_text_fields = [
    'edit-message-0',
  ];

  if ($element['#type'] == 'text_format' && in_array($element['#id'], $plain_text_fields)) {
    $element['#format'] = 'plain_text';
    $element['format']['format']['#access'] = FALSE;
    $element['format']['format']['#value'] = 'plain_text';
    $element['format']['help']['#access'] = FALSE;
    $element['format']['format']['#options'] = ['plain_text' => 'Plain Text'];
  }

  return $element;
}

/**
 * Implements hook_form_FORMID_alter().
 */
function social_private_message_form_private_message_thread_delete_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Redirect cancel to our inbox.
  $form['actions']['cancel']['#url'] = Url::fromRoute('social_private_message.inbox');
  // Redirect delete to our inbox.
  $form['actions']['submit']['#submit'][] = 'social_private_message_thread_delete_redirect';
}

/**
 * Implements hook_form_FORMID_alter().
 */
function social_private_message_form_private_message_add_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $form['members']['widget']['target_id']['#description'] = t('The member(s) of the private message thread. Add multiple members by seperating them with a comma.');

  // Add CTRL/CMD + Enter is submit.
  $form['#attached']['library'][] = 'social_post/keycode-submit';

  $form['#attached']['library'][] = 'social_private_message/validator';
  $form['#attached']['drupalSettings']['social_private_message']['validator'] = t('Please select an item in the list.');

  // Determine if the form is an PMT edit form.
  $form_is_edit_form = $form_state->has('thread_members');

  // There's an alternative submit. We need to change its value.
  foreach ($form['actions'] as $key => &$actions) {
    // Must be pretty sure it's a submit button.
    if (!(is_array($actions) && isset($actions['#submit']) && is_array($actions['#submit']))) {
      continue;
    }

    $actions['#value'] = t('Send');
    $actions['#submit'][] = 'social_private_message_redirect';
    unset($actions['#ajax']);

    if (!($form_is_edit_form && \Drupal::routeMatch()->getRouteName() == 'entity.private_message_thread.canonical' && \Drupal::routeMatch()->getParameter('private_message_thread')->get('members')->count() != count($form_state->get('thread_members')))) {
      continue;
    }

    $callbacks = $actions['#submit'];
    $actions['#submit'] = [];

    foreach ($callbacks as $callback) {
      if ($callback == '::save') {
        $actions['#submit'][] = '_social_private_message_members';
      }

      $actions['#submit'][] = $callback;
    }
  }

  // Alter the users widget.
  $form['members']['widget']['#required'] = TRUE;
  $form['members']['widget']['#description'] = '';
  // Add the required tag.
  $form['members']['widget']['#attributes']['required'] = 'required';
  // Unset the empty and anonymous option.
  unset($form['members']['widget']['#options']['0']);
  unset($form['members']['widget']['#options']['_none']);
  // Unset current user.
  unset($form['members']['widget']['#options'][Drupal::currentUser()->id()]);

  // Alter the message widget.
  // Add the required tag.
  $form['message']['widget']['#attributes']['required'] = 'required';
  // Normal submit button shoud say 'Next'.
  $form['actions']['submit']['#value'] = t('Send');
  $form['actions']['submit']['#submit'][] = 'social_private_message_redirect';

  // Add form class based on form type (create/edit).
  if ($form_is_edit_form) {
    $form_class = 'message__thread_create';

    // In edit mode, remove the label from the message field.
    $form['message']['widget'][0]['#title'] = '';
  }
  else {
    $form_class = 'message__thread_edit';
  }

  $form['#attributes']['class'][] = $form_class;
}

/**
 * Redirects the form to the inbox.
 *
 * @param array $form
 *   The form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form_state.
 */
function social_private_message_thread_delete_redirect(array $form, FormStateInterface $form_state) {
  // Set a nice message.
  \Drupal::messenger()->addStatus(t('Your message has been deleted.'));
  // Force redirect to the inbox.
  $url = Url::fromRoute('social_private_message.inbox');
  $form_state->setRedirectUrl($url);

  // Unset cache tags for user profiles.
  $build_info = $form_state->getBuildInfo();
  if (isset($build_info['callback_object'])) {
    /** @var \Drupal\private_message\Form\PrivateMessageThreadDeleteForm $pm_thread_form */
    $pm_thread_form = $build_info['callback_object'];
    $pm_thread = $pm_thread_form->getEntity();

    $members = $pm_thread->getMembers();

    $cache_tags = [];
    foreach ($members as $member) {
      foreach ($member->getCacheTagsToInvalidate() as $cache_tag) {
        $cache_tags[] = $cache_tag;
      }
    }

    // Invalidate the user's cache tags is not enough to clear the Inbox page
    // cache, add private_message_thread_list tag additionally.
    // @todo Remove this tag after found solution to keep working with the
    //   user cache only.
    $cache_tags[] = 'private_message_thread_list';

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

}

/**
 * Redirects the form to the inbox.
 *
 * @param array $form
 *   The form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form_state.
 */
function social_private_message_redirect(array $form, FormStateInterface $form_state) {
  // Set a nice message.
  \Drupal::messenger()->addStatus(t('Your message has been created.'));
  // Force redirect to the inbox.
  $url = Url::fromRoute('social_private_message.inbox');
  $form_state->setRedirectUrl($url);
}

/**
 * Add deleted users to list of thread members.
 *
 * @param array $form
 *   The form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form_state.
 */
function _social_private_message_members(array $form, FormStateInterface $form_state) {
  $only_exist_members = $form_state->get('thread_members');

  $members_with_deleted = \Drupal::routeMatch()->getParameter('private_message_thread')
    ->get('members')
    ->getValue();

  $thread_members = [];

  foreach ($members_with_deleted as $value) {
    $found = FALSE;

    /** @var \Drupal\user\UserInterface $member */
    foreach ($only_exist_members as $member) {
      if ($value['target_id'] == $member->id()) {
        $found = TRUE;
        break;
      }
    }

    if ($found) {
      $thread_members[] = $member;
    }
    else {
      $thread_members[] = new DeletedUser($value['target_id']);
    }
  }

  $form_state->set('thread_members', $thread_members);
}

/**
 * Implements hook_js_alter().
 */
function social_private_message_js_alter(&$javascript, AttachedAssetsInterface $assets) {
  // Remove Js coming from the private_message module.
  if (isset($javascript['modules/contrib/private_message/js/private_message_inbox_block.js'])) {
    unset($javascript['modules/contrib/private_message/js/private_message_inbox_block.js']);
  }
}

/**
 * Implements hook_entity_view_alter().
 */
function social_private_message_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  // Threads.
  if ($entity->getEntityTypeId() === 'private_message_thread') {
    /** @var \Drupal\private_message\Entity\PrivateMessageThread $thread */
    $thread = &$entity;

    $only_exist_members = $thread->getMembers();

    // View mode inbox.
    if ($build['#view_mode'] === 'inbox') {
      $members_with_deleted = $thread->get('members')->getValue();
      $members_string = [];
      $display_name = '';

      foreach (array_keys($members_with_deleted) as $key) {
        if (isset($only_exist_members[$key])) {
          /** @var \Drupal\user\UserInterface $member */
          $member = $only_exist_members[$key];

          if (\Drupal::currentUser()->id() === $member->id()) {
            unset($members_with_deleted[$key], $only_exist_members[$key]);
          }
          else {
            $user_profile = \Drupal::entityTypeManager()->getStorage('profile')->loadByUser($member, 'profile');
            $content = \Drupal::entityTypeManager()
              ->getViewBuilder('profile')
              ->view($user_profile, 'name_raw');
            $members_string[] = $content;
          }
        }
        else {
          $members_string[] = t('Deleted user');
        }
      }
      // Count the amount of members.
      $member_count = count($members_with_deleted);

      $profile_picture = [];

      if ($member_count === 1) {
        $recipient = end($only_exist_members);

        if ($recipient instanceof UserInterface) {
          $user_profile = \Drupal::entityTypeManager()->getStorage('profile')->loadByUser($recipient, 'profile');
          $display_name = \Drupal::entityTypeManager()->getViewBuilder('profile')->view($user_profile, 'name_raw');
        }
        else {
          // Make this an array, since we no longer render in here.
          $display_name = [
            '#markup' => t('Deleted user'),
          ];
          $recipient = User::load(1);
        }

        // Load compact notification view mode of the attached profile.
        if ($recipient instanceof User) {
          $storage = \Drupal::entityTypeManager()->getStorage('profile');
          $user_profile = $storage->loadByUser($recipient, 'profile');
          if ($user_profile !== FALSE) {
            $content = \Drupal::entityTypeManager()
              ->getViewBuilder('profile')
              ->view($user_profile, 'compact_notification');
            // Add to a new field, so twig can render it.
            $profile_picture = $content;
          }
        }
      }

      // Add either the profile picture or the group picture.
      if ($member_count > 1) {
        $build['members']['#markup'] = '<div class="avatar-icon avatar-group-icon avatar-group-icon--medium"></div>';
        // Add members names.
        $build['membernames'] = $members_string;
        $build['membernames']['#prefix'] = '<strong>';
        $build['membernames']['#suffix'] = '</strong>';
      }

      elseif (!empty($profile_picture && $display_name)) {
        $build['members'] = $profile_picture;
        // Add members name.
        $build['membernames'] = $display_name;
        $build['membernames']['#prefix'] = '<strong>';
        $build['membernames']['#suffix'] = '</strong>';
      }
    }
    elseif ($build['#view_mode'] === 'full') {
      $socialPrivateMessageService = \Drupal::service('social_private_message.service');
      $socialPrivateMessageService->updateLastThreadCheckTime($entity);
      $build['#prefix'] = '';
      $build['#suffix'] = '';

      if ($display->getComponent('private_message_form') && count($only_exist_members) == 1) {
        $build['private_message_form'] = NULL;
      }
    }
  }

  // Private message entity.
  elseif ($entity->getEntityTypeId() === 'private_message') {
    // Default view mode.
    if ($build['#view_mode'] === 'default') {
      /** @var \Drupal\private_message\Entity\PrivateMessage $entity */
      if (\Drupal::currentUser()->id() === $entity->getOwnerId()) {
        // Current user is 'You'.
        $build['owner'][0]['#plain_text'] = t('You');
      }
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_view_alter() for private_message_thread.
 *
 * @todo This hook is actually misnamed but is still fired because we also have
 * an install profile called `social`.
 *
 * @see hook_entity_view()
 */
function social_private_message_thread_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  assert($entity instanceof PrivateMessageThreadInterface);

  $current_user = Drupal::currentUser();
  if (
    $display->getComponent('delete_link') &&
    $current_user->hasPermission('use private messaging system') &&
    $current_user->hasPermission('delete private messages thread') &&
    $entity->isMember($current_user->id())
  ) {
    $url = Url::fromRoute('entity.private_message_thread.delete_form', ['private_message_thread' => $entity->id()]);
    $build['delete_link'] = [
      '#prefix' => '',
      '#suffix' => '',
      '#type' => 'link',
      '#url' => $url,
      '#title' => t('Delete thread'),
    ];
  }
  else {
    unset($build['delete_link']);
  }

  if (
    !$current_user->hasPermission('use private messaging system') ||
    !$current_user->hasPermission('reply to private messages thread')
  ) {
    unset($build['private_message_form']);
  }
  // Also add the back to inbox link
  // but just the link since it's a drop down with icon.
  $build['back_to_inbox']['#markup'] = Url::fromRoute('social_private_message.inbox')->toString();
}

/**
 * Implements hook_preprocess_field().
 */
function social_private_message_preprocess_field(&$variables) {
  if (strpos($variables['entity_type'], 'private_message') === FALSE) {
    return;
  }

  $element = &$variables['element'];

  if (isset($element['#view_mode']) && $element['#view_mode'] == 'default' && $variables['field_name'] == 'message') {
    $variables['view_mode'] = 'full';

    if (empty($element['#object']->getOwner())) {
      foreach ($variables['items'] as &$item) {
        $item['content'] = t("This message was deleted together with the user’s account.");
      }
    }
  }
}

/**
 * Implements hook_user_cancel().
 */
function social_private_message_user_cancel($edit, $account, $method) {
  if ($method == 'user_cancel_reassign') {
    \Drupal::database()->update('private_messages')
      ->fields(['message__value' => ' '])
      ->condition('owner', $account->id())
      ->execute();
  }
}

/**
 * Implements hook_menu_local_actions_alter().
 */
function social_private_message_menu_local_actions_alter(&$local_actions) {
  unset($local_actions['private_message.private_message_add']);
}

/**
 * When the user creates a message, mark this thread as read for the author.
 *
 * Implements hook_ENTITY_TYPE_insert().
 */
function social_private_message_private_message_thread_insert(EntityInterface $entity) {
  $socialPrivateMessageService = Drupal::service('social_private_message.service');
  $socialPrivateMessageService->updateLastThreadCheckTime($entity);
}

/**
 * Implements hook_ENTITY_TYPE_update().
 */
function social_private_message_pm_thread_delete_time_update(EntityInterface $entity) {
  $socialPrivateMessageService = Drupal::service('social_private_message.service');
  $socialPrivateMessageService->deleteUserDataThreadInfo($entity);
}

/**
 * Implements hook_user_cancel_methods_alter().
 */
function social_private_message_user_cancel_methods_alter(&$methods) {
  $methods['user_cancel_reassign']['title'] = t('Delete your account, delete private messages, and anonymize all other content.');
}

/**
 * Implements hook_activity_send_email_notifications_alter().
 */
function social_private_message_activity_send_email_notifications_alter(array &$items, array $email_message_templates) {
  // If our create_private_message template is enabled for email then we add it
  // to the "Message to Me" section.
  if (isset($email_message_templates['create_private_message'])) {
    $items['message_to_me']['templates'][] = 'create_private_message';
  }
}

/**
 * Implements hook_social_user_account_header_items().
 *
 * Adds the Private Message button and indicator to the account header block if
 * it's enabled by the site manager.
 */
function social_private_message_social_user_account_header_items(array $context) {
  if (\Drupal::config('social_user.navigation.settings')->get('display_social_private_message_icon') !== TRUE) {
    return [];
  }

  // We require a logged in user for this button.
  if (empty($context['user']) || !$context['user']->isAuthenticated() || !$context['user']->hasPermission('use private messaging system')) {
    return [];
  }

  // Fetch the amount of unread items.
  $num_account_messages = \Drupal::service('social_private_message.service')->updateUnreadCount();

  return [
    'messages' => [
      '#type' => 'account_header_element',
      '#wrapper_attributes' => [
        'class' => ['desktop'],
      ],
      '#title' => new TranslatableMarkup('Inbox'),
      '#url' => Url::fromRoute('social_private_message.inbox'),
      '#icon' => $num_account_messages > 0 ? 'mail' : 'mail_outline',
      '#label' => new TranslatableMarkup('Inbox'),
      '#notification_count' => $num_account_messages,
    ],
  ];
}

/**
 * Implements hook_social_user_account_header_account_links().
 *
 * Adds the mobile indicator for private messages under the profile icon menu.
 */
function social_private_message_social_user_account_header_account_links(array $context) {
  if (\Drupal::config('social_user.navigation.settings')->get('display_social_private_message_icon') !== TRUE) {
    return [];
  }

  // We require a logged in user for this indicator.
  if (empty($context['user']) || !$context['user']->isAuthenticated()) {
    return [];
  }

  // Fetch the amount of unread items.
  $num_account_messages = \Drupal::service('social_private_message.service')->updateUnreadCount();

  // Default icon values.
  $label_classes = 'hidden';
  // Override icons when there are unread items.
  if ($num_account_messages > 0) {
    $label_classes = 'badge badge-accent badge--pill';
  }

  $title_link = [
    '#type' => 'inline_template',
    '#template' => '<span>{% trans %}Inbox{% endtrans %}</span><span{{ attributes }}>{{ icon }}</span>',
    '#context' => [
      'attributes' => new Attribute(['class' => $label_classes]),
      'icon' => (string) $num_account_messages,
    ],
  ];

  $link = Link::createFromRoute($title_link, 'social_private_message.inbox');

  return [
    'messages_mobile' => [
      '#type' => 'link',
      '#wrapper_attributes' => [
        'class' => ['mobile'],
      ],
      '#weight' => 200,
      '#attributes' => [
        'title' => new TranslatableMarkup('Inbox'),
      ],
      '#access' => $link->getUrl()->access(),
    ] + $link->toRenderable(),
  ];

}

/**
 * Implements hook_social_user_account_header_items().
 *
 * Adds an indicator to the user account menu on mobile.
 */
function social_private_message_social_user_account_header_items_alter(array &$menu_links, array $context) {
  if (\Drupal::config('social_user.navigation.settings')->get('display_social_private_message_icon') !== TRUE) {
    return;
  }

  // We require a logged in user for this indicator.
  if (empty($context['user']) || !$context['user']->isAuthenticated()) {
    return;
  }

  // If the account_box link was removed we have nothing to do.
  if (!isset($menu_links['account_box'])) {
    return;
  }

  // Fetch the amount of unread items.
  $num_account_messages = \Drupal::service('social_private_message.service')->updateUnreadCount();

  if ($num_account_messages > 0) {
    $menu_links['account_box']['#wrapper_attributes']['class'][] = 'has-alert';
  }
}

/**
 * Implements hook_views_data_alter().
 */
function social_private_message_views_data_alter(array &$data) {
  $data['private_message_threads']['social_private_message_deleted_threads_filter'] = [
    'title' => t('Filter deleted threads'),
    'filter' => [
      'title' => t('Filter deleted threads'),
      'help' => t('Do not show threads a user deleted.'),
      'field' => 'id',
      'id' => 'social_private_message_deleted_threads',
    ],
  ];

}

/**
 * Implements hook_views_query_alter().
 */
function social_private_message_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  if ($view->id() == 'inbox') {
    // Current user.
    $current_user_id = \Drupal::currentUser()->id();
    // Add join definition.
    $definition = [
      'table' => 'pm_thread_history',
      'field' => 'thread_id',
      'left_table' => 'private_message_threads',
      'left_field' => 'id',
      'operator' => '=',
      'extra' => 'pm_thread_history.delete_timestamp <= private_message_threads.updated',
    ];
    // Create a join statement from plugin.
    $join = Drupal::service('plugin.manager.views.join')->createInstance('standard', $definition);
    // Add join to the query.
    $query->addRelationship('pm_thread_history', $join, 'delete_timestamp');
    // Add some extra where statements.
    $query->addWhere(NULL, 'pm_thread_history.uid', $current_user_id);
  }
}

/**
 * Implements hook_private_message_thread_access().
 */
function social_private_message_private_message_thread_access(PrivateMessageThreadInterface $entity, $operation, AccountInterface $account) {
  if ($operation === 'delete') {
    if (
      $account->hasPermission('use private messaging system') &&
      $account->hasPermission('delete private messages thread') &&
      $entity->isMember($account->id())
    ) {
      return AccessResult::allowed();
    }

    return AccessResult::forbidden();
  }
}

/**
 * Implements hook_private_message_create_access().
 */
function social_private_message_private_message_create_access(AccountInterface $account, array $context, $entity_bundle) {
  return AccessResult::forbiddenIf(!($account->hasPermission('use private messaging system') && $account->hasPermission('create private messages thread')));
}

/**
 * Implements hook_private_message_view_alter().
 */
function social_private_message_private_message_view_alter(array &$build, EntityInterface $privateMessage, $viewMode): void {
  // Inbox view mode.
  if ($viewMode === 'inbox') {
    // Remove wrapper from private message.
    $build['wrapper'] = $build['wrapper']['message'];
  }
  // Default view mode.
  elseif ($viewMode === 'default') {
    // Replace class "private-message-default" by "private-message-full".
    $class_key = array_search('private-message-default', $build['wrapper']['#attributes']['class']);
    if ($class_key !== NULL) {
      $build['wrapper']['#attributes']['class'][$class_key] = 'private-message-full';
    }

    // Add a wrapper for a message if it is a current user message.
    $wrapper_class = NULL;

    /** @var \Drupal\private_message\Entity\PrivateMessage $privateMessage */
    if (\Drupal::currentUser()->id() === $privateMessage->getOwnerId()) {
      $wrapper_class = 'message__by-me';
    }
    elseif (empty($privateMessage->getOwner())) {
      $wrapper_class = 'message__deleted';
    }

    if (!empty($wrapper_class)) {
      $build['#prefix'] = '<div class="' . $wrapper_class . '">';
      $build['#suffix'] = '</div>';
    }
  }
}

/**
 * Implements hook_private_message_notify_exclude().
 */
function social_private_message_private_message_notify_exclude(PrivateMessageInterface $privateMessage, PrivateMessageThreadInterface $thread, array &$exclude): void {
  // Get all members of this thread.
  /** @var \Drupal\private_message\Entity\PrivateMessageThreadInterface $members */
  $members = $thread->getMembers();

  /** @var \Drupal\user\UserInterface $member */
  foreach ($members as $member) {
    if (
      $member instanceof UserInterface &&
      !$member->hasPermission('use private messaging system')
    ) {
      $exclude[] = $member->id();
    }
  }
}
