<?php

/**
 * @file
 * Theme hooks to support the LocalGov Base Theme.
 */

use Drupal\Component\Utility\Crypt;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Drupal\views\ViewExecutable;

/**
 * Implements hook_form_system_theme_settings_alter().
 */
function localgov_base_form_system_theme_settings_alter(&$form, FormStateInterface $form_state, $form_id = NULL): void {
  // Work-around for a core bug affecting admin themes. See issue #943212.
  if (isset($form_id)) {
    return;
  }

  // Add settings in a details element for better organization.
  $theme_info = \Drupal::service('theme.manager')->getActiveTheme()->getExtension()->info;
  $theme_human_name = $theme_info['name'] ?? \Drupal::service('theme.manager')->getActiveTheme()->getName();
  $form['localgov_base_settings'] = [
    '#type' => 'details',
    '#title' => t('@theme Settings', ['@theme' => $theme_human_name]),
    '#open' => TRUE,
  ];

  // Create a fieldset for general settings and place at the top.
  $form['localgov_base_settings']['general_fieldset'] = [
    '#type' => 'fieldset',
    '#title' => t('General Settings'),
    '#weight' => -10,
  ];

  $form['localgov_base_settings']['general_fieldset']['localgov_base_show_back_to_top_link'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Display a "Back to Top" link on long pages.'),
    '#default_value' => theme_get_setting('localgov_base_show_back_to_top_link'),
    '#description'   => t('This adds a link to the beginning of the page content (<code>#main-content</code>).'),
  ];

  $form['localgov_base_settings']['general_fieldset']['localgov_base_header_behaviour'] = [
    '#type' => 'radios',
    '#title' => t('Header behaviour'),
    '#default_value' => theme_get_setting('localgov_base_header_behaviour'),
    '#options' => [
      'default' => t('Default - scrolls away with the page'),
      'sticky' => t('Sticky - remains at the top of the page'),
      'appears_on_scroll' => t('Scroll - appears when scrolling up the page'),
    ],
    '#description' => t('Select how you want the header to behave. This setting only apply to anonymous users.'),
  ];

  $form['localgov_base_settings']['general_fieldset']['localgov_base_mobile_breakpoint_js'] = [
    '#type'          => 'number',
    '#title'         => t('Mobile breakpoint for JavaScript'),
    '#default_value' => theme_get_setting('localgov_base_mobile_breakpoint_js') ? theme_get_setting('localgov_base_mobile_breakpoint_js') : 768,
    '#description'   => t('This is the mobile breakpoint in pixels. This is used by the JS to determine when to show the mobile menu. If left blank, this will default to 768px.'),
    '#min'           => 0,
    '#attributes' => [
      'placeholder' => '768',
    ],
  ];

  $form['localgov_base_settings']['general_fieldset']['localgov_base_responsive_wysiwyg_tables'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Make WYSIWYG tables responsive.'),
    '#description'   => t('Places tables inserted via WYSIWYG into a responsive container, so they can scroll horizontally on small screens.'),
    '#default_value' => theme_get_setting('localgov_base_responsive_wysiwyg_tables') ? theme_get_setting('localgov_base_responsive_wysiwyg_tables') : FALSE,
  ];

  $form['localgov_base_settings']['general_fieldset']['localgov_base_equal_heights_classes'] = [
    '#type'          => 'textarea',
    '#title'         => t('Equalise heights of components'),
    '#description'   => t('When placing blocks of the same type in a layout (e.g. in a Subsite Overview), you may want them to all have the same height. To do this, add the classes of the blocks you want to equalise here, one per line. For example, if you wanted to equalise the heights of the "<strong>IA Block</strong>" and "<strong>Link Block</strong>" components, you would add <code>.ia-block</code> and <code>.link-block</code> on separate lines.'),
    '#default_value' => theme_get_setting('localgov_base_equal_heights_classes'),
  ];

  // Create a fieldset for the CSS/JS library options.
  $form['localgov_base_settings']['libraries_fieldset'] = [
    '#type' => 'fieldset',
    '#title' => t('Theme Libraries'),
    '#description' => t('Control whether to load base theme CSS and JavaScript libraries.'),
  ];

  $form['localgov_base_settings']['libraries_fieldset']['localgov_base_remove_css'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Remove CSS libraries from base theme.'),
    '#default_value' => theme_get_setting('localgov_base_remove_css'),
    '#description'   => t("Check this box to disable the base theme's CSS"),
  ];

  $form['localgov_base_settings']['libraries_fieldset']['localgov_base_remove_js'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Remove JS libraries from base theme.'),
    '#default_value' => theme_get_setting('localgov_base_remove_js'),
    '#description'   => t("Check this box to disable the base theme's JavaScript"),
  ];

  // Create a fieldset for content status related settings.
  $form['localgov_base_settings']['content_status_fieldset'] = [
    '#type' => 'fieldset',
    '#title' => t('Content Status Display'),
    '#description' => t('Control how unpublished and archived content is displayed.'),
  ];

  $form['localgov_base_settings']['content_status_fieldset']['localgov_base_add_unpublished_background_colour'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Add background colour to unpublished content.'),
    '#default_value' => theme_get_setting('localgov_base_add_unpublished_background_colour'),
    '#description'   => t("If you remove the background colour, you should probably also check the box below to add '[Draft]' to the title of unpublished content."),
  ];

  $form['localgov_base_settings']['content_status_fieldset']['localgov_base_add_draft_note_to_unpublished_content'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Add "Draft" to title of draft unpublished content.'),
    '#default_value' => theme_get_setting('localgov_base_add_draft_note_to_unpublished_content'),
    '#description'   => t('This adds the word "Draft" to the title of any draft unpublished content. This is useful if you have removed the pink background from unpublished content.'),
  ];

  $form['localgov_base_settings']['content_status_fieldset']['localgov_base_add_archived_note_to_archived_content'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Add "Archived" to title of archived content.'),
    '#default_value' => theme_get_setting('localgov_base_add_archived_note_to_archived_content'),
    '#description'   => t('This adds the word "Archived" to the title of any content with the "archived" moderation state.'),
  ];

  // Create a fieldset for service-related settings.
  $form['localgov_base_settings']['services_fieldset'] = [
    '#type' => 'fieldset',
    '#title' => t('Service Display Settings'),
    '#description' => t('Control how service content types child pages are displayed.'),
  ];

  $form['localgov_base_settings']['services_fieldset']['localgov_base_service_landing_page_child_pages_list_style'] = [
    '#type' => 'select',
    '#title' => t('Service Landing Page: Child pages list style'),
    '#default_value' => theme_get_setting('localgov_base_service_landing_page_child_pages_list_style') ?: 'grid_3',
    '#options' => [
      'grid_4' => t('Grid (4 in a row)'),
      'grid_3' => t('Grid (3 in a row)'),
      'grid_2' => t('Grid (2 in a row)'),
      'single_column' => t('Single column'),
    ],
  ];

  $form['localgov_base_settings']['services_fieldset']['localgov_base_service_sub_landing_page_child_pages_list_style'] = [
    '#type' => 'select',
    '#title' => t('Service Sub-landing Page: Child pages list style'),
    '#default_value' => theme_get_setting('localgov_base_service_sub_landing_page_child_pages_list_style') ?: 'grid_2',
    '#options' => [
      'grid_4' => t('Grid (4 in a row)'),
      'grid_3' => t('Grid (3 in a row)'),
      'grid_2' => t('Grid (2 in a row)'),
      'single_column' => t('Single column'),
    ],
  ];

  // Create a fieldset for guide-related settings.
  $form['localgov_base_settings']['guides_fieldset'] = [
    '#type' => 'fieldset',
    '#title' => t('Guide Display Settings'),
    '#description' => t('Control how guides are displayed on the site.'),
  ];

  $form['localgov_base_settings']['guides_fieldset']['localgov_base_localgov_guides_stacked_heading'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Use stacked heading pattern for guides.'),
    '#default_value' => theme_get_setting('localgov_base_localgov_guides_stacked_heading'),
    '#description'   => t('This will stack the Guide title and page heading on top of each other.'),
  ];

  $form['localgov_base_settings']['guides_fieldset']['localgov_base_localgov_guides_vertical_navigation'] = [
    '#type'          => 'checkbox',
    '#title'         => t('Use vertical navigation for guides.'),
    '#default_value' => theme_get_setting('localgov_base_localgov_guides_vertical_navigation'),
    '#description'   => t('This will display the guide navigation vertically above the guide.'),
  ];

  $form['#validate'][] = 'localgov_base_theme_settings_validate';
}

