<?php

declare(strict_types=1);

namespace Drupal\ui_patterns\Plugin\UiPatterns\Source;

use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Entity\EntityDisplayBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Plugin\PluginFormFactoryInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ui_patterns\Attribute\Source;
use Drupal\ui_patterns\Entity\SampleEntityGeneratorInterface;
use Drupal\ui_patterns\PropTypePluginManager;
use Drupal\ui_patterns\SourcePluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of the source.
 */
#[Source(
  id: 'block',
  label: new TranslatableMarkup('Block'),
  description: new TranslatableMarkup('instantiate a block plugin'),
  prop_types: ['slot']
)]
class BlockSource extends SourcePluginBase {
  /**
   * Block to be rendered.
   *
   * @var \Drupal\Core\Block\BlockPluginInterface|null
   */
  protected $block = NULL;

  /**
   * {@inheritdoc}
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    PropTypePluginManager $propTypeManager,
    ContextRepositoryInterface $contextRepository,
    RouteMatchInterface $routeMatch,
    SampleEntityGeneratorInterface $sampleEntityGenerator,
    protected BlockManagerInterface $blockManager,
    protected PluginFormFactoryInterface $pluginFormFactory,
    protected ContextHandlerInterface $contextHandler,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $propTypeManager, $contextRepository, $routeMatch, $sampleEntityGenerator);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ) {
    $instance = new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('plugin.manager.ui_patterns_prop_type'),
      $container->get('context.repository'),
      $container->get('current_route_match'),
      $container->get('ui_patterns.sample_entity_generator'),
      $container->get('plugin.manager.block'),
      $container->get('plugin_form.factory'),
      $container->get('context.handler')
    );
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultSettings(): array {
    return [
      'plugin_id' => NULL,
      'settings' => [],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getPropValue(): mixed {
    // Create a block entity.
    $this->block = $this->getBlock($this->getSetting('plugin_id') ?? '');
    if (!$this->block) {
      return [];
    }
    return $this->block->build();
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    $block = $this->getBlock($form_state->getValue('plugin_id'));
    if ($block) {
      $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
      $this->getPluginForm($block)->validateConfigurationForm($form['settings'], $subform_state);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state): array {
    $form = parent::settingsForm($form, $form_state);
    $this->buildBlockCreateForm($form, $form_state);
    return $form;
  }

  /**
   * Build the form to create a block.
   */
  protected function buildBlockCreateForm(array &$form, FormStateInterface $form_state) : void {
    $definitions = $this->listBlockDefinitions();
    $options = $this->getBlockOptions($definitions);
    $wrapper_id = Html::getId(implode("_", $this->formArrayParents ?? []) . "_block-create-form-ajax");
    $plugin_id = $this->getSetting('plugin_id') ?? '';
    $form["plugin_id"] = [
      "#type" => "select",
      "#title" => $this->t("Block"),
      "#options" => $options,
      '#default_value' => $plugin_id,

      '#ajax' => [
        'callback' => [__CLASS__, 'onBlockPluginIdChange'],
        'wrapper' => $wrapper_id,
        'method' => 'replace',
      // 'callback' => [static::class, 'onBlockPluginIdChange'],
      ],
      '#executes_submit_callback' => FALSE,
      '#empty_value' => '',
      '#empty_option' => $this->t('- None -'),
      '#required' => TRUE,
    ];
    $form["settings"] = [
      '#type' => 'container',
      '#attributes' => ["id" => $wrapper_id],
      "#tree" => TRUE,
    ];
    // @todo support block settings form validation.
    // Ass seen from
    // \Drupal\layout_builder\Form\ConfigureBlockFormBase::validateForm
    $block = $this->getBlock($plugin_id);
    if ($block) {
      // Create a block entity.
      $form['#tree'] = TRUE;
      // $form['#process'] = [ '::validateForm'];
      $form['settings'] = [];
      $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
      $form['settings'] = $this->getPluginForm($block)->buildConfigurationForm($form['settings'], $subform_state);
      $form["settings"]['#tree'] = TRUE;
      $form['settings']['#prefix'] = '<div id="' . $wrapper_id . '">' . ($form['settings']['#prefix'] ?? '');
      $form['settings']['#suffix'] = ($form['settings']['#suffix'] ?? '') . '</div>';
    }
  }

  /**
   * Retrieves the plugin form for a given block.
   *
   * @param \Drupal\Core\Block\BlockPluginInterface $block
   *   The block plugin.
   *
   * @return \Drupal\Core\Plugin\PluginFormInterface
   *   The plugin form for the block.
   */
  protected function getPluginForm(BlockPluginInterface $block) {
    if ($block instanceof PluginWithFormsInterface) {
      return $this->pluginFormFactory->createInstance($block, 'configure');
    }
    return $block;
  }

