<?php

namespace Drupal\acquia_contenthub_publisher\Plugin\views\field;

use Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\acquia_contenthub_publisher\AddToChExportQueueTrait;
use Drupal\acquia_contenthub_publisher\ContentHubEntityEnqueuer;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Routing\ResettableStackedRouteMatchInterface;
use Drupal\depcalc\Cache\DepcalcCacheBackend;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\BulkForm;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Field handler to display checkbox for the entity.
 *
 * @ViewsField("ch_bulk_form")
 */
class EntityBulkForm extends BulkForm {

  use AddToChExportQueueTrait;

  /**
   * Constructs a new BulkForm object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin ID for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   *   The entity repository.
   * @param \Drupal\acquia_contenthub_publisher\ContentHubEntityEnqueuer $entity_enqueuer
   *   Entity enqueuer.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configurations
   *   CH configurations.
   * @param \Psr\Log\LoggerInterface $logger_channel
   *   Logger channel.
   * @param \Drupal\depcalc\Cache\DepcalcCacheBackend $depcalc_cache
   *   Depcalc cache backend.
   * @param \Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface $entity_tracker
   *   Entity tracker.
   * @param \Drupal\Core\Routing\ResettableStackedRouteMatchInterface $route_match
   *   The current route match service.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, MessengerInterface $messenger, EntityRepositoryInterface $entity_repository, ContentHubEntityEnqueuer $entity_enqueuer, ContentHubConfigurationInterface $ach_configurations, LoggerInterface $logger_channel, DepcalcCacheBackend $depcalc_cache, AcquiaContentHubEntityTrackerInterface $entity_tracker, ResettableStackedRouteMatchInterface $route_match) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $language_manager, $messenger, $entity_repository, $route_match);

    $this->entityEnqueuer = $entity_enqueuer;
    $this->achConfigurations = $ach_configurations;
    $this->logger = $logger_channel;
    $this->depcalcCache = $depcalc_cache;
    $this->entityTracker = $entity_tracker;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager'),
      $container->get('language_manager'),
      $container->get('messenger'),
      $container->get('entity.repository'),
      $container->get('acquia_contenthub_publisher.entity_enqueuer'),
      $container->get('acquia_contenthub.configuration'),
      $container->get('acquia_contenthub.logger_channel'),
      $container->get('cache.depcalc'),
      $container->get('acquia_contenthub_publisher.tracker'),
      $container->get('current_route_match'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
    FieldPluginBase::init($view, $display, $options);
  }

  /**
   * {@inheritdoc}
   */
  public function viewsForm(&$form, FormStateInterface $form_state) {
    // Make sure we do not accidentally cache this form.
    // @todo Evaluate this again in https://www.drupal.org/node/2503009.
    $form['#cache']['max-age'] = 0;

    // Add the tableselect javascript.
    $form['#attached']['library'][] = 'core/drupal.tableselect';

    // Only add the bulk form options and buttons if there are results.
    if (!empty($this->view->result)) {
      // Render checkboxes for all rows.
      $form[$this->options['id']]['#tree'] = TRUE;
      foreach ($this->view->result as $row_index => $row) {
        $entity = $this->getEntity($row);
        if ($entity !== NULL) {

          $form[$this->options['id']][$row_index] = [
            '#type' => 'checkbox',
            // We are not able to determine a main "title" for each row, so we
            // can only output a generic label.
            '#title' => $this->t('Update this item'),
            '#title_display' => 'invisible',
            '#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
            '#return_value' => $this->calculateEntityFormKey($row),
          ];
        }
        else {
          $form[$this->options['id']][$row_index] = [];
        }
      }

      // Replace the form submit button label.
      $form['actions']['submit']['#value'] = $this->t('Apply to selected items');

      // Ensure a consistent container for filters/operations in view header.
      $form['header'] = [
        '#type' => 'container',
        '#weight' => -100,
      ];

      // Build the bulk operations action widget for the header.
      // Allow themes to apply .container-inline on this separate container.
      $form['header'][$this->options['id']] = [
        '#type' => 'container',
      ];
      $form['header'][$this->options['id']]['action'] = [
        '#type' => 'select',
        '#title' => $this->options['action_title'],
        '#options' => [
          'add_to_export_queue' => $this->t('Add to export queue'),
        ],
        '#empty_option' => $this->t('- Select -'),
      ];

      // Duplicate the form actions into the action container in the header.
      $form['header'][$this->options['id']]['actions'] = $form['actions'];
    }
    else {
      // Remove the default actions build array.
      unset($form['actions']);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
    if ($form_state->get('step') == 'views_form_views_form') {
      $user_input = $form_state->getUserInput();
      $selected = array_filter($user_input[$this->options['id']]);
      $entities = [];

      foreach ($selected as $bulk_form_key) {
        $entity = $this->loadEntityFromFormKey($bulk_form_key);
        if (empty($entity)) {
          continue;
        }
        $entities[$bulk_form_key] = $entity;
      }
      $this->clearCacheAndNullifyHashes();
      $in_eligible_entities = $this->enqueueEntities(...$entities);

      $this->addStatusMessages($selected, $in_eligible_entities);
    }
  }

  /**
   * Calculates a bulk form key.
   *
   * This generates a key that is used as the checkbox return value when
   * submitting a bulk form. This key allows the entity for the row to be loaded
   * totally independent of the executed view row.
   *
   * @param \Drupal\views\ResultRow $values
   *   The entity to calculate a bulk form key for.s.
   *
   * @return string
   *   The bulk form key representing the entity's type and id.
   */
  protected function calculateEntityFormKey(ResultRow $values): string {
    $key_parts = [
      $values->acquia_contenthub_publisher_export_tracking_entity_type,
      $values->acquia_contenthub_publisher_export_tracking_entity_id,
    ];
    $key = json_encode($key_parts);
    return base64_encode($key);
  }

  /**
   * Loads an entity based on a bulk form key.
   *
   * @param string $bulk_form_key
   *   The bulk form key representing the entity's type and id.
   *
   * @return \Drupal\Core\Entity\EntityInterface|null
   *   The entity.
   */
  protected function loadEntityFromFormKey(string $bulk_form_key): ?EntityInterface {
    $key = base64_decode($bulk_form_key);
    $key_parts = json_decode($key);

    $id = array_pop($key_parts);
    $entity_type = array_pop($key_parts);

    return $this->entityTypeManager->getStorage($entity_type)->load($id);
  }

  /**
   * {@inheritdoc}
   */
  public function getEntity(ResultRow $values) {
    if (is_null($values->acquia_contenthub_publisher_export_tracking_entity_id) || is_null($values->acquia_contenthub_publisher_export_tracking_entity_type)) {
      return NULL;
    }
    return $this->entityTypeManager->getStorage($values->acquia_contenthub_publisher_export_tracking_entity_type)->load($values->acquia_contenthub_publisher_export_tracking_entity_id);
  }

  /**
   * {@inheritdoc}
   */
  public function query() {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function isWorkspaceSafeForm(array $form, FormStateInterface $form_state): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityTypeId() {
    return '';
  }

}
