<?php

namespace Drupal\eca\Plugin\ECA\Event;

use Drupal\Core\Form\FormStateInterface;
use Drupal\eca\Attribute\Token;
use Drupal\eca\Entity\Objects\EcaEvent;
use Drupal\eca\Plugin\DataType\DataTransferObject;
use Drupal\eca\Plugin\ECA\EcaPluginBase;
use Drupal\eca\Plugin\ECA\PluginFormTrait;
use Drupal\eca\Token\Browser;
use Drupal\eca\Token\TokenInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * Base class for ECA event plugins.
 */
abstract class EventBase extends EcaPluginBase implements EventInterface {

  use PluginFormTrait;

  /**
   * According system event, if available.
   *
   * @var \Symfony\Contracts\EventDispatcher\Event|null
   */
  protected ?Event $event = NULL;

  /**
   * An instance holding event data accessible as Token.
   *
   * @var \Drupal\eca\Plugin\DataType\DataTransferObject|null
   */
  protected ?DataTransferObject $eventData = NULL;

  /**
   * The list of tokens that the event provides when dispatched.
   *
   * @var \Drupal\eca\Attribute\Token[]|null
   */
  protected ?array $tokens = NULL;

  /**
   * The event dispatcher service.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected EventDispatcherInterface $eventDispatcher;

  /**
   * ECA token service.
   *
   * @var \Drupal\eca\Token\TokenInterface
   */
  protected TokenInterface $token;

  /**
   * ECA token browser.
   *
   * @var \Drupal\eca\Token\Browser
   */
  protected Browser $tokenBrowser;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $instance->setConfiguration($configuration);
    $instance->eventDispatcher = $container->get('event_dispatcher');
    $instance->token = $container->get('eca.service.token');
    $instance->tokenBrowser = $container->get('eca.token_browser');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  final public function eventClass(): string {
    return $this->pluginDefinition['event_class'];
  }

  /**
   * {@inheritdoc}
   */
  final public function eventName(): string {
    return $this->pluginDefinition['event_name'];
  }

  /**
   * {@inheritdoc}
   */
  final public function subscriberPriority(): int {
    return $this->pluginDefinition['subscriber_priority'];
  }

  /**
   * {@inheritdoc}
   */
  public function generateWildcard(string $eca_config_id, EcaEvent $ecaEvent): string {
    // By default return a small wildcard that should match up for every event
    // that is of the class as returned by ::drupalEventClass.
    return '*';
  }

  /**
   * {@inheritdoc}
   *
   * @throws \InvalidArgumentException
   *   When the given wildcard does not match up with the expected pattern as
   *   generated by ::generateWildcard.
   */
  public static function appliesForWildcard(Event $event, string $event_name, string $wildcard): bool {
    // This default implementation should not be called as a parent method
    // from a child class. When a child class implements this method on its own,
    // it must contain solely the concrete appliance logic of the plugin itself.
    if ($wildcard !== '*') {
      throw new \InvalidArgumentException('The given wildcard is different than expected. If the plugin implements its own appliance logic, the parent method must not be invoked.');
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function setEvent(Event $event): EventInterface {
    $this->event = $event;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getEvent(): ?Event {
    return $this->event;
  }

  /**
   * {@inheritdoc}
   */
  public function getTokens(): array {
    if ($this->tokens === NULL) {
      $this->tokens = $this->tokenBrowser->getSupportedTokens($this::class, $this->eventClass());
    }
    return $this->tokens;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration(): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getConfiguration(): array {
    return $this->configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration): void {
    $this->configuration = $configuration + $this->defaultConfiguration();
  }

  /**
   * Helper function to render a supported token and its properties.
   *
   * @param \Drupal\eca\Attribute\Token $token
   *   The token.
   * @param string $prefix
   *   The prefix.
   *
   * @return array
   *   The list of rendered tokens and properties.
   */
  private function renderToken(Token $token, string $prefix = ''): array {
    $tokens = [];
    $name = $prefix . $token->name;
    $tokens[] = '<p><strong>[' . $name . ']:</strong> ' . $token->description . ($token->aliases ? '<br/>(Alias: ' . implode(', ', $token->aliases) . ')' : '') . '</p>';
    foreach ($token->properties as $property) {
      $tokens += $this->renderToken($property, $name);
    }
    return $tokens;
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
    $i = 0;
    $tokens = $this->getTokens();
    if ($tokens) {
      $form['eca_token_header'] = [
        '#markup' => '<h3>' . $this->t('Provided Tokens') . '</h3>',
      ];
      foreach ($tokens as $token) {
        foreach ($this->renderToken($token) as $item) {
          $i++;
          $form['eca_token_' . $i] = [
            '#markup' => $item,
            '#weight' => 900 + $i,
          ];
        }
      }
    }
    return $this->updateConfigurationForm($form);
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void {}

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {}

  /**
   * {@inheritdoc}
   */
  public function getData(string $key): mixed {
    if ($key === 'event') {
      if (!isset($this->eventData)) {
        $this->eventData = DataTransferObject::create($this->buildEventData());
      }

      return $this->eventData;
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function hasData(string $key): bool {
    return $this->getData($key) !== NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function handleExceptions(): bool {
    return FALSE;
  }

  /**
   * Builds up associative data of the "event" token.
   *
   * At least this should provide a "machine_name" property, so that this may
   * be accessible as "[event:machine_name]" token.
   *
   * @return array
   *   The associative data for the "event" token.
   */
  #[Token(
    name: 'event',
    description: 'The event.',
    properties: [
      new Token(name: 'machine_name', description: 'The machine name of the ECA event.'),
    ],
  )]
  protected function buildEventData(): array {
    return [
      'machine_name' => $this->eventName(),
    ];
  }

}
