<?php

declare(strict_types=1);

namespace Drupal\simple_interactive_maps;

use Drupal\Core\Render\RendererInterface;

/**
 * Load map data from the source data file.
 */
final class MapDataLoader {

  /**
   * The key for the groups data in the temp store.
   */
  public const MAP_TEMP_STORE_GROUPS_KEY = 'groups';

  /**
   * The key for the region data in the temp store.
   */
  public const MAP_TEMP_STORE_REGIONS_KEY = 'regions';

  public function __construct(
    private MapDefinitionPluginManager $mapDefinitionPluginManager,
    private MapActionPluginManager $actionPluginManager,
    private RendererInterface $renderer,
  ) {}

  /**
   * Load the map data into an array.
   *
   * @return array
   *   The map data.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  public function loadMapData(?InteractiveMapInterface $map = NULL): array {
    /** @var \Drupal\simple_interactive_maps\MapDefinitionInterface $plugin */
    $plugin = $this->mapDefinitionPluginManager->createInstance($map->get('base_map'));

    $data = $plugin->mapData();

    $this->mergeConfigData($data, $map);

    return $data;
  }

  /**
   * Merge the config data into the map data.
   *
   * @param array $mapData
   *   The map data.
   * @param \Drupal\simple_interactive_maps\InteractiveMapInterface $map
   *   The map to merge the data from.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  private function mergeConfigData(array &$mapData, InteractiveMapInterface $map): void {
    $regions = $map->getRegions();
    $groups = $map->getGroups();

    array_walk($mapData, function (&$item, $key) use ($regions, $groups) {
      if (array_key_exists($key, $regions)) {
        $item = array_merge($item, $regions[$key]);

        if ($item['action']['plugin_id'] !== 'none') {
          /** @var \Drupal\simple_interactive_maps\MapActionInterface $actionPlugin */
          $actionPlugin = $this->actionPluginManager->createInstance($item['action']['plugin_id'], $item['action']['plugin_configuration']);

          // @phpstan-ignore-next-line
          $item['action'] = $actionPlugin->getPluginId();
          $item['action_configuration'] = $actionPlugin->getActionConfiguration();
        }
        else {
          $item['action'] = 'none';
          $item['action_configuration'] = [];
        }

        if (($item['group'] !== '') && $groups[$item['group']]) {
          $this->applyGroupOverrides($item, $groups[$item['group']]);
        }
        $item['tooltip']['processed'] = is_array($item['tooltip']) ? $this->renderProcessedText($item['tooltip']['value'], $item['tooltip']['format']) : '';
      }
    });
  }

  /**
   * Runs text through the filter system for the given format.
   *
   * @param string $text
   *   The text to filter.
   * @param string $format
   *   The text format to apply.
   *
   * @return string
   *   The filtered, rendered markup.
   */
  private function renderProcessedText(string $text, string $format): string {
    $build = [
      '#type' => 'processed_text',
      '#text' => $text,
      '#format' => $format,
    ];

    return (string) $this->renderer->renderInIsolation($build);
  }

  /**
   * Apply group overrides to the item.
   *
   * @param array $item
   *   The item to apply the overrides to.
   * @param array $group
   *   The group to apply the overrides from.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  private function applyGroupOverrides(array &$item, array $group): void {
    if ($group['override_colors']) {
      $item['fill_color'] = $group['fill_color'];
      $item['hover_color'] = $group['hover_color'];
      $item['stroke_color'] = $group['stroke_color'];
      $item['text_color'] = $group['text_color'];
    }

    if ($group['override_tooltip']) {
      $item['tooltip'] = $group['tooltip'] ?? [
        'value' => '',
        'format' => 'plain_text',
      ];
    }

    if ($group['override_action']) {
      if ($group['action']['plugin_id'] !== 'none') {
        $configuration = $group['action']['plugin_configuration'] ?? [];
        $actionPlugin = $this->actionPluginManager->createInstance($group['action']['plugin_id'], $configuration);
        $item['action'] = $actionPlugin->getPluginId();
        $item['action_configuration'] = $actionPlugin->getActionConfiguration();
      }
      else {
        $item['action'] = 'none';
        $item['action_configuration'] = [];
      }
    }
  }

  /**
   * Get the action libraries required for the map.
   *
   * @param \Drupal\simple_interactive_maps\InteractiveMapInterface $map
   *   The map to get the libraries for.
   *
   * @return array
   *   The libraries required for the map actions.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  public function getActionLibraries(InteractiveMapInterface $map): array {
    $mapLibraries = drupal_static('simple_interactive_maps_map_libraries', []);

    if (array_key_exists($map->id(), $mapLibraries)) {
      return $mapLibraries[$map->id()];
    }

    $actions = [];
    $regions = $map->getRegions();
    foreach ($regions as $region) {
      if (!in_array($region['action']['plugin_id'], $actions)) {
        $actions[] = $region['action']['plugin_id'];
      }
    }

    $groups = $map->getGroups();
    foreach ($groups as $group) {
      if (!in_array($group['action']['plugin_id'], $actions)) {
        $actions[] = $group['action']['plugin_id'];
      }
    }

    $libraries = [];

    foreach ($actions as $plugin_id) {
      $actionPlugin = $this->actionPluginManager->createInstance($plugin_id);
      $pluginLibraries = $actionPlugin->getActionLibrary();
      if (!in_array($pluginLibraries, $libraries)) {
        $libraries[$plugin_id] = $pluginLibraries;
      }
    }

    $libraries = array_filter($libraries, function ($library) {
      return $library != '';
    });

    $mapLibraries[$map->id()] = $libraries;

    return $libraries;
  }

  /**
   * Static helper to load maps for use in a select element.
   *
   * @return array
   *   Map options list.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public static function getMapOptions(): array {
    $mapStorage = \Drupal::entityTypeManager()->getStorage('interactive_map');
    $mapConfigs = $mapStorage->loadMultiple();

    $mapOptions = [];
    foreach ($mapConfigs as $mapConfig) {
      $mapOptions[$mapConfig->id()] = $mapConfig->label();
    }

    return $mapOptions;
  }

}