  /**
   * Gets the plugin for this component.
   *
   * @param string|null $plugin_id
   *   The plugin ID.
   *
   * @return \Drupal\Core\Block\BlockPluginInterface|null
   *   The plugin.
   */
  public function getBlock(?string $plugin_id) {
    if (!$plugin_id) {
      return NULL;
    }
    $block_configuration = $this->getSetting('settings') ?? [];
    $contexts = $this->context;
    $plugin = $this->blockManager->createInstance($plugin_id, $block_configuration);
    if ($plugin instanceof ContextAwarePluginInterface) {
      // Propagate the contexts known by this source to the block instance.
      $plugin_contexts = $plugin->getContexts();
      $plugin_definition = $plugin->getPluginDefinition();
      if ($plugin_definition["provider"] == "layout_builder") {
        if (array_key_exists("view_mode", $plugin_contexts)) {
          $plugin->setContextValue("view_mode", EntityDisplayBase::CUSTOM_MODE);
        }
      }
      foreach ($contexts as $context_name => $context) {
        if (!array_key_exists($context_name, $plugin_contexts)) {
          $plugin->setContext($context_name, $context);
        }
        else {
          $plugin->setContextValue($context_name, $context->getContextValue());
        }
      }
      $this->contextHandler->applyContextMapping($plugin, $contexts);
    }
    // Custom patch for LB FieldBlock blocks.
    return $plugin;
  }

  /**
   * Ajax callback for fields with AJAX callback to update form substructure.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return array
   *   The replaced form substructure.
   */
  public static function onBlockPluginIdChange(array $form, FormStateInterface $form_state): array {
    $triggeringElement = $form_state->getTriggeringElement();
    // Dynamically return the dependent ajax for elements based on the
    // triggering element. This shouldn't be done statically because
    // settings forms may be different, e.g. for layout builder, core, ...
    if (!empty($triggeringElement['#array_parents'])) {
      $subformKeys = $triggeringElement['#array_parents'];
      // Remove the triggering element itself and add the 'block' below key.
      array_pop($subformKeys);
      $subformKeys[] = 'settings';
      // Return the subform:
      $subform = NestedArray::getValue($form, $subformKeys);
      $form_state->setRebuild();
      return $subform;
    }
    return [];
  }

  /**
   * Return blocks list.
   *
   * @see BlockLibraryController::listBlocks()
   * @see FilteredPluginManagerTrait::getFilteredDefinitions()
   * @see layout_builder_plugin_filter_block__block_ui_alter()
   * @see layout_builder_plugin_filter_block__layout_builder_alter()
   * @see layout_builder_plugin_filter_block_alter()
   *
   * @return array
   *   Definitions of blocks
   */
  protected function listBlockDefinitions() : array {
    $context_for_block_discovery = $this->context;
    $definitions = $this->blockManager->getFilteredDefinitions('ui_patterns', $context_for_block_discovery, []);
    // Filter plugins based on the flag 'ui_patterns_compatibility'.
    // @see function ui_patterns_plugin_filter_block__ui_patterns_alter
    // from ui_patterns.module file
    $definitions = array_filter($definitions, function ($definition, $plugin_id) {
      return !isset($definition['_ui_patterns_compatible']) || $definition['_ui_patterns_compatible'];
    }, ARRAY_FILTER_USE_BOTH);
    // Filter based on contexts.
    $definitions = $this->contextHandler->filterPluginDefinitionsByContexts($context_for_block_discovery, $definitions);
    // Order by category, and then by admin label.
    $definitions = $this->blockManager->getSortedDefinitions($definitions);
    return $definitions;
  }

  /**
   * Get options for block select.
   */
  protected function getBlockOptions(array $definitions) : array {
    $definition_groups = [];
    foreach ($definitions as $plugin_id => $plugin_definition) {
      $category = $plugin_definition['category'] ?? 'Other';
      if ($category instanceof MarkupInterface) {
        /** @var \Drupal\Component\Render\MarkupInterface $category */
        $category = $category->__toString();
      }
      if (!array_key_exists($category, $definition_groups)) {
        $definition_groups[$category] = [];
      }
      $definition_groups[$category][$plugin_id] = $plugin_definition;
    }
    $options = [];
    foreach ($definition_groups as $definition_group_id => $definition_group) {
      $group_options = [];
      foreach ($definition_group as $definition_id => $definition) {
        $group_options[$definition_id] = $definition['admin_label'];
      }
      $options[$definition_group_id] = $group_options;
    }
    return $options;
  }

  /**
   * {@inheritdoc}
   */
  public function alterComponent(array $element): array {
    if (!$this->block) {
      return $element;
    }

    // CacheableMetadata::createFromRenderArray($content)
    $cache = $element["#cache"] ?? [];
    $element["#cache"] = array_merge($cache, [
      "max-age" => 0,
     // "tags" => ['config:system.menu.' . $this->menuId],
    ]);
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() : array {
    $dependencies = parent::calculateDependencies();
    if (!$this->block) {
      $this->block = $this->getBlock($this->getSetting('plugin_id') ?? '');
    }
    if (!$this->block) {
      return $dependencies;
    }
    SourcePluginBase::mergeConfigDependencies($dependencies, $this->getPluginDependencies($this->block));
    return $dependencies;
  }

}