<?php

declare(strict_types=1);

namespace Drupal\simple_interactive_maps\Plugin\Filter;

use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\filter\Attribute\Filter;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Drupal\filter\Plugin\FilterInterface;
use Drupal\simple_interactive_maps\InteractiveMapInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a filter to embed interactive maps.
 */
#[Filter(
  id: 'simple_interactive_maps_map_filter',
  title: new TranslatableMarkup('Interactive Map Embed Filter'),
  type: FilterInterface::TYPE_TRANSFORM_REVERSIBLE,
)]
class MapFilter extends FilterBase implements ContainerFactoryPluginInterface {

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected RendererInterface $renderer,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * Static create function.
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   Service DI Container.
   * @param array $configuration
   *   Plugin Configuration.
   * @param string $plugin_id
   *   Plugin ID.
   * @param mixed $plugin_definition
   *   Plugin Definition.
   *
   * @return \Drupal\simple_interactive_maps\Plugin\Filter\MapFilter
   *   The instantiated plugin.
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): MapFilter {
    /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
    $entityTypeManager = $container->get('entity_type.manager');

    /** @var \Drupal\Core\Render\RendererInterface $renderer */
    $renderer = $container->get('renderer');

    // @phpstan-ignore new.static (MapFilter is intentionally not final so other modules can subclass it; new static() is required so the inherited create() constructs the subclass.)
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $entityTypeManager,
      $renderer,
    );
  }

  /**
   * {@inheritdoc}
   */
  public function process($text, $langcode): FilterProcessResult {
    $result = new FilterProcessResult($text);
    $mapStorage = $this->entityTypeManager->getStorage('interactive_map');

    if (str_contains($text, 'data-map-id')) {
      $dom = Html::load($text);
      $xpath = new \DOMXPath($dom);

      foreach ($xpath->query('//simple-map[@data-map-id]') as $mapNode) {

        // @phpstan-ignore-next-line
        $mapId = $mapNode->getAttribute('data-map-id');

        // @phpstan-ignore-next-line
        $showDescription = $mapNode->getAttribute('data-show-description');

        $interactive_map = $mapStorage->load($mapId);

        if ($interactive_map instanceof InteractiveMapInterface) {
          $mapOutput = [
            '#theme' => 'interactive_map',
            '#map' => $interactive_map,
            '#show_description' => $showDescription === 'true',
          ];

          $rendered_map = $this->renderer->render($mapOutput);
          $this->replaceNode($mapNode, (string) $rendered_map);
          $result->addCacheableDependency($interactive_map);
        }
      }

      $result->setProcessedText(Html::serialize($dom));
    }

    // Find all instances of shortcodes and process them (legacy style codes).
    if (preg_match('/\[interactive_map map=([a-zA-Z0-9_]*)\]/', $text, $matches)) {

      $interactive_map = $mapStorage->load($matches[1]);

      if ($interactive_map instanceof InteractiveMapInterface) {

        $map_output = [
          '#theme' => 'interactive_map',
          '#map' => $interactive_map,
          '#show_description' => FALSE,
        ];

        $rendered_map = $this->renderer->render($map_output);
        $text = str_replace($matches[0], (string) $rendered_map, $text);
        $result->setProcessedText($text);
        $result->addCacheableDependency($interactive_map);
      }
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function tips($long = FALSE): string {
    return (string) $this->t('Enable the dynamic map filter.');
  }

  /**
   * Replace a node with a new HTML string.
   *
   * @param \DOMNode $node
   *   Node to replace.
   * @param string $replacement
   *   Replacement HTML string.
   */
  private function replaceNode(\DOMNode $node, string $replacement): void {
    $fragment = $node->ownerDocument->createDocumentFragment();
    $fragment->appendXML($replacement);
    $node->parentNode->replaceChild($fragment, $node);
  }

}
