<?php

namespace Drupal\component_builder\Form;

use Drupal\Component\Serialization\Json;
use Drupal\component_builder\ComponentBuilderHelperInterface;
use Drupal\component_builder\ComponentBuilderManager;
use Drupal\component_builder\ImportConfigComponent;
use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ExtensionPathResolver;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Config component item settings.
 */
class ComponentItemSettingsForm extends ConfigFormBase {

  const SETTING = 'component_builder.settings';

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * The extension path resolver.
   *
   * @var \Drupal\Core\Extension\ExtensionPathResolver
   */
  protected $extensionPathResolver;

  /**
   * The Import config component services.
   *
   * @var \Drupal\component_builder\ImportConfigComponent
   */
  protected $importConfigComponent;

  /**
   * The field plugin manager service.
   *
   * @var \Drupal\Core\Extension\ModuleHandler
   */
  protected $moduleHandler;

  /**
   * The component builder helper.
   *
   * @var \Drupal\component_builder\ComponentBuilderHelperInterface
   */
  protected $componentBuilderHelper;

  /**
   * The component builder manager.
   *
   * @var \Drupal\component_builder\ComponentBuilderManager
   */
  protected $componentBuilderManager;

  /**
   * Constructs a new ComponentItemSettingsForm object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The factory for configuration objects.
   * @param \Drupal\Core\File\FileSystemInterface $file_system
   *   The file system object.
   * @param \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver
   *   The extension path resolver.
   * @param \Drupal\component_builder\ImportConfigComponent $import_config_component
   *   The extension path resolver.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   The module handler.
   * @param \Drupal\component_builder\ComponentBuilderHelperInterface $component_builder_helper
   *   The component builder manager.
   * @param \Drupal\component_builder\ComponentBuilderManager $component_builder_manager
   *   The component builder manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, FileSystemInterface $file_system, ExtensionPathResolver $extension_path_resolver, ImportConfigComponent $import_config_component, ModuleHandlerInterface $moduleHandler, ComponentBuilderHelperInterface $component_builder_helper, ComponentBuilderManager $component_builder_manager) {
    parent::__construct($config_factory);
    $this->entityTypeManager = $entity_type_manager;
    $this->fileSystem = $file_system;
    $this->extensionPathResolver = $extension_path_resolver;
    $this->importConfigComponent = $import_config_component;
    $this->moduleHandler = $moduleHandler;
    $this->componentBuilderHelper = $component_builder_helper;
    $this->componentBuilderManager = $component_builder_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('config.factory'),
      $container->get('file_system'),
      $container->get('extension.path.resolver'),
      $container->get('component_builder.import_config_component'),
      $container->get('module_handler'),
      $container->get('component_builder.helper'),
      $container->get('plugin.manager.component_builder'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'component_item_settings_form';
  }

  /**
   * Finish batch.
   */
  public static function batchFinished($success, $results, $operations) {
    \Drupal::service('cache.render')->invalidateAll();
    \Drupal::messenger()->addMessage('Install completed');
  }

  /**
   * Process batch.
   */
  public static function processItems(ImportConfigComponent $import_config_component, $id, $label, $module, &$context) {
    if (!isset($context['sandbox']['import_config_component'])) {
      $context['sandbox']['import_config_component'] = $import_config_component;
    }

    $import_config_component = $context['sandbox']['import_config_component'];
    try {
      $import_config_component->createComponentItem($id, $label, $module);
    }
    catch (\Exception $e) {
      \Drupal::logger('import')->info("Error: {$e->getMessage()}");
    }
    $context['message'] = t('@component', ['@component' => $label]);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValues();
    $components = $values['components'];
    $definitions = $form_state->get('definitions');

    /** i.e. component types that have unmet condition(s) */
    $invalid_components = $this->componentBuilderHelper->getErrorComponents();
    foreach ($components as $groupId => $group) {
      foreach ($group as $componentId => $value) {
        $configComponent = $definitions[$componentId];
        if (isset($configComponent['dependencies']) && $value === 1) {
          if (isset($invalid_components[$componentId])) {
            $this->messenger()->addError($invalid_components[$componentId]);
            unset($components[$groupId][$componentId]);
          }
        }
      }
    }

    $config = $this->config(self::SETTING);
    $batchBuilder = new BatchBuilder();
    $batchBuilder->setTitle('Install Components')
      ->setInitMessage('Starting')
      ->setErrorMessage('Error when install components.')
      ->setFinishCallback([$this, 'batchFinished']);
    $config_components = $config->get('components');
    $import_config_component = new ImportConfigComponent(
      \Drupal::service('entity_field.manager'),
      \Drupal::service('entity_type.manager'),
      \Drupal::service('module_handler'),
      \Drupal::service('plugin.manager.component_builder'),
    );
    foreach ($components as $group) {
      foreach ($group as $componentId => $value) {
        if ($value === 1 && !isset($config_components[$componentId]) && isset($definitions[$componentId])) {
          $id = $definitions[$componentId]['id'];
          $module = $definitions[$componentId]['module'] ?? 'component_builder';
          $label = $definitions[$componentId]['label']->__toString();
          $batchBuilder->addOperation([
            ComponentItemSettingsForm::class,
            'processItems',
          ], [
            $import_config_component,
            $id,
            $label,
            $module,
          ]);
          $config_components[$componentId] = $value;
        }
      }
    }
    $config->set('components', $config_components);

    if (!$config->get('grant_permissions')) {
      user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, [
        'view component_wrapper',
        'view component_item',
      ]);
      user_role_grant_permissions(AccountInterface::AUTHENTICATED_ROLE, [
        'view component_wrapper',
        'view component_item',
      ]);
      $config->set('grant_permissions', 1);
    }
    $config->save();
    batch_set($batchBuilder->toArray());
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $definitions = $this->componentBuilderManager->getDefinitions();
    $groups = [];
    foreach ($definitions as $config) {
      $groups[$config['group']->__toString()][] = $config;
      usort($groups[$config['group']->__toString()], function ($a, $b) {
        return strcmp($a['label']->__toString(), $b['label']->__toString());
      });
    }
    ksort($groups);
    $form_state->set('definitions', $definitions);


