<?php

/**
 * @file
 * The hook implementation for the entity extra field module.
 */

declare(strict_types=1);

use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\entity_extra_field\Entity\EntityExtraFieldInterface;

/**
 * Implements hook_entity_extra_field_info().
 */
function entity_extra_field_entity_extra_field_info(): array {
  $field_info = [];

  foreach (entity_extra_field_storage()->loadMultiple() as $extra_field) {
    if (!$extra_field instanceof EntityExtraFieldInterface) {
      continue;
    }
    $display_type = $extra_field->getDisplayType();

    if (!in_array($display_type, ['view', 'form'])) {
      continue;
    }
    $display_type = $display_type === 'view' ? 'display' : $display_type;

    $field_name = $extra_field->name();
    $entity_type_id = $extra_field->getBaseEntityTypeId();
    $entity_bundle_id = $extra_field->getBaseBundleTypeId();

    $field_info[$entity_type_id][$entity_bundle_id][$display_type][$field_name] = [
      'label' => $extra_field->label(),
      'description' => $extra_field->description(),
      'weight' => 0,
    ];
  }

  return $field_info;
}

/**
 * Implements hook_entity_view().
 */
function entity_extra_field_entity_view(
  array &$build,
  EntityInterface $entity,
  EntityViewDisplayInterface $display
): void {
  entity_extra_field_display('view', $build, $entity, $display);
}

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

  if (!$form_object instanceof ContentEntityFormInterface) {
    return;
  }
  $entity = $form_object->getEntity();
  $display = $form_object->getFormDisplay($form_state);

  entity_extra_field_display('form', $form, $entity, $display);
}

/**
 * Implements hook_theme().
 */
function entity_extra_field_theme($existing, $type, $theme, $path): array {
  return [
    'entity_extra_field' => [
      'render element' => 'element',
      'path' => $path . '/templates',
      'file' => 'entity_extra_field.theme',
      'template' => 'entity-extra-field',
    ],
  ];
}

/**
 * Entity extra field display.
 *
 * @param string $type
 *   The display type, (e.g. view, form).
 * @param array $build
 *   An array of elements to attach the extra field.
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   An entity instance.
 * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
 *   An entity display instance.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 */
function entity_extra_field_display(
  string $type,
  array &$build,
  EntityInterface $entity,
  EntityDisplayInterface $display
): void {
  $storage = entity_extra_field_storage();

  $extra_field_ids = $storage->getQuery()
    ->condition('display.type', $type)
    ->condition('base_bundle_type_id', $entity->bundle())
    ->condition('base_entity_type_id', $entity->getEntityTypeId())
    ->execute();

  if (!empty($extra_field_ids)) {
    $contexts = [
      'entity_extra_field.target_entity' => EntityContext::fromEntity($entity)
    ];
    $extra_fields = $storage->loadMultiple(array_values($extra_field_ids));

    /** @var \Drupal\entity_extra_field\Entity\EntityExtraField $extra_field */
    foreach ($extra_fields as $extra_field) {
      $all_conditions_pass = $extra_field->getFieldTypeConditionsAllPass();

      if (!$extra_field instanceof EntityExtraFieldInterface
        || !$extra_field->hasDisplayComponent($display)
        || !$extra_field->hasConditionsBeenMet($contexts, $all_conditions_pass)) {
        continue;
      }
      $extra_field_build = $extra_field->build($entity, $display);

      if (!isset($extra_field_build['content'])
        || empty($extra_field_build['content'])) {
        continue;
      }
      $build[$extra_field->name()] = $extra_field_build;

      if ($attachments = $extra_field->getBuildAttachments()) {
        $build[$extra_field->name()]['#attached'] = $attachments;
      }

      (CacheableMetadata::createFromObject($extra_field))
        ->addCacheTags([
          'entity_extra_field',
          "entity_extra_field:{$type}.{$entity->getEntityTypeId()}.{$entity->bundle()}",
        ])->applyTo($build[$extra_field->name()]);
    }
  }
}

/**
 * Get entity extra field storage.
 *
 * @return \Drupal\Core\Entity\EntityStorageInterface
 *   The entity extra field storage.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function entity_extra_field_storage(): EntityStorageInterface {
  return \Drupal::entityTypeManager()->getStorage('entity_extra_field');
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function entity_extra_field_theme_suggestions_entity_extra_field(
  array $variables
): array {
  $original = $variables['theme_hook_original'];
  $view_mode = $variables['element']['#view_mode'];

  $field = $variables['element']['#field'];
  $entity_type_id = $field->getBaseEntityTypeId();
  $bundle_id = $field->getBaseBundleTypeId();
  $field_name = $field->name();

  $base_suggestion = $original . '__' . $entity_type_id . '__' . $bundle_id;

  $suggestions = [];
  $suggestions[] = $base_suggestion;
  $suggestions[] = $base_suggestion . '__' . $field_name;
  $suggestions[] = $base_suggestion . '__' . $field_name . '__' . $view_mode;

  return $suggestions;
}

/**
 * Get the context repository.
 *
 * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface
 *   The context repository service.
 */
function contextRepository(): ContextRepositoryInterface {
  return \Drupal::service('context.repository');
}
