<?php

/**
 * @file
 * Contains core functionality for the Lightning distribution.
 */

use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Entity\EntityDisplayModeInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\NodeTypeInterface;
use Drupal\path\Plugin\Field\FieldType\PathFieldItemList;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;

/**
 * Implements hook_help().
 */
function lightning_core_help($route_name, RouteMatchInterface $route_match) {
  $matched = [];
  // Parse the route name to figure out what display mode we're looking at:
  //
  // 0 = entire string
  // 1 = view || form
  // 2 = entity type
  // 3 = view_mode || form_mode
  // 4 = view || form
  $expr = '/^entity\.entity_(view|form)_display\.([a-z_]+)\.((view|form)_mode)$/';

  if (preg_match($expr, $route_name, $matched)) {
    $entity_id = $matched[2] . '.' . $route_match->getParameter($matched[3] . '_name');

    $description = \Drupal::entityTypeManager()
      ->getStorage('entity_' . $matched[3])
      ->load($entity_id)
      ->getThirdPartySetting('lightning_core', 'description');

    if ($description) {
      return '<p>' . $description . '</p>';
    }
  }
}

/**
 * Implements hook_entity_load().
 */
function lightning_core_entity_load(array $entities, $entity_type_id) {
  /** @var \Drupal\Core\Path\AliasStorageInterface $alias_storage */
  $alias_storage = \Drupal::service('path.alias_storage');

  foreach ($entities as $entity) {
    // If the entity has an empty path field, try to set its value. Amazingly,
    // Path does not do this on its freaking own.
    if ($entity instanceof FieldableEntityInterface && $entity->hasField('path') && $entity->path instanceof PathFieldItemList && $entity->path->isEmpty()) {
      $alias = $alias_storage->load([
        'source' => '/' . $entity->toUrl()->getInternalPath(),
      ]);

      if ($alias) {
        $entity->path->setValue($alias);
      }
    }
  }
}

/**
 * Puts an associative array into an arbitrary order.
 *
 * @param array $values
 *   The array to reorder.
 * @param array $keys
 *   The keys, in their desired order.
 */
function array_reorder(array &$values, array $keys) {
  $keys = array_values($keys);

  uksort($values, function ($a, $b) use ($keys) {
    return array_search($a, $keys) - array_search($b, $keys);
  });
}

/**
 * Pre-render function to disable all buttons in a renderable element.
 *
 * @param array $element
 *   The renderable element.
 *
 * @return array
 *   The renderable element with all buttons (at all levels) disabled.
 *
 * @internal
 */
function lightning_core_disable_buttons(array $element) {
  if (isset($element['#type'])) {
    $element['#access'] = !in_array($element['#type'], [
      'button',
      'submit',
      'image_button',
    ]);
  }

  // Recurse into child elements.
  foreach (Element::children($element) as $key) {
    if (is_array($element[$key])) {
      $element[$key] = call_user_func(__FUNCTION__, $element[$key]);
    }
  }
  return $element;
}

/**
 * Implements hook_form_alter().
 */
function lightning_core_form_alter(array &$form, FormStateInterface $form_state) {
  $form_object = $form_state->getFormObject();

  if ($form_object instanceof EntityFormInterface) {
    $entity = $form_object->getEntity();

    if ($entity instanceof EntityDisplayModeInterface || $entity instanceof RoleInterface) {
      $form['description'] = [
        '#type' => 'textarea',
        '#title' => t('Description'),
        '#description' => t('Additional relevant information about this @entity_type, such as where it is used and what it is for.', [
          '@entity_type' => $entity->getEntityType()->getSingularLabel(),
        ]),
        '#rows' => 2,
        '#default_value' => $entity->getThirdPartySetting('lightning_core', 'description'),
      ];
      $form['actions']['submit']['#submit'][] = 'lightning_core_set_third_party_description';
    }
  }
}

/**
 * Implements hook_block_view_alter().
 */
function lightning_core_block_view_alter(array &$build, BlockPluginInterface $block) {
  \Drupal::service('renderer')->addCacheableDependency($build, $block);

  // Always add block_view:BASE_PLUGIN_ID as a cache tag.
  $build['#cache']['tags'][] = 'block_view:' . $block->getBaseId();

  // If the plugin is a derivative, add block_view:FULL_PLUGIN_ID as well.
  if ($block->getDerivativeId()) {
    $build['#cache']['tags'][] = 'block_view:' . $block->getPluginId();
  }
}

/**
 * Form submit handler: updates the description of a configuration entity.
 *
 * @param array $form
 *   The complete form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The current form state.
 */
