<?php

/**
 * @file
 * Contains entity_usage.module.
 */

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\entity_usage\EntityUpdateManagerInterface;
use Drupal\entity_usage\PreSaveUrlRecorder;
use Drupal\field\FieldStorageConfigInterface;

/**
 * Implements hook_help().
 */
function entity_usage_help(string $route_name, RouteMatchInterface $route_match): string {
  switch ($route_name) {
    // Main module help for the entity_usage module.
    case 'help.page.entity_usage':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Track usage of entities referenced by other entities.') . '</p>';
      return $output;

    default:
      return '';
  }
}

/**
 * Implements hook_entity_presave().
 */
function entity_usage_entity_presave(EntityInterface $entity): void {
  if (!$entity->isNew() && ($entity->hasLinkTemplate('canonical') || $entity->hasLinkTemplate('edit-form'))) {
    $enabled_target_entity_types = \Drupal::config('track_enabled_target_entity_types');
    // Every entity type is tracked if not set.
    if (!is_array($enabled_target_entity_types) || in_array($entity->getEntityTypeId(), $enabled_target_entity_types, TRUE)) {
      \Drupal::service(PreSaveUrlRecorder::class)->recordEntity($entity);
    }
  }
}

/**
 * Implements hook_entity_insert().
 */
function entity_usage_entity_insert(EntityInterface $entity): void {
  \Drupal::service('entity_usage.entity_update_manager')->trackUpdateOnCreation($entity);
}

/**
 * Implements hook_entity_update().
 */
function entity_usage_entity_update(EntityInterface $entity): void {
  $entity_usage_update_manager = \Drupal::service('entity_usage.entity_update_manager');
  assert($entity_usage_update_manager instanceof EntityUpdateManagerInterface);
  $entity_usage_update_manager->trackUpdateOnEdition($entity);
  // The previous URL is no longer needed so remove it to save memory in case
  // this is a part of a long-running process updating a large number of
  // entities.
  \Drupal::service(PreSaveUrlRecorder::class)->removeEntity($entity);
}

/**
 * Implements hook_entity_predelete().
 */
function entity_usage_entity_predelete(EntityInterface $entity): void {
  $entity_usage_update_manager = \Drupal::service('entity_usage.entity_update_manager');
  assert($entity_usage_update_manager instanceof EntityUpdateManagerInterface);
  $entity_usage_update_manager->trackUpdateOnDeletion($entity);
}

/**
 * Implements hook_entity_translation_delete().
 */
function entity_usage_entity_translation_delete(EntityInterface $translation): void {
  $entity_usage_update_manager = \Drupal::service('entity_usage.entity_update_manager');
  assert($entity_usage_update_manager instanceof EntityUpdateManagerInterface);
  $entity_usage_update_manager->trackUpdateOnDeletion($translation, 'translation');
}

/**
 * Implements hook_entity_revision_delete().
 */
function entity_usage_entity_revision_delete(EntityInterface $entity): void {
  $entity_usage_update_manager = \Drupal::service('entity_usage.entity_update_manager');
  assert($entity_usage_update_manager instanceof EntityUpdateManagerInterface);
  $entity_usage_update_manager->trackUpdateOnDeletion($entity, 'revision');
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function entity_usage_field_storage_config_delete(FieldStorageConfigInterface $field): void {
  // Delete all usages tracked through this field.
  \Drupal::service('entity_usage.usage')->deleteByField($field->getTargetEntityTypeId(), $field->getName());
}

/**
 * Implements hook_form_alter().
 */
function entity_usage_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
  $form_object = $form_state->getFormObject();
  if (!$form_object instanceof EntityFormInterface) {
    return;
  }
  /** @var \Drupal\Core\Entity\EntityInterface $entity */
  $entity = $form_object->getEntity();
  if (empty($entity)) {
    return;
  }
  $entity_type_id = $entity->getEntityTypeId();
  $config = \Drupal::config('entity_usage.settings');

  // Add the configuration cache tag to rebuild forms when the config changes.
  $metadata = CacheableMetadata::createFromRenderArray($form);
  $metadata = $metadata->merge(CacheableMetadata::createFromObject($config));
  $metadata->applyTo($form);

  $edit_entity_types = $config->get('edit_warning_message_entity_types') ?: [];
  $delete_entity_types = $config->get('delete_warning_message_entity_types') ?: [];
  // Abort early if this entity is not configured to show any message.
  if (!in_array($entity_type_id, $edit_entity_types) && !in_array($entity_type_id, $delete_entity_types)) {
    return;
  }
  $is_edit_form = $form_object->getOperation() === 'edit' && in_array($entity_type_id, $edit_entity_types);
  $is_delete_form = FALSE;
  if (!$is_edit_form && in_array($entity_type_id, $delete_entity_types)) {
    // Even if this is not on the UI, sites can define additional form classes
    // where the delete message can be shown.
    $form_classes = $config->get('delete_warning_form_classes') ?: ['Drupal\Core\Entity\ContentEntityDeleteForm'];
    foreach ($form_classes as $class) {
      if ($form_object instanceof $class) {
        $is_delete_form = TRUE;
        break;
      }
    }
  }
  if (!$is_edit_form && !$is_delete_form) {
    return;
  }

  // As we now depend on usage data these forms are uncacheable.
  $metadata = $metadata->setCacheMaxAge(0);
  $metadata->applyTo($form);
  $usage_data = \Drupal::service('entity_usage.usage')->listSources($entity);
  if (empty($usage_data)) {
    return;
  }

  $local_task_entity_types = $config->get('local_task_enabled_entity_types') ?: [];
  $usage_url = in_array($entity_type_id, $local_task_entity_types, TRUE) ?
    Url::fromRoute("entity.$entity_type_id.entity_usage", [
      $entity_type_id => $entity->id(),
    ]) :
    Url::fromRoute('entity_usage.usage_list', [
      'entity_type' => $entity_type_id,
      'entity_id' => $entity->id(),
    ]);

  // Check for the edit warning.
  if ($is_edit_form) {
    $form['entity_usage_edit_warning'] = [
      '#theme' => 'status_messages',
      '#message_list' => [
        'warning' => [
          t('Modifications on this form will affect all <a href="@usage_url" target="_blank">existing usages</a> of this entity.', [
            '@usage_url' => $usage_url->toString(),
          ]),
        ],
      ],
      '#status_headings' => ['warning' => t('Warning message')],
      '#weight' => -201,
    ];
  }
  // Check for the delete warning.
  elseif ($is_delete_form) {
    $form['entity_usage_delete_warning'] = [
      '#theme' => 'status_messages',
      '#message_list' => [
        'warning' => [
          t('There are <a href="@usage_url" target="_blank">recorded usages</a> of this entity.', [
            '@usage_url' => $usage_url->toString(),
          ]),
        ],
      ],
      '#status_headings' => ['warning' => t('Warning message')],
      '#weight' => -201,
    ];
  }
}
