<?php

/**
 * @file
 * Contains Social Flexible Group Book functions.
 */

use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\group\Entity\GroupInterface;
use Drupal\social_group\Plugin\Field\FieldWidget\SocialGroupSelectorWidget;

/**
 * Implements hook_entity_form_display_alter().
 */
function social_flexible_group_book_entity_form_display_alter(EntityFormDisplayInterface $form_display, array $context): void {
  if (
    $context['entity_type'] === 'node' &&
    $context['bundle'] === 'book' &&
    $form_display->getMode() === 'default'
  ) {
    // Add "groups" field to the form.
    // @see \Drupal\social_group\SocialGroupSelectorWidgetConfigOverride::loadOverrides
    $form_display->setComponent('groups', [
      'type' => 'social_group_selector_widget',
      'weight' => 0,
      'region' => 'content',
      'third_party_settings' => [],
      'settings' => [
        'width' => '100%',
        'match_operator' => 'CONTAINS',
        'match_limit' => '10',
        'autocomplete' => FALSE,
      ],
    ]);
  }

  if (
    $context['entity_type'] === 'group' &&
    $context['bundle'] === 'flexible_group' &&
    $form_display->getMode() === 'default'
  ) {
    $form_display->setComponent('enable_books', [
      'weight' => 6,
      'settings' => [
        'display_label' => 'true',
      ],
      'third_party_settings' => [],
      'type' => 'boolean_checkbox',
      'region' => 'content',
    ]);
  }
}

/**
 * Implements hook_form_alter().
 */
function social_flexible_group_book_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
  switch ($form_id) {
    case 'group_flexible_group_add_form':
    case 'group_flexible_group_edit_form':
      // Move 'enable_books' field to the "Personalisation" field group.
      if (isset($form['#fieldgroups']['group_personalisation'])) {
        $form['#fieldgroups']['group_personalisation']->children[] = 'enable_books';
        $form['#group_children']['enable_books'] = 'group_personalisation';
      }
      break;

  }
}

/**
 * Implements hook_form_FORM_ID_alter() for "node_form".
 */
function social_flexible_group_book_form_node_form_alter(array &$form, FormStateInterface $form_state): void {
  /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
  $form_object = $form_state->getFormObject();
  /** @var \Drupal\node\NodeInterface $node */
  $node = $form_object->getEntity();

  if ($node->bundle() !== 'book') {
    return;
  }

  if (!empty($form['#fieldgroups']['group_visibility'])) {
    $form['#fieldgroups']['group_visibility']->children[] = 'groups';
    $form['#group_children']['groups'] = 'group_visibility';
  }

  // We need add this option here because by default "book" module require
  // a specific global permission - "create new books".
  // Group managers with only "verified" global role should be able to create
  // new books as well even without this permission.
  if (!isset($form['book']['bid']['#options'])) {
    return;
  }
  $nid = !$node->isNew() ? $node->id() : 'new';
  $options = $form['book']['bid']['#options'];
  if (!isset($options[$nid])) {
    if ($nid === 'new' || (isset($node->book['original_bid']) && $nid !== $node->book['original_bid'])) {
      // The node can become a new book, if it is not one already.
      $form['book']['bid']['#options'] = [$nid => t('- Create a new book -')] + $options;
    }
  }

  // Do not allow for user to move book if it's a parent because this may cause
  // troubles with "Parent item" field in "Book outline" form element.
  // Also, disable the "Group" field if the book is creating in group context.
  $disable_groups_field = in_array(\Drupal::routeMatch()->getRouteName(), [
    'entity.group_content.add_form',
    'entity.group_content.create_form',
  ]) || (isset($node->book['nid'], $node->book['bid']) && $node->book['nid'] === $node->book['bid'] && empty($node->book['pid']));

  if ($disable_groups_field) {
    $form['groups']['#disabled'] = TRUE;
  }

  // If user is creating a book in group context, then leave only the books
  // that belong to the current group.
  if (!empty($form_state->getValue('groups'))) {
    $value = $form_state->getValue('groups');
    $gid = is_array($value) && !empty($value[0]['target_id'])
      ? $value[0]['target_id']
      : (int) $value;
  }
  elseif (!empty($form['groups']['widget']['#default_value'][0])) {
    $gid = $form['groups']['widget']['#default_value'][0];
  }

  if (!empty($gid)) {
    $group = \Drupal::entityTypeManager()
      ->getStorage('group')
      ->load($gid);

    if ($group instanceof GroupInterface) {
      /** @var \Drupal\social_node\Entity\Node[] $books */
      $books = $group->getContentEntities('group_node:book');
      $bids = array_map(function ($book) {
        return $book->id();
      }, $books);

      $form['book']['bid']['#options'] = array_filter($form['book']['bid']['#options'], function ($key) use ($bids) {
        return $key === 0 || $key === 'new' || in_array($key, $bids);
      }, ARRAY_FILTER_USE_KEY);

      // We need skip validation for the field "Books". The issue is that when
      // ajax reload this field it still contains old values and default
      // validation rise an error.
      $form['book']['bid']['#validated'] = TRUE;
    }
  }
  else {
    // If book is creating without group we remove all books
    // that are in groups already.
    $query = \Drupal::database()->select('group_content_field_data', 't');
    $query->addField('t', 'entity_id', 'nid');
    $query->condition('type', 'flexible_group-group_node-book');
    $results = $query->execute();
    $bids = $results instanceof StatementInterface ? $results->fetchAllKeyed(0, 0) : [];

    $form['book']['bid']['#options'] = array_filter($form['book']['bid']['#options'], function ($key) use ($bids) {
      return $key === 0 || $key === 'new' || !in_array($key, $bids);
    }, ARRAY_FILTER_USE_KEY);
  }

  // We want to change "Book outline" book list according the "Group" chosen.
  // The "Group" field is already ajaxified, so we can't add another ajax
  // callback. So, we just override "Group" field ajax callback and change it
  // response.
  $form['groups']['widget']['#ajax']['callback'] = '_social_flexible_group_book_ajax_callback';
}