function lightning_core_set_third_party_description(array &$form, FormStateInterface $form_state) {
  $form_state
    ->getFormObject()
    ->getEntity()
    ->setThirdPartySetting('lightning_core', 'description', $form_state->getValue('description'))
    ->save();

  // The help text block is very likely to be render cached, so invalidate the
  // relevant cache tag. See lightning_core_block_view_alter() and
  // lightning_core_help().
  \Drupal::service('cache_tags.invalidator')->invalidateTags(['block_view:help_block']);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function lightning_core_form_system_modules_alter(array &$form) {
  $package = 'Lightning (Experimental)';

  if (isset($form['modules'][$package])) {
    $warning = t('Here may be dragons! Please be sure you <a href="@url">understand the potential risks</a> of experimental extensions before enabling one.', [
      '@url' => 'http://lightning.acquia.com/lightning-experimental-modules',
    ]);
    $form['modules'][$package]['#description'] = '<p>' . $warning . '</p>';
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function lightning_core_form_user_form_alter(array &$form) {
  if (isset($form['account']['roles'])) {
    $element = &$form['account']['roles'];

    // Always ensure the standard #process function(s) run first.
    if (empty($element['#process'])) {
      $element_info = \Drupal::service('plugin.manager.element_info')
        ->createInstance($element['#type'])
        ->getInfo();

      if (isset($element_info['#process'])) {
        $element['#process'] = $element_info['#process'];
      }
    }
    $form['account']['roles']['#process'][] = 'lightning_core_set_role_descriptions';
  }
}

/**
 * Element process function: sets descriptions for role checkboxes on user_form.
 *
 * @param array $element
 *   The element to process.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The current form state.
 * @param array $complete_form
 *   The complete form.
 *
 * @return array
 *   The processed element.
 */
function lightning_core_set_role_descriptions(array $element, FormStateInterface $form_state, array &$complete_form) {
  // Try to add a description for each individual role.
  foreach (Element::children($element) as $role) {
    // Don't overwrite any existing description.
    if (empty($element[$role]['#description'])) {
      $element[$role]['#description'] = Role::load($role)
        ->getThirdPartySetting('lightning_core', 'description');
    }
  }
  return $element;
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function lightning_core_node_type_insert(NodeTypeInterface $node_type) {
  $config = \Drupal::config('lightning_core.settings')->get('content_roles');
  if (empty($config)) {
    return;
  }

  $permission_map = function ($permission) use ($node_type) {
    return str_replace('?', $node_type->id(), $permission);
  };

  foreach ($config as $key => $info) {
    if ($info['enabled']) {
      Role::create([
        'id' => $node_type->id() . '_' . $key,
        'label' => str_replace('?', $node_type->label(), $info['label']),
        'permissions' => array_map($permission_map, $info['permissions']),
      ])->save();
    }
  }

  user_role_grant_permissions('content_manager', [
    'create ' . $node_type->id() . ' content',
    'delete any ' . $node_type->id() . ' content',
    'edit any ' . $node_type->id() . ' content',
  ]);
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function lightning_core_node_type_delete(NodeTypeInterface $node_type) {
  $config = \Drupal::config('lightning_core.settings')->get('content_roles');

  foreach (array_keys($config) as $key) {
    $role = Role::load($node_type->id() . '_' . $key);
    if ($role) {
      $role->delete();
    }
  }

  $role = Role::load('content_manager');
  if ($role) {
    user_role_revoke_permissions($role->id(), [
      'create ' . $node_type->id() . ' content',
      'delete any ' . $node_type->id() . ' content',
      'edit any ' . $node_type->id() . ' content',
    ]);
  }
}

/**
 * Rebuilds the service container.
 */
function lightning_core_rebuild_container() {
  require_once \Drupal::root() . '/core/includes/utility.inc';
  $class_loader = \Drupal::service('class_loader');
  $request = \Drupal::request();
  drupal_rebuild($class_loader, $request);
}

/**
 * Implements template_preprocess_block().
 */
function lightning_core_preprocess_block(array &$variables) {
  $variables['attributes']['data-block-plugin-id'] = $variables['elements']['#plugin_id'];
}

/**
 * Creates a config entity from default configuration.
 *
 * @param string $entity_type
 *   The config entity type ID.
 * @param string $id
 *   The unprefixed entity ID.
 * @param string $module
 *   (optional) The module which has the default configuration.
 */
function lightning_core_create_config($entity_type, $id, $module = 'lightning_core') {
  $values = lightning_core_read_config(
    \Drupal::entityTypeManager()->getDefinition($entity_type)->getConfigPrefix() . '.' . $id,
    $module
  );
  if ($values) {
    \Drupal::entityTypeManager()
      ->getStorage($entity_type)
      ->create($values)
      ->save();
  }
}

/**
 * Reads a stored config file from a module's config/install directory.
 *
 * @param string $id
 *   The config ID.
 * @param string $module
 *   (optional) The module to search. Defaults to 'lightning_core'.
 *
 * @return array
 *   The config data.
 */
function lightning_core_read_config($id, $module = 'lightning_core') {
  // Statically cache all FileStorage objects, keyed by module.
  static $storage = [];

  if (empty($storage[$module])) {
    $dir = \Drupal::service('module_handler')->getModule($module)->getPath();
    $storage[$module] = new FileStorage($dir . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY);
  }
  return $storage[$module]->read($id);
}
