<?php

/**
 * @file
 * Contains social_event_managers.module.
 */

use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\social_core\Service\MembersPageHelperService;
use Drupal\social_event\Entity\EventEnrollment;
use Drupal\social_event\EventEnrollmentInterface;
use Drupal\social_event_managers\SocialEventManagersAccessHelper;
use Drupal\user\Entity\User;
use Drupal\views\ViewExecutable;
use Drupal\views_bulk_operations\ViewsBulkOperationsBatch;

/**
 * Implements hook_preprocess_block().
 */
function social_event_managers_preprocess_block(&$variables) {
  /** @var \Drupal\node\Entity\Node $node */
  $node = \Drupal::routeMatch()->getParameter('node');

  // Add variables to sidebar blocks.
  switch ($variables['elements']['#derivative_plugin_id']) {
    case 'managers-event_managers':
      // Show "All" button only when there are more than 10 organisers.
      if ($node->field_event_managers->count() > 10) {
        $variables['view_all_path'] = Url::fromUserInput('/node/' . $node->id() . '/organisers');
        $variables['button_text'] = t('All @label', ['@label' => $variables['label']['#markup']]);
      }
      break;
  }
}

/**
 * Implements hook_views_data_alter().
 */
function social_event_managers_views_data_alter(array &$data) {
  // Create our own views VBO field for enrollments.
  $data['views']['social_views_bulk_operations_bulk_form_enrollments'] = [
    'title' => t('Social Views bulk operations for Enrollments'),
    'help' => t("Process enrollments returned by the view with Views Bulk Operations' actions."),
    'field' => [
      'id' => 'social_views_bulk_operations_bulk_form_enrollments',
    ],
  ];
}

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

/**
 * Implements hook_entity_access().
 */
function social_event_managers_event_enrollment_access(EventEnrollment $event_enrollment, string $operation, AccountInterface $account): AccessResult {
  // This allows view access to event_enrollment entities for users which are
  // the recipients of the event enrollment but not the owner of the entity.
  // For example a site manager can create an enrollment for a specific user.
  if ($operation !== 'view') {
    // If we are doing different operations than viewing, then let other
    // access checks to determine the access.
    return AccessResult::neutral();
  }

  $enrollment_status = $event_enrollment->get('field_enrollment_status')->getString();
  if (!(bool) $enrollment_status) {
    // Return neutral and let other access checks acts on this.
    return AccessResult::neutral();
  }

  $owner = $event_enrollment->getOwner();
  if (!$owner->hasPermission('view published event enrollment entities')) {
    // Return neutral and let other access checks acts on this.
    return AccessResult::neutral();
  }

  $field_account_id = $event_enrollment->get('field_account')->getString();

  // If the user is a recipient allow access to event enrollment.
  return AccessResult::allowedIf((int) $field_account_id === (int) $account->id());
}

/**
 * Implements hook_block_access().
 */
function social_event_managers_block_access(Block $block, $operation, AccountInterface $account) {
  if ($operation == 'view' && $block->getPluginId() == 'views_block:managers-event_managers') {
    // Exclude block form edit node page.
    $route_name = \Drupal::routeMatch()->getRouteName();
    if ($route_name == 'entity.node.edit_form') {
      return AccessResult::forbidden();
    }
  }

  $route_name = \Drupal::routeMatch()->getRouteName();
  $excluded_routes = [
    'social_event_managers.vbo.confirm',
    'social_event_managers.vbo.execute_configurable',
  ];

  if (in_array($route_name, $excluded_routes)) {
    if ($operation === 'view' &&
      ($block->getPluginId() === 'social_page_title_block' ||
      $block->getPluginId() === 'views_block:event_enrollments-event_enrollments')) {
      return AccessResult::forbidden();
    }
  }

  // No opinion for other situations really.
  return AccessResult::neutral();
}

/**
 * Implements hook_link_alter().
 */
function social_event_managers_link_alter(&$variables) {
  /** @var \Drupal\Core\Url $url */
  $url = $variables['url'];

  // Let's make sure we reroute the view more link.
  if ($url instanceof Url && !$url->isExternal() && $url->isRouted() && $url->getRouteName() === 'view.event_enrollments.view_enrollments'
    && stripos(strtolower($variables['text']), 'all enrollments') !== FALSE) {
    $params = $url->getRouteParameters();
    $variables['url'] = Url::fromRoute('view.event_manage_enrollments.page_manage_enrollments', $params);
  }
}

