<?php

namespace Drupal\castorcito\Controller;

use Drupal\castorcito\CastorcitoManager;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;



/**
 * Returns responses for CastorcitoComponentOverrideDisplayController.
 */
class CastorcitoComponentOverrideDisplayController extends ControllerBase {

  /**
   * List of container types used to group components.
   */
  protected const CONTAINERS = ['container', 'advanced_container'];

  /**
   * Castorcito Manager Service.
   *
   * @var \Drupal\castorcito\CastorcitoManager
   */
  protected $castorcitoManager;

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

  /**
   * Constructs a new CastorcitoComponentOverrideDisplayController.
   *
   * @param \Drupal\castorcito\CastorcitoManager $castorcitoManager
   *   Castorcito manager service.
   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
   *   The entity display repository.
   */
  public function __construct(
    CastorcitoManager $castorcitoManager,
    EntityDisplayRepositoryInterface $entity_display_repository
  ) {
    $this->castorcitoManager = $castorcitoManager;
    $this->entityDisplayRepository = $entity_display_repository;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('castorcito.manager'),
      $container->get('entity_display.repository')
    );
  }

  /**
   * Provides the override display configuration list for a specific component.
   *
   * @param string $entity_type
   *   The entity type ID where the component is used (e.g., 'node', 'block_content').
   * @param string $bundle
   *   The bundle of the entity type (e.g., 'article', 'basic_page').
   * @param string $field_name
   *   The field name in which the component is configured.
   * @param string $component_id
   *   The machine name of the component to list override settings for.
   *
   * @return array
   *   A render array representing the configuration table.
   */
  public function listOverrideComponentDisplay(string $entity_type, string $bundle,  string $field_name, string $component_id) {
    $view_display = $this->entityDisplayRepository->getViewDisplay($entity_type, $bundle);
    $formatter = $view_display->getComponent($field_name);
    $components_display_settings = $formatter['settings']['components_display_settings'] ?? [];

    $parameters = [
      'entity_type' => $entity_type,
      'bundle' => $bundle,
      'field_name' => $field_name,
    ];

    return $this->listComponentDisplay($component_id, $components_display_settings, $parameters);
  }

  /**
   * Title callback for the override component page.
   *
   * @param string $entity_type
   * @param string $bundle
   * @param string $field_name
   * @param string $component_id
   *
   * @return string
   *   The page title including the component label.
   */
  public function listOverrideComponentDisplayTitle(string $entity_type, string $bundle, string $field_name, string $component_id): string {
    $component = $this->castorcitoManager->castorcitoComponent($component_id);
    return $this->t('Component @label override display', ['@label' => $component->label()]);
  }

  /**
   * Recursively builds the configuration tree for a component.
   *
   * @param string $component_id
   *   The machine name of the component to render.
   * @param array $component_overridden_display
 *     The overridden display configuration for the component, as a nested array.
   * @param array $parameters
   *   Route parameters to build URLs for actions (e.g., edit links).
   * @param array $parent_keys
   *   The array keys representing the current hierarchy path, within the configuration tree.
   *
   * @return array
   *   A nested render array representing the component configuration tree.
   */
  private function listComponentDisplay(string $component_id, array $component_overridden_display, array $parameters, array $parent_keys = []): array {
    $list_component_display = [];
    $component = $this->castorcitoManager->castorcitoComponent($component_id);
    $display_settings = $component->get('display_settings');
    $current_keys = [...$parent_keys, $component->id()];

    // Add parent component container if we are in a nested call.
    if (!empty($parent_keys)) {
      NestedArray::setValue($list_component_display, $current_keys, [
        '#type' => 'details',
        '#title' => $component->label(),
      ]);
    }

    if (!empty($display_settings)) {
      $option_current_keys = [...$current_keys, 'display_options'];
      NestedArray::setValue($list_component_display, $option_current_keys, [
        '#type' => 'details',
        '#title' => $this->t('Display options'),
        '#open' => TRUE,
      ]);

      NestedArray::setValue($list_component_display,
        [...$option_current_keys, 'display_settings_table'],
        $this->buildComponentConfigTable($this->componentDisplaySettings($display_settings), $component_overridden_display, $parameters, $option_current_keys)
      );
    }

    // SDC
    $sdc_current_keys = [...$current_keys, 'sdc'];
    NestedArray::setValue($list_component_display, $sdc_current_keys, [
      '#type' => 'details',
      '#title' => $this->t('Single Directory Component'),
    ]);

    NestedArray::setValue($list_component_display,
      [...$sdc_current_keys, 'sdc_table'],
      $this->buildComponentConfigTable(['id' => $this->t('ID')], $component_overridden_display, $parameters, $sdc_current_keys)
    );

    // Add "CFields" section.
    $cfield_configurations = $component->get('field_configuration');
    if ($this->hasComponentCFieldSettingsDisplay($cfield_configurations)) {
      $cfields_keys = [...$current_keys, 'cfields'];
      NestedArray::setValue($list_component_display, $cfields_keys, [
        '#type' => 'fieldset',
        '#title' => $this->t('CFields'),
      ]);

      foreach ($cfield_configurations as $f_key => $configuration) {
        if (!isset($configuration['settings']['display']) && !in_array($configuration['id'], self::CONTAINERS)) {
          continue;
        }

        $cfield_keys = [...$cfields_keys, $f_key];
        NestedArray::setValue($list_component_display, $cfield_keys, [
          '#type' => 'details',
          '#title' => $this->t($configuration['label']),
        ]);

        if (in_array($configuration['id'], self::CONTAINERS)) {
          $container_field_current_keys = $cfield_keys;
          if ($configuration['id'] === 'advanced_container') {
            $field_current_keys_head = [...$cfield_keys, 'head'];
            NestedArray::setValue($list_component_display, $field_current_keys_head, [
              '#type' => 'details',
              '#title' => $this->t('Head'),
            ]);

            $head_component_id = $configuration['settings']['head_component'];
            $child_config = $this->listComponentDisplay($head_component_id, $component_overridden_display, $parameters, $field_current_keys_head);
            $list_component_display = NestedArray::mergeDeepArray([$list_component_display, $child_config]);

            $container_field_current_keys = [...$cfield_keys, 'body'];
            NestedArray::setValue($list_component_display, $container_field_current_keys, [
              '#type' => 'details',
              '#title' => $this->t('Body'),
            ]);
          }


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

        NestedArray::setValue($list_component_display,
          [...$cfield_keys, 'cfield_element_table'],
          $this->buildComponentConfigTable($this->getComponentFieldOverridableDisplay($configuration['id']), $component_overridden_display, $parameters, $cfield_keys)
        );

      }
    }

    return $list_component_display;
  }

  /**
   * Builds a configuration table for component settings.
   *
   * @param array $elements
   *   The array of configuration elements to display in the table.
   * @param array $component_overridden_display
   *   The overridden display configuration for the component, as a nested array.
   * @param array $parameters
   *   Route parameters to build URLs for operation links.
   * @param array $keys
   *   The hierarchy path within the component configuration tree.
   *
   * @return array
   *   A render array representing the configuration table.
   */
  private function buildComponentConfigTable(array $elements, array $component_overridden_display, array $parameters, array $keys) {
    $rows = [];
    foreach ($elements as $item_key => $item) {
      $config_key = implode('.', [...$keys, $item_key]);
      $parameters['config_key'] = $config_key;
      $overridden_config = NestedArray::getValue($component_overridden_display, [...$keys, $item_key]);

      $row_data['label'] = [
        'data' => $item
      ];

      $row_data['overridden'] = [
        'data' => !is_null($overridden_config) ? $this->t('Overridden') : $this->t('Default'),
      ];

      $operations = [];
      $operations['edit'] = [
        'title' => !is_null($overridden_config) ? $this->t('Edit') : $this->t('Override'),
        'url' => Url::fromRoute('castorcito.override_display_form')
        ->setRouteParameters($parameters)
        ->setOptions([
          // ‘ccod_source’ refers to “CastorcitoComponentOverrideDisplay”.
          // It is used as a query parameter to identify the source of the action.
          'query' => ['ccod_source' => 'edit'],
          'attributes' => [
            'class' => ['use-ajax'],
            'data-dialog-type' => 'modal',
            'data-dialog-options' => Json::encode([
              'width' => 800,
              'title' => $this->t('Override display')
            ]),
          ],
        ]),
      ];

      if (!is_null($overridden_config)) {
        $operations['use_default'] = [
          'title' => $this->t('Use default'),
          'url' => Url::fromRoute('castorcito.override_display_form')
          ->setRouteParameters($parameters)
          ->setOptions([
            // ‘ccod_source’ refers to “CastorcitoComponentOverrideDisplay”.
            // It is used as a query parameter to identify the source of the action.
            'query' => ['ccod_source' => 'use_default'],
            'attributes' => [
              'class' => ['use-ajax'],
              'data-dialog-type' => 'modal',
              'data-dialog-options' => Json::encode([
                'width' => 800,
                'title' => $this->t('Use default display')
              ]),
            ],
          ]),
        ];
      }

      $row_data['operations'] = [
        'data' => [
          '#type' => 'operations',
          '#links' => $operations,
        ]
      ];

      $rows[] = $row_data;
    }


    return  [
      '#type' => 'table',
      '#header' => [],
      '#rows' => $rows,
    ];
  }

  /**
   * Retrieves the list of overridable display configuration elements for a component field.
   *
   * @param string $plugin_id
   *   The plugin ID of the component field instance.
   *
   * @return array
   */
  private function getComponentFieldOverridableDisplay($plugin_id) {
    $cfield_instance = $this->castorcitoManager->getComponentFieldInstance($plugin_id);
    $build_configuration_form = $this->castorcitoManager->getComponentFieldInstanceConfigurationForm($cfield_instance);
    $elements_display = $build_configuration_form['display'] ?? [];
    $overridable_elements = [];
    foreach ($elements_display as $e_key => $element) {
      if (is_array($element) && isset($element['#title'])) {
        if ($plugin_id === 'entity_reference' && str_contains($e_key, 'view_mode')) {
          $overridable_elements['view_mode'] = $element['#title'];
        }
        else {
          $overridable_elements[$e_key] = $element['#title'];
        }
      }
    }

    return $overridable_elements;
  }

  /**
   * Extracts a list of display setting labels from the component configuration.
   *
   * @return array
   *   An associative array.
   */
  private function componentDisplaySettings($display_settings) {
    $settings = [];

    foreach ($display_settings as $ds => $setting) {
      $settings[$ds] = $setting['label'];
    }

    return $settings;
  }

  /**
   * Determines whether any components have display settings.
   *
   * @param array $ids
   *   An array of component identifiers.
   *
   * @return array
   *   An array of component that have display settings.
   */
  private function hasComponentCFieldSettingsDisplay(array $cfield_configurations) {
    foreach ($cfield_configurations as $configuration) {
      if (in_array($configuration['id'], self::CONTAINERS)) {
        return TRUE;
      }

      if (!empty($configuration['settings']['display'])) {
        return TRUE;
      }
    }

    return FALSE;
  }

}
