<?php

/**
 * @file
 * The farmOS Flags module.
 */

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\farm_flag\Form\EntityFlagActionForm;
use Drupal\farm_flag\Routing\EntityFlagActionRouteProvider;

/**
 * Implements hook_entity_base_field_info().
 */
function farm_flag_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];

  // Add flag field to farmOS entities.
  if (in_array($entity_type->id(), ['asset', 'log', 'plan'])) {
    $field_info = [
      'type' => 'list_string',
      'label' => t('Flags'),
      'description' => t('Add flags to enable better sorting and filtering of records.'),
      'allowed_values_function' => 'farm_flag_field_allowed_values',
      'multiple' => TRUE,
      'weight' => [
        'form' => -75,
        'view' => -75,
      ],
    ];
    $fields['flag'] = \Drupal::service('farm_field.factory')->baseFieldDefinition($field_info);
  }

  return $fields;
}

/**
 * Flag options helper.
 *
 * @param string|null $entity_type
 *   The entity type. Returns all flags if NULL.
 * @param string[] $bundles
 *   Array of bundle ids to limit to. An empty array loads all bundles.
 * @param bool $intersection
 *   A flag indicating to return an intersection of the allowed options.
 *
 * @return array
 *   Returns an array of flags for use in form select options.
 */
function farm_flag_options(string $entity_type = NULL, array $bundles = [], bool $intersection = FALSE) {
  /** @var \Drupal\farm_flag\Entity\FarmFlagInterface[] $flags */
  $flags = \Drupal::entityTypeManager()->getStorage('flag')->loadMultiple();

  // If an entity type is provided, begin the filtering process...
  if (!empty($entity_type)) {

    // If no bundles are specified, load all bundles of the entity type.
    if (empty($bundles) && $bundle_entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type)->getBundleEntityType()) {
      $bundles = array_keys(\Drupal::entityTypeManager()
        ->getStorage($bundle_entity_type)
        ->loadMultiple());
    }

    // Find only the flags that apply to the entity type and bundles.
    $flags = array_filter($flags, function ($flag) use ($entity_type, $bundles, $intersection) {
      $flag_entity_types = $flag->getEntityTypes();

      // The flag applies if no entity type is specified.
      if (empty($flag_entity_types)) {
        return TRUE;
      }

      // Otherwise the flag must specify the entity type.
      if (!array_key_exists($entity_type, $flag_entity_types)) {
        return FALSE;
      }

      // The flag applies to the bundle if:
      // Case 1: The flag specifies 'all' bundles of the entity type.
      $bundle_applies = in_array('all', $flag_entity_types[$entity_type]);

      // Case 2: No intersection.
      // The flag applies if any of the requested bundles are supported.
      $bundle_applies |= !$intersection && !empty(array_intersect($bundles, $flag_entity_types[$entity_type]));

      // Case 3: Intersection.
      // The flag only applies if all the requested bundles are supported.
      $bundle_applies |= $intersection && empty(array_diff($bundles, $flag_entity_types[$entity_type]));

      return $bundle_applies;
    });
  }

  // Assemble the options.
  $options = [];
  foreach ($flags as $id => $flag) {
    $options[$id] = $flag->label();
  }
  return $options;
}

/**
 * Allowed values callback function for the flags field.
 *
 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
 *   The field storage definition.
 * @param \Drupal\Core\Entity\ContentEntityInterface|null $entity
 *   The entity being created if applicable.
 * @param bool $cacheable
 *   Boolean indicating if the allowed values can be cached. Defaults to TRUE.
 *
 * @return array
 *   Returns an array of allowed values for use in form select options.
 */
function farm_flag_field_allowed_values(FieldStorageDefinitionInterface $definition, ContentEntityInterface $entity = NULL, bool &$cacheable = TRUE) {
  $entity_type = NULL;
  $bundles = [];
  if (!empty($entity)) {
    $cacheable = FALSE;
    $entity_type = $entity->getEntityTypeId();
    $bundles = [$entity->bundle()];
  }
  return farm_flag_options($entity_type, $bundles);
}

/**
 * Implements hook_theme().
 */
function farm_flag_theme() {
  return [
    'field__flag' => [
      'base hook' => 'field',
    ],
  ];
}

/**
 * Prepares variables for field--flag templates.
 *
 * Adds classes to each flag wrapper.
 *
 * Default template: field--flag.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - element: An associative array containing render arrays for the list of
 *     flags.
 */
function template_preprocess_field__flag(array &$variables) {

  // Preprocess list_string flag fields.
  if ($variables['element']['#field_type'] == 'list_string') {

    /** @var \Drupal\Core\Field\FieldItemListInterface $items */
    $items = $variables['element']['#items'];

    // Add classes to each flag.
    foreach ($items as $key => $list_item) {
      $classes = ['flag', 'flag--' . $list_item->getString()];
      $variables['items'][$key]['attributes']->addClass($classes);
    }
  }
}

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

  // Enable the entity flag action on entity types with a flag field.
  foreach (['asset', 'log', 'plan'] as $entity_type) {
    if (!empty($entity_types[$entity_type])) {
      $route_providers = $entity_types[$entity_type]->getRouteProviderClasses();
      $route_providers['flag'] = EntityFlagActionRouteProvider::class;
      $entity_types[$entity_type]->setHandlerClass('route_provider', $route_providers);
      $entity_types[$entity_type]->setLinkTemplate('flag-action-form', '/' . $entity_type . '/flag');
      $entity_types[$entity_type]->setFormClass('flag-action-form', EntityFlagActionForm::class);
    }
  }
}
