<?php

namespace Drupal\pagedesigner\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\RendererInterface as CoreRendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\editor\Plugin\EditorManager;
use Drupal\pagedesigner\ElementViewBuilder;
use Drupal\pagedesigner\Entity\Element;
use Drupal\pagedesigner\OutputTrait;
use Drupal\pagedesigner\PagedesignerServiceInterface;
use Drupal\pagedesigner\Service\Render\Styles;
use Drupal\ui_patterns\UiPatternsManagerInterface;

/**
 * The renderer service allows to render a pagedesigner element container.
 *
 * @todo Refactor class to split responsibilities and reduce dependencies.
 */
class Renderer implements RendererInterface {
  use StringTranslationTrait;
  use OutputTrait;

  /**
   * Style object to collect the styles during rendering.
   *
   * @var Drupal\pagedesigner\Service\Render\Styles|null
   */
  protected static $styles = NULL;

  /**
   * Whether rendering is in process.
   *
   * @var bool
   */
  protected $rendering = FALSE;

  /**
   * Create a new instance.
   *
   * @param \Drupal\pagedesigner\PagedesignerServiceInterface $pagedesignerService
   *   The pagedesigner service.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   * @param \Drupal\pagedesigner\Service\Locker $pagedesignerLocker
   *   The pagedesigner locker service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The configuration factory.
   * @param \Drupal\ui_patterns\UiPatternsManagerInterface $patternsManager
   *   The Ui patterns manager.
   * @param Drupal\editor\Plugin\EditorManager $editorPluginManager
   *   The Text Editor plugin manager service.
   */
  public function __construct(
    protected PagedesignerServiceInterface $pagedesignerService,
    protected MessengerInterface $messenger,
    protected Locker $pagedesignerLocker,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected CoreRendererInterface $renderer,
    protected ConfigFactoryInterface $configFactory,
    protected UiPatternsManagerInterface $patternsManager,
    protected EditorManager $editorPluginManager,
  ) {
  }

  /**
   * {@inheritdoc}
   */
  public static function addStyle($key, $style, $id) {
    if (self::$styles == NULL) {
      self::$styles = new Styles();
    }
    self::$styles->addStyle($key, $style, $id);
  }

