<?php

namespace Drupal\castorcito\Plugin\Field\FieldFormatter;

use Drupal\castorcito\CastorcitoComponentFieldManager;
use Drupal\castorcito\CastorcitoManager;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\Core\Url;
use Drupal\field\FieldConfigInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of the 'castorcito_component_formatter' formatter.
 *
 * @FieldFormatter(
 *   id = "castorcito_component_formatter",
 *   label = @Translation("Castorcito component"),
 *   field_types = {
 *     "json",
 *     "json_native",
 *     "json_native_binary",
 *   }
 * )
 */
class CastorcitoComponentFormatter extends FormatterBase {

  protected const CONTAINERS = ['container', 'advanced_container'];

  /**
   * The entity display repository.
   *
   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
   */
  protected $entityDisplayRepository;

  /**
   * The SDC component plugin manager.
   *
   * @var \Drupal\Core\Theme\ComponentPluginManager;
   */
  protected $componentPluginManager;

  /**
   * The castorcito manager.
   *
   * @var Drupal\castorcito\CastorcitoManager
   */
  protected $castorcitoManager;

  /**
   * The component field manager.
   *
   * @var \Drupal\castorcito\CastorcitoComponentFieldManager
   */
  protected $componentFieldManager;

  /**
   * Constructs a new CastorcitoComponentFormatter.
   *
   * @param string $plugin_id
   *   The plugin_id for the formatter.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The definition of the field to which the formatter is associated.
   * @param array $settings
   *   The formatter settings.
   * @param string $label
   *   The formatter label display setting.
   * @param string $view_mode
   *   The view mode.
   * @param array $third_party_settings
   *   Third party settings.
   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
   *   The entity display repository.
   * @param \Drupal\Core\Theme\ComponentPluginManager; $component_plugin_manager
   *   The SDC component plugin manager.
   * @param Drupal\castorcito\CastorcitoManager $castorcito_manager
   *   The castorcito manager.
   * @param Drupal\castorcito\CastorcitoComponentFieldManager $component_field_manager
   *   The castorcito component field manager.
   */
  public function __construct(
    $plugin_id,
    $plugin_definition,
    FieldDefinitionInterface $field_definition,
    array $settings,
    $label,
    $view_mode,
    array $third_party_settings,
    EntityDisplayRepositoryInterface $entity_display_repository,
    ComponentPluginManager $component_plugin_manager,
    CastorcitoManager $castorcito_manager,
    CastorcitoComponentFieldManager $component_field_manager
  ) {
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
    $this->entityDisplayRepository = $entity_display_repository;
    $this->componentPluginManager = $component_plugin_manager;
    $this->castorcitoManager = $castorcito_manager;
    $this->componentFieldManager = $component_field_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $plugin_id,
      $plugin_definition,
      $configuration['field_definition'],
      $configuration['settings'],
      $configuration['label'],
      $configuration['view_mode'],
      $configuration['third_party_settings'],
      $container->get('entity_display.repository'),
      $container->get('plugin.manager.sdc'),
      $container->get('castorcito.manager'),
      $container->get('plugin.manager.castorcito_component_field')
    );
  }

  /**
   * {@inheritdoc}
  */
  public static function defaultSettings() {
    return [
      'components_display_settings' => [],
    ] + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
  */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $form = parent::settingsForm($form, $form_state);
    $entity_type_id = $this->fieldDefinition->getTargetEntityTypeId();
    $bundle_id = '';

    if ($this->fieldDefinition instanceof FieldConfigInterface) {
      $bundle_id = $this->fieldDefinition->getTargetBundle();
    }

    $widget_config = $this->entityDisplayRepository
      ->getFormDisplay($entity_type_id, $bundle_id)
      ->getComponent($this->fieldDefinition->getName());

    if (!empty($widget_config) && $widget_config['type'] === 'castorcito_component_widget') {
      $component_ids = array_filter($widget_config['settings']['components']);
      $components = $this->castorcitoManager->castorcitoComponents($component_ids);
      $parameters = [
        'entity_type' => $entity_type_id,
        'bundle' => $bundle_id,
        'field_name' => $this->fieldDefinition->getName(),
      ];

      $form['components_display_settings_fieldset'] = [
        '#type' => 'fieldset',
        '#title' => '',
      ];

      $form['components_display_settings_fieldset']['help'] = [
        '#markup' => '<p>' . $this->t('Click one of the component buttons to override its settings.') . '</p>',
      ];

      foreach ($components as $c_key => $component) {
        $parameters['component_id'] = $c_key;
        $form['components_display_settings_fieldset'][$c_key] = [
          '#type' => 'link',
          '#title' => $component->label(),
          '#url' => Url::fromRoute('castorcito.list_override_component_display')->setRouteParameters($parameters),
          '#attributes' => [
            'class' => ['button'],
            'target' => '_blank'
          ],
        ];
      }

      $form['components_display_settings'] = [
        '#type' => 'hidden',
        '#default_value' => Json::encode($component_ids),
        '#element_validate' => [
          [$this, 'validateComponentsDisplaySettings'],
        ],
      ];
    }

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $components_display_settings = $this->getSetting('components_display_settings');
    $summary = [];

    if (!empty($components_display_settings)) {
      $components_options = $this->castorcitoManager->castorcitoComponentsOptions();
      $items = [];
      foreach ($components_display_settings as $con_key => $config) {
        $items[] = $components_options[$con_key];
      }

      $summary[] = $this->t('<strong>Overridden settings</strong>');
      $summary[] = [
        '#theme' => 'item_list',
        '#list_type' => 'ul',
        '#items' => $items,
      ];
    }
    else {
      $summary[] = $this->t('Default settings.');
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];

    if (!empty($items)) {
      foreach ($items as $key => $item) {
        $model = Json::decode($item->value);
        $component_display_settings = $this->getDisplaySettings($model['type']);
        $display_settings = $component_display_settings[$model['type']];
        $attributes = new Attribute(['class' => 'castorcito--component']);

        if (!empty($display_settings) && isset($display_settings['display_options'])) {
          $attributes = $this->predefinedOptionsAttribute($attributes, $display_settings['display_options']);
        }

        $elements[$key] = [
          '#type' => 'component',
          '#component' => $display_settings['sdc']['id'],
          '#props' => [
            'component_data' => $model,
            'display_settings' => $display_settings,
            'attributes' => $attributes,
            'cfield_plugins' => $this->getCfeldPluginsSettings(),
          ],
        ];
      }
    }

    return $elements;
  }

  /**
   * Retrieves the display settings of the components.
   */
  private function getDisplaySettings($component_id, $parent_keys = []) {
    $display_settings = [];
    $formatter_settings = $this->getSetting('components_display_settings');
    $component = $this->castorcitoManager->castorcitoComponent($component_id);
    $current_keys = [...$parent_keys, $component->id()];

    if ($settings = $component->get('display_settings')) {
      $option_current_keys = [...$current_keys, 'display_options'];
      $values = [];
      foreach ($settings as $s_key => $setting) {
        $option_value_keys = [...$option_current_keys, $s_key];
        $overridden_setting = NestedArray::getValue($formatter_settings, $option_value_keys);
        $values[$s_key] = is_null($overridden_setting) ? $setting['default_value'] : $overridden_setting;
      }

      NestedArray::setValue($display_settings, $option_current_keys, $values);
    }

    // SDC: Resolve which Single Directory Component (SDC) to render.
    $sdc_values = $component->get('sdc');
    $sdc_current_keys = [...$current_keys, 'sdc'];
    $sdc_id_overridden = NestedArray::getValue($formatter_settings, [...$sdc_current_keys, 'id']);

    if (empty($sdc_values)) {
      $sdc_values['id'] = 'castorcito:default_sdc';
    }
    else {
      if (!$sdc_values['fixed_sdc'] || ($sdc_values['fixed_sdc'] && !$this->componentPluginManager->hasDefinition($sdc_values['id']))) {
        $sdc_values['id'] = 'castorcito:default_sdc';
      }
    }

    // Apply overridden SDC ID if available and valid.
    if (!is_null($sdc_id_overridden) && $this->componentPluginManager->hasDefinition($sdc_id_overridden)) {
      $sdc_values['id'] = $sdc_id_overridden;
    }

    NestedArray::setValue($display_settings, $sdc_current_keys, $sdc_values);

    if ($cfield_configurations = $component->get('field_configuration')) {
      foreach ($cfield_configurations as $f_key => $configuration) {
        $cfield_current_keys = [...$current_keys, 'cfields', $f_key];

        if (in_array($configuration['id'], self::CONTAINERS)) {
          $container_field_current_keys = $cfield_current_keys;

          if ($configuration['id'] === 'advanced_container') {
            $container_field_current_keys = [...$cfield_current_keys, 'body'];
            $field_current_keys_head = [...$cfield_current_keys, 'head'];
            $child_config = $this->getDisplaySettings($configuration['settings']['head_component'], $field_current_keys_head);
            $display_settings = NestedArray::mergeDeepArray([$display_settings, $child_config]);
          }

          foreach ($configuration['settings']['allowed_children'] as $child_id) {
            $child_config = $this->getDisplaySettings($child_id, $container_field_current_keys);
            $display_settings = NestedArray::mergeDeepArray([$display_settings, $child_config]);
          }
        }

        if (!isset($configuration['settings']['display'])) {
          continue;
        }

        $overridden_values = NestedArray::getValue($formatter_settings, $cfield_current_keys) ?? [];
        $values = NestedArray::mergeDeepArray([$configuration['settings']['display'], $overridden_values]);
        NestedArray::setValue($display_settings, $cfield_current_keys, $values);
      }
    }

    return $display_settings;
  }

  /**
   * Function to set predefined options as attribute.
   *
   * @param array $display_options
   * @return Attribute
   */
  public function predefinedOptionsAttribute(Attribute $attributes, array $display_options) {
    if (isset($display_options['component_classes_text']) && !empty($display_options['component_classes_text'])) {
      $attributes->addClass(explode(' ', $display_options['component_classes_text']));
    }

    if (isset($display_options['component_classes_select']) && !empty($display_options['component_classes_select'])) {
      $attributes->addClass($display_options['component_classes_select']);
    }

    if (isset($display_options['component_id']) && !empty($display_options['component_id'])) {
      $attributes->setAttribute('id', $display_options['component_id']);
    }

    return $attributes;
  }

  /**
   * Retrieves basic settings for all registered Cfeld plugins.
   *
   * @return array
   *   An associative array of plugin settings, keyed by plugin ID. Each item
   *   contains:
   *   - module_name: The machine name of the module that provides the plugin.
   */
  private function getCfeldPluginsSettings() {
    $plugins_settings = [];
    foreach ($this->componentFieldManager->getDefinitions() as $plugin_id => $plugin_definition) {
      $plugins_settings[$plugin_id]['module_name']  = $plugin_definition['provider'];
    }

    return $plugins_settings;
  }

  /**
   * Validation callback for a components_display_settings element.
   *
   * @param array $element
   *   The form element whose value is being validated.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   */
  public function validateComponentsDisplaySettings(&$element, FormStateInterface $form_state, &$complete_form) {
    // We avoid using $this->getSetting('components_display_settings') because the configuration
    // may have been recently updated from another page,
    // and getSetting() may return a stale cached value. To ensure accuracy, we load the
    // latest configuration directly from the EntityViewDisplay storage.
    $entity_type = $this->fieldDefinition->get('entity_type');
    $bundle = $this->fieldDefinition->get('bundle');
    $view_display = $this->entityDisplayRepository->getViewDisplay($entity_type, $bundle);
    $view_display_component = $view_display->getComponent($this->fieldDefinition->getName());
    $components_display_settings = $view_display_component['settings']['components_display_settings'] ?? [];

    // Filter only those component settings that match selected components.
    $components = Json::decode($form_state->getValue($element['#parents']));
    $new_element_value = array_intersect_key($components_display_settings, $components);

    // Set the filtered value to the element.
    $form_state->setValueForElement($element, $new_element_value);
  }

}
