<?php

namespace Drupal\pagedesigner\Service;

use Drupal\Core\Cache\Cache;
use Drupal\pagedesigner\ElementEvents;
use Drupal\pagedesigner\Entity\Element;
use Drupal\pagedesigner\Event\ElementEvent;
use Drupal\pagedesigner\Plugin\pagedesigner\HandlerPluginInterface;
use Drupal\ui_patterns\Definition\PatternDefinition;
use Drupal\ui_patterns\Definition\PatternDefinitionField;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Handle pagedesigner elements.
 *
 * The ElementHandler provides a central interface to handle entities.
 */
class ElementHandler implements HandlerPluginInterface {

  /**
   * The element handler manager.
   *
   * @var \Drupal\pagedesigner\Service\HandlerPluginManager
   */
  protected $handlerManager = NULL;

  /**
   * The Event dispatcher.
   *
   * @var Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher = NULL;

  /**
   * Create a new instance.
   *
   *   The handler manager from which to retrieve the element handlers.
   *   The event dispatcher to dispatch events.
   */
  public function __construct(HandlerPluginManager $handler_manager, EventDispatcherInterface $event_dispatcher) {
    $this->handlerManager = $handler_manager;
    $this->eventDispatcher = $event_dispatcher;
  }

  /**
   * Internal helper to dispatch events.
   *
   * @param string $event
   *   The event name to dispatch.
   * @param array $data
   *   The data to pass to the event.
   */
  protected function dispatch($event, array &$data = []) {
    $this->eventDispatcher->dispatch(new ElementEvent($event, $data), $event);
  }

  /**
   * {@inheritdoc}
   */
  public function collectAttachments(array &$attachments) {
    $eventData = [];
    $eventData[] = &$attachments;
    $this->dispatch(ElementEvents::COLLECTATTACHMENTS_BEFORE, $eventData);

    $handlers = $this->handlerManager->getHandlers();
    foreach ($handlers as $handler) {
      $handler->collectAttachments($attachments);
    }

    $this->dispatch(ElementEvents::COLLECTATTACHMENTS_AFTER, $eventData);

  }

  /**
   * {@inheritdoc}
   */
  public function collectPatterns(array &$patterns, bool $render_markup = FALSE) {
    $eventData = [];
    $eventData[] = &$patterns;
    $this->dispatch(ElementEvents::COLLECTPATTERNS_BEFORE, $eventData);

    $handlers = $this->handlerManager->getHandlers();
    foreach ($handlers as $handler) {
      $handler->collectPatterns($patterns, $render_markup);
    }

    $this->dispatch(ElementEvents::COLLECTPATTERNS_AFTER, $eventData);
  }

  /**
   * {@inheritdoc}
   */
  public function adaptPatterns(array &$patterns) {
    $eventData = [];
    $eventData[] = &$patterns;
    $this->dispatch(ElementEvents::ADAPTPATTERNS_BEFORE, $eventData);

    $handlers = $this->handlerManager->getHandlers();
    foreach ($handlers as $handler) {
      $handler->adaptPatterns($patterns);
    }

    $this->dispatch(ElementEvents::ADAPTPATTERNS_AFTER, $eventData);
  }

  /**
   * {@inheritdoc}
   */
  public function prepare(PatternDefinitionField &$field, array &$fieldArray) {
    $eventData = [];
    $eventData[] = &$field;
    $eventData[] = &$fieldArray;
    $this->dispatch(ElementEvents::PREPARE_BEFORE, $eventData);

    $handlers = $this->handlerManager->getHandlers();
    foreach ($handlers as $handler) {
      $handler->prepare($field, $fieldArray);
    }
    $this->dispatch(ElementEvents::PREPARE_AFTER, $eventData);

  }