/**
 * Ajax callback to replace "Book outline" books options list.
 */
function _social_flexible_group_book_ajax_callback(array $form, FormStateInterface $form_state): AjaxResponse {
  // Get a parent response and then override it.
  $response = SocialGroupSelectorWidget::validateGroupSelection($form, $form_state);

  // Get the "group id" if it available.
  if (!empty($form_state->getValue('groups'))) {
    $value = $form_state->getValue('groups');
    $gid = is_array($value) && !empty($value[0]['target_id'])
      ? $value[0]['target_id']
      : (int) $value;
  }
  elseif (!empty($form['groups']['widget']['#default_value'][0])) {
    $gid = $form['groups']['widget']['#default_value'][0];
  }

  if (empty($gid)) {
    return $response;
  }

  $group = \Drupal::entityTypeManager()
    ->getStorage('group')
    ->load($gid);

  if (!$group instanceof GroupInterface) {
    return $response;
  }

  $all_books = \Drupal::service('book.outline_storage')->getBooks();

  if (empty($all_books)) {
    return $response;
  }

  /** @var \Drupal\social_node\Entity\Node[] $books_in_group */
  $books_in_group = $group->getContentEntities('group_node:book', [
    'entity_id' => $all_books,
  ]);
  // Get translations.
  $books_in_group = array_map([\Drupal::service('entity.repository'), 'getTranslationFromContext'], $books_in_group);

  // Rebuild options.
  if (!empty($form['book']['bid']['#options']['new'])) {
    $form['book']['bid']['#options'] = ['new' => $form['book']['bid']['#options']['new']];
  }
  else {
    $form['book']['bid']['#options'] = [];
  }

  foreach ($books_in_group as $book) {
    if (is_null($book)) {
      continue;
    }

    $options[$book->id()] = $book->label();
  }

  $form['book']['bid']['#options'] += ($options ?? []);

  // Reset "Parent item" field in "Book outline" to default values.
  // @see \Drupal\book\BookManager::addParentSelectFormElements()
  $form['book']['pid'] = [
    '#type' => 'hidden',
    '#value' => -1,
    '#prefix' => '<div id="edit-book-plid-wrapper"><em>' . t('No book selected.') . '</em>',
    '#suffix' => '</div>',
  ];

  // Replace the list of books in "Book outline" field.
  $response->addCommand(new ReplaceCommand('.book-outline-form', $form['book']));

  $form_state->setRebuild();

  return $response;
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function social_flexible_group_book_menu_local_tasks_alter(array &$data, string $route_name): void {
  if (empty($data['tabs'][0]['social_flexible_group_book.books'])) {
    return;
  }

  /** @var \Drupal\group\Entity\GroupInterface $group */
  $group = _social_group_get_current_group();
  if (!$group instanceof GroupInterface) {
    return;
  }

  if ($group->bundle() === 'flexible_group') {
    $is_books_enabled = !$group->hasField('enable_books')
      || (bool) $group->get('enable_books')->getString();
    $access = $is_books_enabled ? AccessResult::allowed() : AccessResult::forbidden();
  }
  else {
    // For other group types we disable the tab.
    $access = AccessResult::forbidden();
  }

  $data['tabs'][0]['social_flexible_group_book.books']['#access'] = $access;
}

/**
 * Implements hook_block_access().
 */
function social_flexible_group_book_block_access(Block $block, string $operation, AccountInterface $account): AccessResultInterface {
  if ($operation !== 'view') {
    return AccessResult::neutral();
  }

  if (
    in_array($block->getPluginId(), ['group_hero_block', 'group_statistic_block']) ||
    $block->id() === 'socialblue_pagetitleblock_content'
  ) {
    $request_path = $block->getVisibility()['request_path'];
    $request_path['pages'] .= "\r\n/group/*/books";
    $block->setVisibilityConfig('request_path', $request_path);
  }

  return AccessResult::neutral();
}

/**
 * Implements hook_options_list_alter().
 */
function social_flexible_group_book_options_list_alter(array &$options, array $context): void {
  // Filter options in "groups" field when creating book pages in group
  // context - remove flexible groups with disabled book pages.
  if (
    $context['fieldDefinition']->getName() === 'groups' &&
    $context['entity']->getEntityTypeId() === 'node' &&
    $context['entity']->bundle() === 'book'
  ) {

    $valid_gids = \Drupal::entityQuery('group')
      ->accessCheck(FALSE)
      ->condition('enable_books', TRUE)
      ->execute();

    foreach ($options as $key => $value) {
      // If options contain sub-categories loop in nested array.
      if (is_array($value)) {
        foreach ($value as $nested_key => $nested_value) {
          if (!in_array($nested_key, $valid_gids)) {
            unset($options[$key][$nested_key]);
          }
        }
        continue;
      }

      if (!in_array($key, $valid_gids)) {
        unset($options[$key]);
      }
    }
  }
}