  /**
   * {@inheritdoc}
   */
  public function preload(ContentEntityInterface $entity, bool $public = FALSE) {
    $storageManager = $this->entityTypeManager->getStorage('pagedesigner_element');
    $entity_query = $storageManager->getQuery();
    $entity_query
      ->accessCheck(FALSE)
      ->condition('entity', $entity->id())
      ->condition('langcode', $entity->langcode->value);
    $group = $entity_query->orConditionGroup();
    $group->condition('deleted', 0)->condition('deleted', NULL, 'IS NULL');
    $entity_query->condition($group);
    if ($public) {
      $entity_query->condition('status', 1);
    }
    $result = $entity_query->execute();
    if ($result) {
      $storageManager->loadMultiple($result);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function renderForPublic(Element $container, ContentEntityBase $entity = NULL) {
    if ($container == NULL) {
      return $this;
    }
    if (empty($this->output)) {
      $this->output = [];
    }
    if (!$this->rendering) {
      self::$styles = NULL;
    }
    if ($entity) {
      $this->preload($entity, TRUE);
    }
    $this->rendering = TRUE;
    $view_builder = $this->entityTypeManager->getViewBuilder('pagedesigner_element');
    $this->output = $view_builder->view($container, ElementViewBuilder::RENDER_MODE_PUBLIC);
    $this->rendering = FALSE;
    $this->addStyles($container);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function render(Element $container, ContentEntityBase $entity = NULL, $nocache = FALSE) {
    if ($container == NULL) {
      return $this;
    }
    if (empty($this->output)) {
      $this->output = [];
    }
    if (!$this->rendering) {
      self::$styles = NULL;
    }
    if ($entity) {
      $this->preload($entity, FALSE);
    }
    $this->rendering = TRUE;
    $view_builder = $this->entityTypeManager->getViewBuilder('pagedesigner_element');
    $this->output = $view_builder->view($container, ElementViewBuilder::RENDER_MODE_INTERNAL);
    $this->rendering = FALSE;
    if ($nocache) {
      $this->output['#cache']['max-age'] = 0;
    }
    if (empty($this->output['#attached'])) {
      $this->output['#attached'] = [];
    }
    $this->addStyles($container);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function renderForEdit(Element $container, ContentEntityBase $entity = NULL) {
    if ($container == NULL) {
      return $this;
    }
    if (!$this->pagedesignerService->hasEditAccess($entity)) {
      $this->messenger->addWarning($this->t('You are not authorized to access this page.'));
      $this->render($container, $entity);
      return $this;
    }
    if (empty($this->output)) {
      $this->output = [];
    }
    if (!$this->rendering) {
      self::$styles = NULL;
    }
    if ($entity != NULL) {
      $locker = $this->pagedesignerLocker->setEntity($entity);
      $lock = $locker->acquire();
      if (!$lock && !$locker->hasLock()) {
        $username = 'unknown';
        /** @var \Drupal\user\Entity\User $otherUser */
        $otherUser = $this->entityTypeManager->getStorage('user')->load($locker->getMetaData()->getOwnerId());
        if ($otherUser != NULL) {
          $username = $otherUser->getDisplayName();
        }
        $this->messenger->addWarning(
          $this->t('This page is currently being edited by user %user. You may access it again after the user is done editing.', ['%user' => $username])
        );
        $this->render($container, $entity, TRUE);
        return $this;
      }
    }
    if ($entity) {
      $this->preload($entity, FALSE);
    }
    $this->rendering = TRUE;
    $view_builder = $this->entityTypeManager->getViewBuilder('pagedesigner_element');
    $this->output = $view_builder->view($container, ElementViewBuilder::RENDER_MODE_EDIT);
    $this->rendering = FALSE;
    $this->addStyles($container);
    $this->addEditorAttachments();
    $this->addPagedesigner();
    $this->addPatternAttachments();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getMarkup() {
    return Markup::create($this->renderer->render($this->output));
  }

  /**
   * Return the styles generated by the last render.
   *
   * @return string
   *   The generated styles.
   */
  public function getStyles() {
    $rendered = '';
    if (self::$styles != NULL) {
      $output = [
        'large' => '',
        'medium' => '@media (max-width: 992px)',
        'small' => '@media (max-width: 768px)',
      ];
      foreach ($output as $size => $query) {
        $styles = self::$styles->getStyles($size);
        $context = ['key' => $size];
        $this->pagedesignerService->getModuleHandler()->alter('pagedesigner_css_rendered', $styles, $context);
        if (!empty($styles)) {
          if (empty($query)) {
            $rendered .= $styles . "\n";
          }
          else {
            $rendered .= $query . '{' . $styles . '}' . "\n";
          }
        }
      }
    }
    return $rendered;
  }

  /**
   * Add the collected styles of the elements to the output.
   */
  protected function addStyles(Element $container) {
    if (self::$styles != NULL) {
      $rendered = $this->getStyles();
      if (!empty($rendered)) {
        $this->output['#attached']['html_head']['pagedesigner_dynamic_css_' . $container->id()] = [
          [
            '#tag' => 'style',
            '#value' => $rendered,
            '#attributes' => [
              'id' => 'pagedesigner_dynamic_css_' . $container->id(),
              'data-pagedesigner-container' => $container->id(),
            ],
          ],
          'pagedesigner_dynamic_css_' . $container->id(),
        ];
      }
    }
  }

  /**
   * Add attachments for the WYSIWYG editor to the output.
   */
  protected function addEditorAttachments() {
    if (empty($this->output['#attached'])) {
      $this->output['#attached'] = [];
    }
    $config = $this->configFactory->get('pagedesigner.settings');
    $this->output['#attached']['drupalSettings']['pagedesigner']['filter_format'] = $config->get('filter_format');
    $this->output['#attached'] = array_merge(
        $this->output['#attached'],
        $this->editorPluginManager->getAttachments([$config->get('filter_format')])
    );
  }

  /**
   * Attach pagedesigner library.
   *
   * Adds the basic pagedesigner edit and pattern libraries needed to
   * edit to the pagedesigner to the output.
   */
  protected function addPagedesigner() {
    if (empty($this->output['#attached'])) {
      $this->output['#attached'] = [];
    }
    $this->output['#attached']['library'][] = 'pagedesigner/pagedesigner';
    $this->pagedesignerService->getHandler()->collectAttachments($this->output['#attached']);
  }

  /**
   * Adds the attachments of all paterns to the output.
   */
  protected function addPatternAttachments() {
    if (empty($this->output['#attached'])) {
      $this->output['#attached'] = [];
    }
    $patternDefinitions = $this->patternsManager->getDefinitions();
    $this->pagedesignerService->getHandler()->collectPatterns($patternDefinitions);
    foreach ($patternDefinitions as $definition) {
      foreach ($definition->getLibrariesNames() as $library) {
        $this->output['#attached']['library'][] = $library;
      }
    }
  }

}
