<?php

namespace Drupal\component_builder;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;

/**
 * Component builder helper service.
 */
class ComponentBuilderHelper implements ComponentBuilderHelperInterface {

  use StringTranslationTrait;

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

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The entity bundle info.
   *
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $entityTypeBundleInfo;


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

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

  /**
   * Create an ComponentBuilderHelper object.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, ComponentBuilderManager $component_builder_manager, ModuleHandlerInterface $moduleHandler) {
    $this->entityTypeManager = $entity_type_manager;
    $this->entityFieldManager = $entity_field_manager;
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
    $this->componentBuilderManager = $component_builder_manager;
    $this->moduleHandler = $moduleHandler;
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldsReferencedComponent($entity_id = NULL) {
    $results = &drupal_static(__FUNCTION__);
    if (empty($results)) {
      $storage = $this->entityTypeManager->getStorage('field_storage_config');
      $entity_ids = $storage->getQuery()->accessCheck()->execute();
      $field_storages = $storage->loadMultipleOverrideFree($entity_ids);
      foreach ($field_storages as $field_storage) {
        $field_storage_name = $field_storage->getName();
        $filed_storage_type = $field_storage->getType();
        if ($filed_storage_type === 'entity_reference') {
          $filed_storage_target_type = $field_storage->getSettings()['target_type'];
          if ($filed_storage_target_type === 'component_wrapper') {
            foreach ($field_storage->getBundles() as $bundle) {
              [$target_entity_id] = explode('.', $field_storage->id(), 2);
              if ($target_entity_id != 'component_wrapper') {
                $results[$target_entity_id][$bundle][$field_storage_name] = $field_storage;
              }
            }
          }
        }
      }
    }

    return $entity_id && isset($results[$entity_id]) ? $results[$entity_id] : $results;
  }

  /**
   * {@inheritdoc}
   */
  public function getBundleHasField(string $entity_type, string $field_name) {
    $field_config = \Drupal::entityTypeManager()
      ->getStorage('field_config')
      ->loadByProperties([
        'entity_type' => $entity_type,
        'field_name' => $field_name,
      ]);
    if ($field_config) {
      $field_config = reset($field_config);
      return $field_config->getLabel();
    }
    return '';
  }

  /**
   * {@inheritdoc}
   */
  public function getErrorComponents() {
    $definitions = $this->componentBuilderManager->getDefinitions();
    $errors = [];
    $results = [];
    foreach ($definitions as $id => $definition) {
      if (isset($definition['dependencies']) && $definition['dependencies']) {
        $dependencies = $definition['dependencies'];
        foreach ($dependencies as $module_id => $dependency) {
          if (!$this->moduleHandler->moduleExists($module_id)) {
            $errors[$id][] = $dependency;
          }
        }
      }
    }
    foreach ($errors as $plugin_id => $module_requires) {
      $modules = array_column($module_requires, 'label');
      $module_names = implode(', ', $modules);
      $links_markup = [];
      foreach ($module_requires as $module) {
        $links_markup[] = "<a href='{$module['url']}' target='_blank'>{$module['label']}</a>";
      }
      $links_markup = implode(', ', $links_markup);
      $results[$plugin_id] = $this->t("@component_name component requires the @module module. Please install the $links_markup module before activating this component.",
        [
          '@component_name' => $definitions[$plugin_id]['label'] ?? '',
          '@module' => $module_names,
        ]);
    }
    return $results;
  }