/**
 * Validate the theme settings form.
 */
function localgov_base_theme_settings_validate(&$form, FormStateInterface $form_state): void {
  // Check if the mobile breakpoint is a number.
  if (!is_numeric($form_state->getValue('localgov_base_mobile_breakpoint_js'))) {
    $form_state->setErrorByName('localgov_base_mobile_breakpoint_js', t('The mobile breakpoint must be a number.'));
  }
}

/**
 * Implements hook_preprocess_html().
 */
function localgov_base_preprocess_html(&$variables): void {
  // Add the mobile breakpoint value to drupalSettings for use in JavaScript.
  if (!empty(theme_get_setting('localgov_base_mobile_breakpoint_js'))) {
    $mobile_breakpoint = theme_get_setting('localgov_base_mobile_breakpoint_js');
    $variables['#attached']['drupalSettings']['localgov_base']['mobileBreakpointJS'] = $mobile_breakpoint;
  }
  else {
    $variables['#attached']['drupalSettings']['localgov_base']['mobileBreakpointJS'] = 768;
  }

  // Add the 'sticky-header' library if the sticky header setting is enabled.
  if (theme_get_setting('localgov_base_header_behaviour') !== 'default') {

    // Check if the user is logged out.
    // We have much fewer calculations if we keep this to anonymous users only.
    if (\Drupal::currentUser()->isAnonymous()) {
      $variables['#attached']['library'][] = 'localgov_base/sticky-header';
      $variables['attributes']['class'][] = 'sticky-header';
      $variables['html_attributes']['class'] = [];
      $variables['html_attributes']['class'][] = 'sticky-header-html';

      // If 'sticky' is chosen add a class to the body element.
      if (theme_get_setting('localgov_base_header_behaviour') === 'sticky') {
        $variables['attributes']['class'][] = 'sticky-header--sticky';
      }

      // If 'scroll' is chosen add a class to the body element.
      if (theme_get_setting('localgov_base_header_behaviour') === 'appears_on_scroll') {
        $variables['attributes']['class'][] = 'sticky-header--scroll';
      }
    }
  }

  // Add the 'localgov_base_responsive_wysiwyg_tables' value to drupalSettings.
  if (!empty(theme_get_setting('localgov_base_responsive_wysiwyg_tables'))) {
    if (theme_get_setting('localgov_base_responsive_wysiwyg_tables') === TRUE || 1) {
      $variables['#attached']['library'][] = 'localgov_base/responsive-tables';
    }
  }

  // Add the 'equal-heights-blocks' library if classes are configured.
  $equal_heights_setting = theme_get_setting('localgov_base_equal_heights_classes');
  if (!empty($equal_heights_setting)) {
    $classes = array_filter(array_map('trim', explode("\n", $equal_heights_setting)));
    if (!empty($classes)) {
      $variables['#attached']['library'][] = 'localgov_base/equal-heights-blocks';
      $variables['#attached']['drupalSettings']['localgov_base']['equalHeightsClasses'] = array_values($classes);
    }
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function localgov_base_preprocess_page(&$variables): void {
  // Work around for Drupal core issue.
  // Blocks employ lazy building.  This makes it difficult to determine from
  // **Twig templates** if they will eventually produce empty content or not.
  // @see https://www.drupal.org/node/953034
  // @see https://www.drupal.org/forum/support/module-development-and-code-questions/2016-04-07/drupal-8-regions-with-and-empty#comment-12149518
  $active_theme = \Drupal::service('theme.manager')->getActiveTheme()->getName();
  $regions = array_keys(system_region_list($active_theme, REGIONS_VISIBLE));

  $excluded_regions = [
    'messages',
    'disabled',
  ];

  foreach ($regions as $region) {
    if (in_array($region, $excluded_regions, TRUE)) {
      continue;
    }
    $copy = $variables['page'][$region];

    /** @var \Drupal\Core\Render\RendererInterface $renderer */
    $renderer = \Drupal::service('renderer');
    $rendered = $renderer->renderInIsolation($copy);

    $variables['has_' . $region] = strlen(trim(strip_tags($rendered, '<drupal-render-placeholder><embed><hr><iframe><img><input><link><object><script><source><style><video>'))) > 0;
  }
  $variables['has_sidebars'] = $variables['has_sidebar_first'] || $variables['has_sidebar_second'];

  if (!isset($variables['localgov_base_remove_css'])) {
    $variables['#attached']['library'][] = 'localgov_base/global';
  }

  // We have a theme setting boolean for removing the pink background from
  // unpublished content. If that setting is false,
  // we will add the 'unpublished' library to our page.
  if (theme_get_setting('localgov_base_add_unpublished_background_colour') === TRUE) {
    $variables['#attached']['library'][] = 'localgov_base/unpublished-bg';
  }

  // Render the Back to Top link or not according to the theme setting.
  if (theme_get_setting('localgov_base_show_back_to_top_link')) {
    $variables['back_to_top'] = TRUE;
  }

  // Load the site configuration.
  $site_config = \Drupal::config('system.site');
  $site_403 = $site_config->get('page.403');
  $site_404 = $site_config->get('page.404');

  // Custom 403 and 404 pages.
  // We have a variable in page.html.twig called default_status_content.
  // This is used to determine if we should show the default 403/404 content,
  // or if we should show the content of the node that is set as the 403/404
  // page.
  $variables['default_status_content'] = TRUE;
  $route_match = Drupal::routeMatch();
  $route = $route_match->getRouteName();
  if ($route === 'entity.node.canonical') {
    $node = $route_match->getParameter('node');
    $node_id = $node->id();
    $is_404_page_node = $site_404 === 'node/' . $node_id;
    $is_403_page_node = $site_403 === 'node/' . $node_id;
    if ($is_404_page_node && is_null($site_404)) {
      $variables['default_status_content'] = TRUE;
    }
    else {
      $variables['default_status_content'] = FALSE;
    }
    if ($is_403_page_node && is_null($site_403)) {
      $variables['default_status_content'] = TRUE;
    }
    else {
      $variables['default_status_content'] = FALSE;
    }
  }

}

/**
 * Get the restricted width content types.
 *
 * @return array
 *   An array of restricted width content types.
 */
function _localgov_get_restricted_width_content_types() {
  return [
    'localgov_services_page',
    'localgov_event',
    'localgov_services_status',
    'localgov_step_by_step_overview',
    'localgov_publication_cover_page',
  ];
}

/**
 * Implements hook_preprocess_HOOK().
 */
function localgov_base_preprocess_node(&$variables): void {
  $node = $variables['node'];
  $node_status = $variables['node']->isPublished();

  if ($node instanceof NodeInterface && $variables['view_mode'] === 'full') {
    // Restrict width of the content area to two-thirds of the screen for
    // some content types. This is helpful to reduce the line-length for
    // better readability.
    $variables['restricted_width_content_types'] = [];
    if (in_array($node->bundle(), _localgov_get_restricted_width_content_types(), TRUE)) {
      $variables['restricted_width_content_types'][] = $node->bundle();
    }

  }

  if ($node_status === FALSE) {
    if (theme_get_setting('localgov_base_add_draft_note_to_unpublished_content') === TRUE) {
      $state = $variables['node']->get('moderation_state')->getString();
      if ($state == 'draft') {
        $variables['label'] = t('[Draft]') . " " . $variables['node']->label();
      }
    }
  }
  // We have a theme setting to prepend 'archived' to the title of
  // archived content, but we don't mind whether it is published or not.
  // An archived state does not have to always be unpubilshed.
  if (theme_get_setting('localgov_base_add_archived_note_to_archived_content') === TRUE) {
    $state = $variables['node']->get('moderation_state')->getString();
    if ($state == 'archived') {
      $variables['label'] = t('[Archived]') . " " . $variables['node']->label();
    }
  }

  if ($variables['node']->getType() === 'localgov_guides_page') {
    if (theme_get_setting('localgov_base_localgov_guides_stacked_heading') === TRUE) {
      $variables['stacked_heading'] = TRUE;
    }
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function localgov_base_preprocess_block(&$variables): void {
  $route_match = Drupal::routeMatch();
  $route = $route_match->getRouteName();
  if ($variables['plugin_id'] === 'localgov_page_header_block') {

    // Check if we are on a node page, latest version or a preview link.
    if ($route === 'entity.node.canonical' || $route === 'entity.node.latest_version' || $route === 'entity.node.preview_link') {
      $node = $route_match->getParameter('node');
      $node_status = $node->isPublished();
      $node_type = $node->getType();
      if ($node_status === FALSE) {
        if (theme_get_setting('localgov_base_add_draft_note_to_unpublished_content') === TRUE) {
          $current_title = $variables['content'][0]['#title'];
          $state = $node->get('moderation_state')->getString();
          if ($state == 'draft') {
            $variables['content'][0]['#title'] = t('[Draft]') . " " . $current_title;
          }
        }
      }

      // We have a theme setting to prepend 'archived' to the title of
      // archived content, but we don't mind whether it is published or not.
      // An archived state does not have to always be unpubilshed.
      if (theme_get_setting('localgov_base_add_archived_note_to_archived_content') === TRUE) {
        $current_title = $variables['content'][0]['#title'];
        $state = $node->get('moderation_state')->getString();
        if ($state == 'archived') {
          $variables['content'][0]['#title'] = t('[Archived]') . " " . $current_title;
        }
      }

      // Check if we should use the stacked heading pattern for guides.
      // This places the guide title on top of the node title, all within the
      // same h1 element, like the NHS design system.
      if ($node_type === 'localgov_guides_page' && theme_get_setting('localgov_base_localgov_guides_stacked_heading') === TRUE) {
        $node_title = $node->getTitle();
        $variables['content'][0]['#subtitle'] = $node_title;
      }
    }
  }

  // Add a decent aria label to the facets blocks.
  if (in_array($variables['base_plugin_id'], ['localgov_directories_channel_search_block'], TRUE)) {
    if ($variables['elements']['#configuration']['label_display'] === '0') {
      $routeMatch = \Drupal::routeMatch();
      $node = $routeMatch->getParameter('node');
      if ($node instanceof NodeInterface) {
        // If there is a localgov_directory_channels value, load that node to
        // get its title, otherwise we are already on the Directory Channel
        // node, so we can use the current node title.
        if ($node->hasField('localgov_directory_channels') && !$node->get('localgov_directory_channels')->isEmpty()) {
          $referenced_node = $node->get('localgov_directory_channels')->entity;
          $node_title = $referenced_node->getTitle();
        }
        else {
          $node_title = $node->getTitle();
        }

        $variables['attributes']['aria-label'] = t('Search') . ' ' . $node_title;
        $variables['attributes']['role'] = 'search';
      }
    }
  }

  // Add vertical or horizontal class to the guides navigation block.
  // In config, this will default to vertical for new installations, but remain
  // horizontal for existing installations.
  if ($variables['plugin_id'] === 'localgov_guides_contents') {
    if (theme_get_setting('localgov_base_localgov_guides_vertical_navigation') === TRUE) {
      $variables['attributes']['class'][] = 'block-localgov-guides-contents--navigation-vertical';
    }
    else {
      $variables['attributes']['class'][] = 'block-localgov-guides-contents--navigation-horizontal';
    }
  }

}

/**
 * Implements hook_preprocess_field().
 */
function localgov_base_preprocess_field(array &$variables): void {
  $element = $variables['element'] ?? [];
  $entity = $element['#object'] ?? NULL;

  $is_service_landing_destinations_field =
    $entity instanceof NodeInterface &&
    $entity->bundle() === 'localgov_services_landing' &&
    ($element['#field_name'] ?? NULL) === 'localgov_destinations';

  if ($is_service_landing_destinations_field) {
    $variables['localgov_base_service_landing_page_child_pages_list_style'] = theme_get_setting('localgov_base_service_landing_page_child_pages_list_style') ?: 'grid_3';
  }

  $is_service_sub_landing_topic_list_links_field = FALSE;
  if (($element['#field_name'] ?? NULL) === 'topic_list_links' && is_object($entity) && method_exists($entity, 'getEntityTypeId') && $entity->getEntityTypeId() === 'paragraph') {
    $route_node = \Drupal::routeMatch()->getParameter('node');
    if ($route_node instanceof NodeInterface && $route_node->bundle() === 'localgov_services_sublanding' && $route_node->hasField('localgov_topics')) {
      $current_paragraph_id = method_exists($entity, 'id') ? $entity->id() : NULL;
      if ($current_paragraph_id) {
        foreach ($route_node->get('localgov_topics')->referencedEntities() as $topic_paragraph) {
          if ($topic_paragraph->id() == $current_paragraph_id) {
            $is_service_sub_landing_topic_list_links_field = TRUE;
            break;
          }
        }
      }
    }
  }

  if ($is_service_sub_landing_topic_list_links_field) {
    $variables['localgov_base_service_sub_landing_page_child_pages_list_style'] = theme_get_setting('localgov_base_service_sub_landing_page_child_pages_list_style') ?: 'grid_2';
  }
}

/**
 * Implements hook_views_pre_render().
 */
function localgov_base_views_pre_render(ViewExecutable $view): void {
  if ($view->storage->id() === 'localgov_sitewide_search') {
    $view->element['#attached']['library'][] = 'localgov_base/sitewide-search';
  }
  if ($view->id() === 'service_status' && $view->current_display === 'service_status_page') {
    $view->element['#attached']['library'][] = 'localgov_base/service-statuses';
  }
}

/**
 * Implements hook_preprocess_container().
 */
function localgov_base_preprocess_container(&$variables): void {
  // See https://www.drupal.org/project/drupal/issues/1852090
  // Ensure the container has an ID.
  if (isset($variables['element']['#id'])) {
    $original_id = $variables['element']['#id'];

    // Check if the ID starts with 'edit-actions'.
    if (strpos($original_id, 'edit-actions') === 0) {
      $random_suffix = Crypt::randomBytesBase64(8);
      $random_id = $original_id . '--' . $random_suffix;

      // Keep a stable selector but allow ID randomization.
      $variables['attributes']['id'] = $random_id;
      $variables['attributes']['data-drupal-selector'] = $original_id;
      $variables['element']['#attributes']['data-drupal-selector'] = $original_id;
      $variables['element']['#id'] = $random_id;
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function localgov_base_form_preview_link_entity_form_alter(&$form, $form_state, $form_id): void {
  $form['#attached']['library'][''] = 'localgov_base/preview-link';
}

/**
 * Implements hook_preprocess_guides_prev_next_block().
 */
function localgov_base_preprocess_guides_prev_next_block(&$variables): void {
  if ($variables['previous_url'] instanceof Url) {
    $variables['previous_url']->setOption('fragment', 'lgd-guides__title');
  }
  if ($variables['next_url'] instanceof Url) {
    $variables['next_url']->setOption('fragment', 'lgd-guides__title');
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function localgov_base_preprocess_media(&$variables) {
  $media = $variables['media'];
  if ($media->bundle() === 'document') {
    $variables['#attached']['library'][] = 'claro/classy.file';
  }
}