/**
 * Implements hook_entity_access().
 */
function social_event_managers_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
  // Event Manager & Organizers can view/delete/edit enrollments for events
  // they are organizing.
  if ($entity instanceof EventEnrollmentInterface) {
    if (social_event_manager_or_organizer()) {
      return AccessResult::allowedIf($entity instanceof EventEnrollmentInterface);
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function social_event_managers_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Make sure we pass along the ccorrect view id, display id and node
  // parameter to our custom views bulk forms and redirects.
  if (isset($form['views_bulk_operations_bulk_form']) && isset($form['output'][0]['#view'])) {
    $view = &$form['output'][0]['#view'];

    if ($view instanceof ViewExecutable) {
      $view_id = $view->id();
      $display_id = $view->current_display;
    }
  }
  elseif (strpos($form_id, 'views_form_event_manage_enrollments_page_manage_enrollments') !== FALSE && isset($form['output'][0]['#view'])) {
    $view = &$form['output'][0]['#view'];

    if ($view instanceof ViewExecutable) {
      $view_id = $view->id();
      $display_id = $view->current_display;
    }
  }
  elseif (isset($form['social_views_bulk_operations_bulk_form_enrollments_1']) && isset($form['output'][0]['#view'])) {
    $view = &$form['output'][0]['#view'];

    if ($view instanceof ViewExecutable) {
      $view_id = $view->id();
      $display_id = $view->current_display;
    }
  }
  elseif ($form_id === 'views_bulk_operations_configure_action') {
    $data = $form_state->get('views_bulk_operations');
    $view_id = $data['view_id'];
    $display_id = $data['display_id'];
  }

  if (isset($view_id) && $view_id === 'event_manage_enrollments' && $display_id === 'page_manage_enrollments') {
    $form_state->set('node', \Drupal::routeMatch()->getRawParameter('node'));

    if ($form_id === 'views_bulk_operations_configure_action') {
      $callbacks = &$form['actions']['submit']['#submit'];
    }
    else {
      $callbacks = &$form['#submit'];
    }

    $callbacks[] = '_social_event_managers_views_bulk_operations_bulk_form_submit';
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function social_event_managers_form_node_event_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // On event event edit node form we check if users can alter author.
  $node = \Drupal::routeMatch()->getParameter('node');

  // Not on newly created nodes so we check if there is a route match for a node
  // object.
  if ($node) {
    // Get the current user.
    $user = \Drupal::currentUser();

    // Remove authoring information for everybody on node event edit form.
    $form['author']['#access'] = FALSE;

    // Check for permission. Otherwise you can't change the author.
    // Unless you are the author / have the right permissions.
    if ($user->hasPermission('administer nodes') || $user->id() == $node->getOwnerId()) {
      $form['author']['#access'] = TRUE;
    }
  }
}

/**
 * Implements hook_form_form_ID_alter().
 */
function social_event_managers_form_views_exposed_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
  if ($form['#id'] === 'views-exposed-form-event-manage-enrollments-page-manage-enrollments') {
    /** @var \Drupal\views\ViewExecutable $view */
    $view = $form_state->get('view');

    // Declare value 'module_name' since we send it to callback.
    $form['module_name'] = [
      '#type' => 'value',
      '#value' => 'social_event_managers',
    ];
    // Declare 'items_per_value_key' that will be sent in users_data table.
    $form['items_per_value_key'] = [
      '#type' => 'value',
      '#value' => $view->id() . '__items_per_page',
    ];
    $form['actions']['submit']['#submit'][] = [MembersPageHelperService::class, 'setItemsPerPage'];
  }
}

/**
 * Implements hook_views_pre_view().
 */
function social_event_managers_views_pre_view(ViewExecutable $view, $display_id, array &$args) {
  if ($view->id() === 'event_manage_enrollments') {
    // Remove fields from the Enrollment Management tab if the user is not a
    // manager or organiser.
    if (!social_event_manager_or_organizer()) {
      $fields = $view->display_handler->getOption('fields');
      $fields['operations_1']['exclude'] = TRUE;
      $fields['social_views_bulk_operations_bulk_form_enrollments_1']['exclude'] = TRUE;
      $view->display_handler->overrideOption('fields', $fields);
    }

    if ($display_id === 'page_manage_enrollments') {
      // Get related '__items_per_value' value from users_data table.
      \Drupal::service('social_core.members_page_helper')
        ->getItemsPerPage(
          $view,
          'social_event_managers',
          $view->id() . '__items_per_page'
        );
    }
  }
}

/**
 * Implements hook_node_access_records().
 */
function social_event_managers_node_access_records(NodeInterface $node) {
  $grants = [];
  // Only for events.
  if (($node->getType() === 'event') && $event_managers_ids = array_column($node->get('field_event_managers')
    ->getValue(), 'target_id')) {
    // Event organizers should be granted access.
    // Load the event managers accounts.
    $users = User::loadMultiple($event_managers_ids);
    foreach ($users as $event_manager) {
      // Event organizers must have access
      // to view the record in the first place.
      if ($node->access('view', $event_manager)) {
        // Add grant.
        $grants[] = [
          'realm' => 'social_event_managers:' . $node->id(),
          'gid' => $event_manager->id(),
          'grant_view' => 1,
          'grant_update' => 1,
          'grant_delete' => 0,
        ];
      }
    }
  }

  return $grants;
}

/**
 * Implements hook_node_grants().
 */
function social_event_managers_node_grants(AccountInterface $account, $op) {

  $grants = [];

  // @todo Fetch all nodes this user has access to
  // and add a grant for each of those.
  $query = \Drupal::database()->select('node__field_event_managers', 'em');
  $query->fields('em', ['entity_id']);
  $query->condition('em.field_event_managers_target_id', $account->id());
  // Add grants.
  foreach ($query->execute()->fetchAllAssoc('entity_id') as $nid) {
    $grants['social_event_managers:' . $nid->entity_id][] = $account->id();
  }

  // Tell Drupal about users grants.
  return $grants;
}

/**
 * Implements hook_node_access().
 *
 * Remember: if any module returns forbidden and denies access to certain node
 * and operation it will not allow the user to do the operation on the node.
 *
 * We need this implementation because we also want to give edit access to event
 * manager regardless these scenarios thought of in gnode_node_access:
 * - is a member in the group and:
 * - has edit own or edit any permission in the group
 *
 * The gnode module specifically returns allowed if any of the above scenarios
 * are met, but forbidden in all the other scenarios. Our code ensures that if
 * we are in operation update and if gnode already returns forbidden we are able
 * to return an allowed if user is an event manager.
 */
function social_event_managers_node_access(NodeInterface $node, $op, AccountInterface $account) {
  return SocialEventManagersAccessHelper::getEntityAccessResult($node, $op, $account);
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function social_event_managers_menu_local_tasks_alter(&$data, $route_name) {
  $can_show_managers_link = FALSE;
  $routes_to_check = _social_event_menu_local_tasks_routes();

  if (in_array($route_name, $routes_to_check)) {
    $node = \Drupal::service('current_route_match')->getParameter('node');
    if (!is_null($node) && (!$node instanceof Node)) {
      $node = Node::load($node);
    }
    if (($node instanceof Node) && $node->getType() === 'event') {
      $can_show_managers_link = TRUE;

      // Should we minimise the amount of tabs? If so we remove enrollees as we
      // show it in the management tab.
      if (!empty($data['tabs'][0]['views_view:view.event_enrollments.view_enrollments'])) {
        unset($data['tabs'][0]['views_view:view.event_enrollments.view_enrollments']);
      }
      // For Guest Enrollments alone we hide them as well and place them in the
      // manage tab.
      if (!empty($data['tabs'][0]['views_view:view.manage_enrollments.page'])) {
        unset($data['tabs'][0]['views_view:view.manage_enrollments.page']);
      }
      if (!social_event_manager_or_organizer()) {
        $data['tabs'][0]['views_view:view.event_manage_enrollments.page_manage_enrollments']['#link']['title'] = t('Enrollments');
      }
    }

  }

  // PLace this here, since hiding it should happen
  // always and not only on the mentioned routes.
  if (!$can_show_managers_link) {
    unset($data['tabs'][0]['views_view:view.managers.view_managers']);
    unset($data['tabs'][0]['views_view:view.event_manage_enrollments.page_manage_enrollments']);
  }

}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function social_event_managers_form_node_event_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Set author of event as event organiser automatically.
  $config = \Drupal::configFactory()
    ->getEditable('social_event_managers.settings');

  if ($config->get('author_as_manager')) {
    if ($form_state->getTriggeringElement() === NULL) {
      $account = \Drupal::currentUser();
      $user = \Drupal::entityTypeManager()
        ->getStorage('user')
        ->load($account->id());

      $last_key = $form['field_event_managers']['widget']['#max_delta'];
      $form['field_event_managers']['widget'][$last_key]['target_id']['#default_value'] = $user;
    }
  }
  // Update the field event managers widget form.
  if (isset($form['field_event_managers']['widget']['add_more'])) {
    $form['field_event_managers']['widget']['add_more']['#value'] = t('Add another organizer');
  }
}

/**
 * Implements hook_activity_recipient_organizer_alter().
 */
function social_event_managers_activity_recipient_organizer_alter(array &$recipients, Node $event, $data) {
  $receiver = '';
  $organizers = $event->get('field_event_managers')->getValue();

  if ($data['target_type'] === 'event_enrollment' && !empty($data['target_id'])) {
    $enrollment = EventEnrollment::load($data['target_id']);
    $receiver = $enrollment->getAccount();
  }

  // If there are more organizers we want them to receive a notification too
  // so we add them to the array of recipients.
  if (!empty($organizers)) {
    foreach ($organizers as $organizer) {
      // We don't want Organizers to receive activity_on_events_im_organizing.
      // It will already receive it as part of a different context.
      if (!empty($receiver) && $organizer['target_id'] === $receiver) {
        continue;
      }

      // Make sure we don't add the people twice.
      if (!in_array($organizer['target_id'], array_column($recipients, 'target_id'))) {
        $recipients[] = [
          'target_type' => 'user',
          'target_id' => $organizer['target_id'],
        ];
      }
    }
  }
}

/**
 * Implements hook_batch_alter().
 */
function social_event_managers_batch_alter(&$batch) {
  if (!isset($batch['source_url'])) {
    return;
  }

  $actions = [
    'social_event_enrolments_export_enrollments_action',
    'social_event_managers_send_email_action',
    'social_event_managers_delete_event_enrollment_action',
    'social_event_an_enroll_enrolments_export_action',
    'social_event_an_enroll_send_email_action',
  ];

  /** @var \Drupal\Core\Url $url */
  $url = &$batch['source_url'];

  if ($url->getRouteName() === 'social_event_managers.vbo.confirm' ||
    $url->getRouteName() === 'views_bulk_operations.confirm' ||
    $url->getRouteName() === 'views_bulk_operations.execute_batch') {
    // Get the action ID.
    $action_id = _social_event_managers_get_action_id($batch);

    $batch['sets'][0]['results']['action'] = $action_id;
    if (in_array($action_id, $actions, TRUE)) {
      $batch['sets'][0]['finished'] = '_social_event_managers_action_batch_finish';
    }
  }
}

/**
 * Action batch finished callback.
 *
 * @param bool $success
 *   Was the process successfull?
 * @param array $results
 *   Batch process results array.
 * @param array $operations
 *   Performed operations array.
 */
function _social_event_managers_action_batch_finish($success, array $results, array $operations) {
  // When we do a bulk action on all the items in a view, across multiple pages,
  // the saveList function needs to be called. So after pre-populating the list
  // the actual action is performed on the entities.
  if (!empty($results['view_id']) && !empty($results['display_id'])) {
    ViewsBulkOperationsBatch::saveList(TRUE, $results, $operations);
    return;
  }

  $results_count = $results['operations'][0]['count'];

  $hook = 'social_event_managers_action_' . $results['action'] . '_finish';

  \Drupal::moduleHandler()->invokeAllWith($hook, function (callable $hook) use ($success, $results_count) {
    $messages = $hook($success);

    if (is_array($messages)) {
      $fields = 0;

      foreach ($messages as $type => $message) {
        if (($type === 'singular' || $type === 'plural') && !empty($message) && is_string($message)) {
          $fields++;
        }
      }

      if ($fields === 2) {
        // @todo source strings will never be translatable when we use
        // variables. This should be refactored.
        $message = \Drupal::translation()->formatPlural($results_count, $messages['singular'], $messages['plural']);
        $type = $success ? MessengerInterface::TYPE_STATUS : MessengerInterface::TYPE_WARNING;
        \Drupal::messenger()->addMessage($message, $type);
      }
    }
  });
}

/**
 * Function to get the action id of a batch.
 *
 * @param array $batch
 *   The batch array.
 *
 * @return string
 *   Returns the batch action id.
 */
function _social_event_managers_get_action_id(array &$batch) {
  $form_state = &$batch['form_state'];

  $action_id = '';
  if ($form_state instanceof FormStateInterface) {
    $data = $form_state->get('views_bulk_operations');
    $action_id = $data['action_id'];
  }
  else {
    foreach ($batch['sets'][0]['operations'] as $operations) {
      if (empty($operations) || !is_array($operations)) {
        break;
      }
      foreach ($operations as $operation) {
        if (empty($operation) || !is_array($operation)) {
          break;
        }
        foreach ($operation as $items) {
          if (empty($items) || !is_array($items)) {
            break;
          }

          if (!empty($items['action_id'])) {
            $action_id = $items['action_id'];
            break;
          }
        }
      }
    }
  }
  return $action_id;
}

/**
 * Implements hook_social_event_action_ACTION_ID_finish().
 */
function social_event_managers_social_event_managers_action_social_event_managers_send_email_action_finish($success) {
  if ($success) {
    // When the queue storage module is enabled the email is send in the
    // background.
    if (\Drupal::moduleHandler()->moduleExists('social_queue_storage')) {
      return [
        'singular' => 'Your email will be send to 1 selected enrollee',
        'plural' => 'Your email will be send to @count selected enrollees',
      ];
    }

    return [
      'singular' => 'Your email has been sent to 1 selected enrollee successfully',
      'plural' => 'Your email has been sent to @count selected enrollees successfully',
    ];
  }

  return [
    'singular' => 'Your email has not been sent to 1 selected enrollee successfully',
    'plural' => 'Your email has not been sent to @count selected enrollees successfully',
  ];
}

/**
 * Implements hook_social_event_action_ACTION_ID_finish().
 */
function social_event_managers_social_event_managers_action_social_event_managers_delete_event_enrollment_action_finish($success) {
  if ($success) {
    return [
      'singular' => '1 selected enrollee has been removed from the event successfully',
      'plural' => '@count selected enrollees have been removed from the event successfully',
    ];
  }

  return [
    'singular' => '1 selected enrollee has not been removed from the event successfully',
    'plural' => '@count selected enrollees have not been removed from the event successfully',
  ];
}

/**
 * Add node ID to the route of action confirmation step.
 */
function _social_event_managers_views_bulk_operations_bulk_form_submit($form, FormStateInterface $form_state) {
  $node = '';

  /** @var \Drupal\Core\Url|null $url */
  $url = $form_state->getRedirect();

  if ($form_state->get('node')) {
    $node = $form_state->get('node');
  }
  if (empty($node)) {
    $route = \Drupal::routeMatch()->getParameter('node');
    $node = $route->id();
  }

  if (empty($node)) {
    return;
  }

  $route_parameters = [
    'node' => $node,
  ];

  // If we have confirm route then just go with it.
  if ($url && $url->getRouteName() === "social_event_managers.vbo.confirm") {
    $url = Url::fromRoute('social_event_managers.vbo.confirm', $route_parameters);
    $form_state->setRedirectUrl($url);
    return;
  }

  // For additional configurations, such as 'Send email' action.
  if ($form['#id'] !== 'views-bulk-operations-configure-action') {
    $url = Url::fromRoute('social_event_managers.vbo.execute_configurable', $route_parameters);
  }
  else {
    $url = Url::fromRoute('social_event_managers.vbo.confirm', $route_parameters);
  }

  $form_state->setRedirectUrl($url);
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_event_managers_preprocess_views_view(&$variables) {
  if (!\Drupal::moduleHandler()->moduleExists('social_event_invite')) {
    /** @var \Drupal\views\ViewExecutable $view */
    $view = &$variables['view'];

    // Remove header & VBO actions from the Enrollment Management tab if the
    // user is not a manager or organiser.
    if ($view->id() === 'event_manage_enrollments') {
      if (!social_event_manager_or_organizer()) {
        unset($variables['rows']['social_views_bulk_operations_bulk_form_enrollments_1']);
        unset($variables['rows']['header']);
        unset($variables['rows']['actions']);
      }

      $block = \Drupal::entityTypeManager()->getStorage('block')
        ->load('socialblue_local_actions');

      $variables['header']['actions'] = \Drupal::entityTypeManager()
        ->getViewBuilder('block')
        ->view($block);
    }
  }

}