    $form['components'] = [
      '#type' => 'details',
      '#title' => $this->t('Enable Component Type'),
      '#open' => FALSE,
      '#tree' => TRUE,
    ];

    $form['components']['check_all'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Select / Unselect all'),
      '#attributes' => [
        'class' => ['wrapper-checkbox-check-uncheck-all'],
      ],
      '#weight' => -1,
      '#tree' => FALSE,
    ];

    $form['components']['collapse_all'] = [
      '#type' => 'button',
      '#value' => $this->t('Collapse / Uncollapse All'),
      '#attributes' => [
        'class' => ['wrapper-collapse-all'],
      ],
      '#weight' => -1,
    ];
    foreach ($groups as $key => $plugins) {
      $group_key = strtolower(str_replace(' ', '-', $key));
      $form['components'][$group_key] = [
        '#type' => 'details',
        '#title' => $key,
        '#open' => FALSE,
        '#attributes' => [
          'class' => ['wrapper-collapse-group-component'],
        ],
      ];
      foreach ($plugins as $plugin) {
        $dependencies_message = '';
        if (isset($plugin['dependencies'])) {
          $dependencies = $plugin['dependencies'];
          $dependencies_message = array_column($dependencies, 'label');
          $dependencies_message = '<div class="button--danger">(Requires: ' . implode(',', array_values($dependencies_message)) . ') </div>';
        }
        $name = $plugin['label'];
        $id = $plugin['id'];
        $form['components'][$group_key]['wrapper-' . $id] = [
          '#type' => 'container',
          '#attributes' => [
            'class' => ['wrapper-checkbox'],
          ],
        ];
        $form['components'][$group_key]['wrapper-' . $id][$id] = [
          '#type' => 'checkbox',
          '#title' => $dependencies_message ? $name . ' ' . $dependencies_message : $name,
          '#default_value' => $this->config(self::SETTING)
            ->get('components')[$id] ?? FALSE,
          '#attributes' => [
            'class' => ['checkbox-component'],
          ],
          '#parents' => [
            'components',
            $group_key,
            $id,
          ],
        ];
        if ($plugin = $this->componentBuilderManager->getInstanceByTemplateName($plugin['id'])) {
          if ($icon = $plugin->getIcon()) {
            $form['components'][$group_key]['wrapper-' . $id]['image'] = [
              '#theme' => 'image',
              '#uri' => $icon,
              '#alt' => $dependencies_message ? $name . ' ' . $dependencies_message : $name,
              '#attributes' => [
                'class' => ['collapse-image', 'visually-hidden'],
              ],
            ];
          }
        }
      }
    }
    $this->renderConfigComponentField($form, $form_state);
    $form['#attached']['library'][] = 'component_builder/component_settings';
    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return [
      static::SETTING,
    ];
  }

  /**
   * Render config component field.
   */
  public function renderConfigComponentField(array &$form, FormStateInterface $form_state) {
    $form['managed_component_fields'] = [
      '#type' => 'details',
      '#title' => $this->t('Add Component Fields'),
      '#open' => FALSE,
      '#tree' => TRUE,
    ];

    $form['managed_component_fields']['add_field'] = [
      '#type' => 'link',
      '#title' => $this->t('Add field'),
      '#url' => Url::fromRoute('component_builder.config_field', [
        'render_modal' => 1,
      ]),
      '#attributes' => [
        'class' => ['use-ajax', 'button button--primary'],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => Json::encode([
          'width' => 700,
          'height' => 500,
        ]),
      ],
    ];
    $form['managed_component_fields']['table'] = $this->componentBuilderHelper->buildTableComponentFields();
  }

}