  /**
   * {@inheritdoc}
   */
  public function get(Element $entity, string &$result = '') {
    $eventData = [];
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::GET_BEFORE, $eventData);

    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);

    foreach ($handlers as $handler) {
      $handler->get($entity, $result);
    }
    $eventData[] = &$result;
    $this->dispatch(ElementEvents::GET_AFTER, $eventData);
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function getContent(Element $entity, array &$list = [], $published = TRUE) {
    $eventData = [];
    $eventData[] = &$entity;
    $eventData[] = &$list;
    $eventData[] = $published;
    $this->dispatch(ElementEvents::GETCONTENT_BEFORE, $eventData);

    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->getContent($entity, $list, $published);
    }
    $this->dispatch(ElementEvents::GETCONTENT_AFTER, $eventData);
    return $list;

  }

  /**
   * {@inheritdoc}
   */
  public function serialize(Element $entity, array &$result = []) {
    $eventData = [];
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::SERIALIZE_BEFORE, $eventData);

    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->serialize($entity, $result);
    }
    $eventData[] = &$result;
    $this->dispatch(ElementEvents::SERIALIZE_AFTER, $eventData);
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function describe(Element $entity, array &$result = []) {
    $eventData = [];
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::DESCRIBE_BEFORE, $eventData);

    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->describe($entity, $result);
    }
    $eventData[] = &$result;
    $this->dispatch(ElementEvents::DESCRIBE_AFTER, $eventData);
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function generate($patternDefinition, array $data, ?Element &$entity = NULL) {
    if (!\is_array($patternDefinition) && !($patternDefinition instanceof PatternDefinition)) {
      throw new \InvalidArgumentException('The definition must be an array or of type Drupal\ui_patterns\Definition\PatternDefinition.');
    }

    if ($patternDefinition instanceof PatternDefinition) {
      $definition = $patternDefinition->toArray();
      $definition['type'] = $patternDefinition->getAdditional()['type'];
      $definition['patternDefinition'] = &$patternDefinition;
    }
    else {
      $definition = $patternDefinition;
    }
    if (empty($definition['type'])) {
      throw new \InvalidArgumentException('Could not extract type from definition.');
    }

    $eventData = [];
    $eventData[] = &$definition;
    $eventData[] = &$data;
    $this->dispatch(ElementEvents::GENERATE_BEFORE, $eventData);

    if (!empty($data['entity'])) {
      Cache::invalidateTags(['pagedesigner:node:' . $data['entity']]);
    }

    $handlers = $this->handlerManager->getInstance($definition);
    foreach ($handlers as $handler) {
      $handler->generate($definition, $data, $entity);
    }
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::GENERATE_AFTER, $eventData);
    return $entity;
  }

  /**
   * {@inheritdoc}
   */
  public function patch(Element $entity, array $data = []) {
    $eventData = [];
    $eventData[] = &$entity;
    $eventData[] = &$data;
    $this->dispatch(ElementEvents::PATCH_BEFORE, $eventData);

    if (\is_numeric($entity->entity->target_id)) {
      Cache::invalidateTags(['pagedesigner:node:' . $entity->entity->target_id]);
    }
    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->patch($entity, $data);
    }
    $this->dispatch(ElementEvents::PATCH_AFTER, $eventData);
  }

  /**
   * {@inheritdoc}
   */
  public function copy(Element $entity, ?Element $container = NULL, ?Element &$clone = NULL) {

    $eventData = [];
    $eventData[] = &$entity;
    $eventData[] = &$container;
    $this->dispatch(ElementEvents::COPY_BEFORE, $eventData);

    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->copy($entity, $container, $clone);
    }
    if (\is_numeric($clone->entity->target_id)) {
      Cache::invalidateTags(['pagedesigner:node:' . $clone->entity->target_id]);
    }
    $eventData[] = &$clone;
    $this->dispatch(ElementEvents::COPY_AFTER, $eventData);
    return $clone;
  }

  /**
   * {@inheritdoc}
   */
  public function delete(Element $entity, bool $remove = FALSE) {
    $eventData = [];
    $eventData[] = &$entity;
    $eventData[] = &$remove;
    $this->dispatch(ElementEvents::DELETE_BEFORE, $eventData);

    if (\is_numeric($entity->entity->target_id)) {
      Cache::invalidateTags(['pagedesigner:node:' . $entity->entity->target_id]);
    }
    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->delete($entity, $remove);
    }
    $this->dispatch(ElementEvents::DELETE_AFTER, $eventData);
  }

  /**
   * {@inheritdoc}
   */
  public function restore(Element $entity) {
    $eventData = [];
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::RESTORE_BEFORE, $eventData);

    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->restore($entity);
    }
    $this->dispatch(ElementEvents::RESTORE_AFTER, $eventData);
  }

  /**
   * {@inheritdoc}
   */
  public function view(Element $entity, string $view_mode, array &$build = []) {
    $eventData = [];
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::VIEW_BEFORE, $eventData);

    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->view($entity, $view_mode, $build);
    }
    $eventData[] = &$build;
    $this->dispatch(ElementEvents::VIEW_AFTER, $eventData);
    return $build;
  }

  /**
   * {@inheritdoc}
   */
  public function build(Element $entity, string $view_mode, array &$build = []) {
    $eventData = [];
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::BUILD_BEFORE, $eventData);
    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->build($entity, $view_mode, $build);
    }
    $eventData[] = &$build;
    $this->dispatch(ElementEvents::BUILD_AFTER, $eventData);
    return $build;
  }

  /**
   * {@inheritdoc}
   */
  public function publish(Element $entity) {
    $eventData = [];
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::PUBLISH_BEFORE, $eventData);

    if (\is_numeric($entity->entity->target_id)) {
      Cache::invalidateTags(['pagedesigner:node:' . $entity->entity->target_id]);
    }
    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->publish($entity);
    }
    $this->dispatch(ElementEvents::PUBLISH_AFTER, $eventData);
  }

  /**
   * {@inheritdoc}
   */
  public function unpublish(Element $entity) {
    $eventData = [];
    $eventData[] = &$entity;
    $this->dispatch(ElementEvents::UNPUBLISH_BEFORE, $eventData);

    if (\is_numeric($entity->entity->target_id)) {
      Cache::invalidateTags(['pagedesigner:node:' . $entity->entity->target_id]);
    }
    $handlers = $this->handlerManager->getInstance(['type' => $entity->getHandlerType()]);
    foreach ($handlers as $handler) {
      $handler->unpublish($entity);
    }
    $this->dispatch(ElementEvents::UNPUBLISH_AFTER, $eventData);
  }

}
