<?php

/**
 * @file
 * Contains farm_entity.module.
 */

use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\entity\EntityAccessControlHandler;
use Drupal\entity\EntityPermissionProvider;
use Drupal\farm_entity\BundlePlugin\FarmEntityBundlePluginHandler;
use Drupal\farm_entity\Routing\DefaultHtmlRouteProvider;

/**
 * Implements hook_module_implements_alter().
 */
function farm_entity_module_implements_alter(&$implementations, $hook) {

  // Make sure this module's hook_entity_type_build() runs before the
  // entity module's implementation, so that we can override the bundle plugin
  // handler, and so that we can set the Log entity type's bundle_plugin_type.
  $module = 'farm_entity';
  if ($hook == 'entity_type_build') {
    $implementation = [$module => $implementations[$module]];
    unset($implementations[$module]);
    $implementations = array_merge($implementation, $implementations);
  }
}

/**
 * Implements hook_entity_type_build().
 */
function farm_entity_entity_type_build(array &$entity_types) {
  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */

  // Allow the "view label" operation on the bundle entity type.
  foreach (['asset', 'log', 'plan', 'quantity', 'data_stream'] as $entity_type) {
    if (!empty($entity_types[$entity_type])) {
      $bundle_entity_type = $entity_types[$entity_type]->getBundleEntityType();
      $entity_types[$bundle_entity_type]->setHandlerClass('access', EntityAccessControlHandler::class);
      $entity_types[$bundle_entity_type]->setHandlerClass('permission_provider', EntityPermissionProvider::class);
    }
  }

  // Enable the use of bundle plugins on specific entity types.
  foreach (['asset', 'log', 'plan', 'plan_record', 'quantity'] as $entity_type) {
    if (!empty($entity_types[$entity_type])) {
      $entity_types[$entity_type]->set('bundle_plugin_type', $entity_type . '_type');
      $entity_types[$entity_type]->setHandlerClass('bundle_plugin', FarmEntityBundlePluginHandler::class);

      // Deny access to the entity type add form. New entity types of entities
      // with bundle plugins cannot be created in the UI.
      // See https://www.drupal.org/project/farm/issues/3196423
      $bundle_entity_type = $entity_types[$entity_type]->getBundleEntityType();
      $route_providers = $entity_types[$bundle_entity_type]->getRouteProviderClasses();
      $route_providers['default'] = DefaultHtmlRouteProvider::class;
      $entity_types[$bundle_entity_type]->setHandlerClass('route_provider', $route_providers);
    }
  }
}

/**
 * Implements hook_entity_field_storage_info_alter().
 *
 * @todo https://www.drupal.org/project/farm/issues/3194206
 */
function farm_entity_entity_field_storage_info_alter(&$fields, EntityTypeInterface $entity_type) {

  // Bail if not a farm entity type that allows bundle plugins.
  if (!in_array($entity_type->id(), ['log', 'asset', 'plan', 'quantity'])) {
    return;
  }

  // Get all bundles of the entity type.
  $bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type->id());

  // Invoke hook_farm_entity_bundle_field_info() with each bundle.
  $hook = 'farm_entity_bundle_field_info';
  foreach (array_keys($bundles) as $bundle) {
    \Drupal::moduleHandler()->invokeAllWith($hook, function (callable $hook, string $module) use ($fields, $entity_type, $bundle) {

      // Get bundle field definitions provided by the module.
      $definitions = $hook($entity_type, $bundle);

      // Set the provider for each field the module provided.
      // This is required so that field storage definitions are created in the
      // database when the module is installed.
      foreach (array_keys($definitions) as $field) {
        if (isset($fields[$field])) {
          $fields[$field]->setProvider($module);
        }
      }
    });
  }
}

/**
 * Implements hook_entity_presave().
 *
 * Forces revisions on all farm entities if the entity type supports them and
 * the bundle has them enabled. This removes the option for users to disable a
 * revision per-entity but as JSON:API doesn't support revisions yet, this is a
 * trade-off that allows us to create revisions consistently on both the UI and
 * the API.
 */
function farm_entity_entity_presave(EntityInterface $entity) {

  // Only apply to farm controlled entities.
  $entity_types = [
    'asset',
    'log',
    'plan',
    'quantity',
  ];
  if (!in_array($entity->getEntityTypeId(), $entity_types)) {
    return;
  }

  // Force create new revision as json api doesn't do that by default.
  // @see https://www.drupal.org/project/drupal/issues/2993557
  // @see https://www.drupal.org/project/drupal/issues/2795279
  // @see https://github.com/json-api/json-api/pull/824
  if ($entity->type->entity->shouldCreateNewRevision() && $entity->getEntityType()->isRevisionable()) {
    /** @var \Drupal\Core\Entity\RevisionLogInterface $entity */

    // Always create a new revision.
    $entity->setNewRevision(TRUE);

    // If the new revision log message matches the original, then set a blank
    // revision log message. We don't want the same message repeated across
    // every revision created by the API.
    if (!empty($entity->original)) {
      if ($entity->original->get('revision_log_message')->value == $entity->get('revision_log_message')->value) {
        $entity->setRevisionLogMessage('');
      }
    }

    // Set the user ID and creation time.
    $entity->setRevisionUserId(\Drupal::currentUser()->getAccount()->id());
    $entity->setRevisionCreationTime(\Drupal::time()->getRequestTime());
  }
}

/**
 * Implements hook_form_alter().
 *
 * Hides the revision control from the user, @see farm_entity_entity_presave()
 */
function farm_entity_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Only alter content entity forms.
  $form_object = $form_state->getFormObject();
  if (!($form_object instanceof ContentEntityFormInterface)) {
    return;
  }

  // Only apply to farm controlled entities.
  $entity = $form_object->getEntity();
  $entity_types = [
    'asset',
    'log',
    'plan',
    'quantity',
  ];
  if (!in_array($entity->getEntityTypeId(), $entity_types)) {
    return;
  }

  // Disable access to the revision checkbox.
  $form['revision']['#access'] = FALSE;
}