  /**
   * {@inheritdoc}
   */
  public function createStorageField(string $type, string $field_name) {
    if (!FieldStorageConfig::loadByName($type, $field_name)) {
      FieldStorageConfig::create([
        'field_name' => $field_name,
        'type' => 'entity_reference',
        'entity_type' => $type,
        'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
        'settings' => [
          'target_type' => 'component_wrapper',
        ],
      ])->save();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function createReferenceComponentWrapperField(string $type, string $bundle, string $field_name, string $name) {
    $field_definitions = $this->entityFieldManager->getFieldDefinitions($type, $bundle);
    if (!isset($field_definitions[$field_name])) {
      if (!FieldConfig::loadByName($type, $bundle, $field_name)) {
        FieldConfig::create([
          'field_name' => $field_name,
          'entity_type' => $type,
          'bundle' => $bundle,
          'label' => $name,
          'settings' => [
            'handler' => 'default',
          ],
        ])->save();
      }
      $this->entityFieldManager->clearCachedFieldDefinitions();

      /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
      $display_repository = \Drupal::service('entity_display.repository');

      $form_display = $display_repository->getFormDisplay($type, $bundle);
      $form_display->setComponent($field_name, [
        'type' => 'inline_entity_form_complex',
        'settings' => [],
      ])->save();
      $view_display = $display_repository->getViewDisplay($type, $bundle);
      $view_display->setComponent($field_name, [
        'type' => 'entity_reference_entity_view',
        'label' => 'hidden',
        'settings' => [
          'view_mode' => 'full',
        ],
      ])->save();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function updateComponentField(string $type, string $bundle, string $field_name, string $name) {
    if (!FieldConfig::loadByName($type, $bundle, $field_name)) {
      $this->createReferenceComponentWrapperField($type, $bundle, $field_name, $name);
    }
    else {
      $field_definitions = $this->entityFieldManager->getFieldDefinitions($type, $bundle);
      if (isset($field_definitions[$field_name])) {
        if ($field_config = FieldConfig::loadByName($type, $bundle, $field_name)) {
          $field_config->setLabel($name);
          $field_config->save();
        }
        $this->entityFieldManager->clearCachedFieldDefinitions();
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function deleteComponentField(string $type, string $bundle, string $field_name) {
    $list_bundles = $this->entityTypeBundleInfo->getAllBundleInfo();
    $bundle_of_type = $list_bundles[$type];
    if (isset($bundle_of_type[$bundle])) {
      unset($bundle_of_type[$bundle]);
    }
    $allow_delete_bundle = TRUE;
    $field_configs = \Drupal::entityTypeManager()
      ->getStorage('field_config')
      ->loadByProperties([]);
    foreach ($bundle_of_type as $bundle_machine => $value) {
      $config_name = "$type.$bundle_machine.$field_name";
      if (isset($field_configs[$config_name])) {
        $allow_delete_bundle = FALSE;
        break;
      }
    }
    $field_config = FieldConfig::loadByName($type, $bundle, $field_name);
    if ($field_config) {
      $field_config->delete();
    }
    if ($allow_delete_bundle) {
      if ($field_storage = FieldStorageConfig::loadByName($type, $field_name)) {
        $field_storage->delete();
      }
    }
    $this->entityFieldManager->clearCachedFieldDefinitions();
  }

  /**
   * {@inheritdoc}
   */
  public function buildTableComponentFields() {
    $entity_types = $this->entityTypeManager->getDefinitions();
    $bundles = $this->entityTypeBundleInfo->getAllBundleInfo();
    $option_entity_types = [];
    $rows = [];
    foreach ($entity_types as $entity_type_id => $entity_type) {
      if ($entity_type instanceof ContentEntityTypeInterface) {
        $option_entity_types[$entity_type_id] = $entity_type->getLabel();
      }
    }
    $field_storages = \Drupal::entityTypeManager()
      ->getStorage('field_storage_config')
      ->loadByProperties([
        'type' => 'entity_reference',
      ]);
    $field_configs = \Drupal::entityTypeManager()
      ->getStorage('field_config')
      ->loadByProperties([]);
    $field_config_keys = array_keys($field_configs);
    foreach ($field_storages as $field_storage_id => $field_storage) {
      $settings = $field_storage->getSettings();
      if ($settings) {
        $settings = array_flip($settings);
        if (!isset($settings['component_wrapper']) || $field_storage->getTargetEntityTypeId() == 'component_wrapper') {
          unset($field_storages[$field_storage_id]);
        }
      }
    }
    $destination = Url::fromRoute('component_builder.settings')->toString();
    /** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
    foreach ($field_storages as $field_storage) {
      $target_entity_type_id = $field_storage->getTargetEntityTypeId();
      $row = [];
      $bundle_of_entity_type = $bundles[$target_entity_type_id];
      if ($bundle_of_entity_type) {
        $pattern = '/^' . $target_entity_type_id . '.([^#]+).' . $field_storage->getName() . '/';
        $matches = preg_grep($pattern, $field_config_keys);
        $bundle_labels = [];
        foreach ($matches as $match) {
          $type = $field_configs[$match]->getTargetEntityTypeId();
          $bundle = $field_configs[$match]->getTargetBundle();
          $bundle_labels[] = $bundles[$type][$bundle]['label'];
        }
        $bundle_labels = implode('<br/>', $bundle_labels);
      }
      $sibling_bundles = $bundle_of_entity_type;
      foreach ($sibling_bundles as $sibling_entity_type => $sibling_bundle) {
        $field_config_machine = $field_storage->getTargetEntityTypeId() . '.' . $sibling_entity_type . '.' . $field_storage->getName();
        if (!isset($field_configs[$field_config_machine])) {
          unset($sibling_bundles[$sibling_entity_type]);
        }
      }
      $entity_target_type = $field_storage->getTargetEntityTypeId();
      $row[] = $option_entity_types[$entity_target_type];
      $row[] = $field_storage->getName();
      $row[] = Markup::create($bundle_labels);
      $row[] = [
        'data' => [
          '#type' => 'operations',
          '#links' => [
            'edit' => [
              'title' => $this->t('Edit'),
              'url' => Url::fromRoute('component_builder.config_field', [], [
                'attributes' => [
                  'class' => ['use-ajax'],
                  'data-dialog-type' => 'modal',
                  'data-dialog-options' => Json::encode([
                    'width' => 700,
                  ]),
                ],
                'query' => [
                  'destination' => $destination,
                  'entity_type' => $entity_target_type,
                  'field_name' => $field_storage->getName(),
                  'mode' => 'edit',
                ],
              ]),
            ],
            'delete' => [
              'title' => $this->t('Delete'),
              'url' => Url::fromRoute('component_builder.config_field', [], [
                'attributes' => [
                  'class' => ['use-ajax'],
                  'data-dialog-type' => 'modal',
                  'data-dialog-options' => Json::encode([
                    'width' => 700,
                  ]),
                ],
                'query' => [
                  'destination' => $destination,
                  'entity_type' => $entity_target_type,
                  'field_name' => $field_storage->getName(),
                  'mode' => 'delete',
                ],
              ]),
            ],
          ],
        ],
      ];
      $rows[] = $row;
    }
    return [
      '#prefix' => "<div id='table-information'>",
      '#suffix' => "</div>",
      '#type' => 'table',
      '#header' => [
        $this->t('Entity Type'),
        $this->t('Field Machine Name'),
        $this->t('Bundles'),
        $this->t('Operations'),
      ],
      '#rows' => $rows,
      '#empty' => $this->t('No fields found'),
    ];
  }

}
