<?php

namespace Drupal\component_builder;

use Drupal\Core\Cache\Cache;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Symfony\Component\Yaml\Yaml;

/**
 * The ImportConfigComponent service.
 */
class ImportConfigComponent {

  use DependencySerializationTrait;

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

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

  /**
   * The module handler to invoke the alter hook.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The module.
   *
   * @var string
   */
  protected $module;

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

  /**
   * The component item entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $componentItemTypeStorage;

  /**
   * ImportConfigComponent constructor.
   *
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\component_builder\ComponentBuilderManager $component_builder_manager
   *   The component builder plugin manager.
   */
  public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, ComponentBuilderManager $component_builder_manager) {
    $this->entityFieldManager = $entity_field_manager;
    $this->entityTypeManager = $entity_type_manager;
    $this->moduleHandler = $module_handler;
    $this->componentBuilderManager = $component_builder_manager;
    $this->componentItemTypeStorage = $entity_type_manager->getStorage('component_item_type');
    $this->module = 'component_builder';
  }

  /**
   * Create new component item.
   */
  public function createComponentItem(string $bundle, string $label, string $module = 'component_builder') {
    $this->module = $module;
    $component_type = $this->componentItemTypeStorage->load($bundle);

    if (!$component_type) {
      if ($bundle === 'composite') {
        $this->createTerm($label);
      }
      elseif ($plugin = $this->componentBuilderManager->getInstanceByTemplateName($bundle)) {
        $component_path = $plugin->getComponentPath();
        $single_value = $plugin->isSingeValue();

        if ($component_path) {
          $config_path = $component_path . '/config/config.yml';
          $config_values = $this->parseYaml($config_path);
          $this->createComponentItemType($bundle, $component_path);
          $this->createSubComponentItem($config_values);
          $this->createFieldsComponent($bundle, $component_path, $config_values);
          $this->createImageStylesComponent($component_path, $config_values);
          $this->createViewMode($component_path, $config_values);
          $this->createDisplay($bundle, $component_path, $config_values);
          $this->createComponentWrapperField($bundle, $single_value);
          $this->createComponentWrapperFields($bundle, $component_path, $config_values);
          $this->createTerm($label);
        }
      }
    }
  }

  /**
   * Create component item type.
   */
  public function createComponentItemType(string $bundle, string $component_path = '') {
    $component_item_path = $component_path . "/config/component_builder.component_item_type.$bundle.yml";
    $component_item_values = $this->parseYaml($component_item_path);
    if ($component_item_values) {
      $new_component_type = $this->componentItemTypeStorage->create($component_item_values);
      $new_component_type->save();
    }
  }

  /**
   * Create sub component item.
   */
  public function createSubComponentItem($config_values = []) {
    if (isset($config_values['references'])) {
      $references = $config_values['references'];
      if (is_array($references)) {
        foreach ($references as $bundle => $label) {
          $component_type = $this->componentItemTypeStorage->load($bundle);
          if (!$component_type) {
            $component_name = get_component_name($bundle);
            $component_path = $this->getPathComponentItem($component_name);
            if ($component_path) {
              $config_path = $component_path . '/config/config.yml';
              $config_sub_item_values = $this->parseYaml($config_path);
              $this->createComponentItemType($bundle, $component_path);
              $this->createSubComponentItem($config_sub_item_values);
              $this->createFieldsComponent($bundle, $component_path, $config_sub_item_values);
              $this->createImageStylesComponent($component_path, $config_sub_item_values);
              $this->createDisplay($bundle, $component_path, $config_values);
            }
          }
        }
      }
    }
  }

  /**
   * Create fields for component.
   */
  public function createFieldsComponent($bundle = '', $component_path = '', $config_values = []) {
    $entity = 'component_item';
    if (isset($config_values['fields'])) {
      $field_defines = $this->entityFieldManager->getFieldDefinitions($entity, $bundle) ?? [];
      $fields = $config_values['fields'];
      foreach ($fields as $field_name => $label) {
        if (!array_key_exists($field_name, $field_defines)) {
          $this->createField($field_name, $entity, $bundle, $component_path);
        }
      }
    }
  }

  /**
   * Create entity form display.
   */
  public function createDisplay(string $bundle, string $component_path, array $config_values) {
    $entity = 'component_item';
    $this->setFormDisplay($component_path, $entity, $bundle, 'default');
    if (isset($config_values['display_modes'])) {
      $display_modes = $config_values['display_modes'];
      foreach ($display_modes as $display_mode) {
        $this->setViewDisplay($component_path, $entity, $bundle, $display_mode);
      }
    }
    $this->setViewDisplay($component_path, $entity, $bundle, 'default');
  }

  /**
   * Create field.
   */
  public function createField($field_name = '', $entity = '', $bundle = '', $component_path = '') {
    $field_storage = FieldStorageConfig::loadByName($entity, $field_name);
    if (!$field_storage) {
      $path_storage_config = $component_path . "/config/field.storage.$entity.$field_name.yml";
      $this->importFieldStorageYmlConfig($path_storage_config);
    }
    $path_config = $component_path . "/config/field.field.$entity.$bundle.$field_name.yml";
    $this->importFieldYmlConfig($path_config);
    $this->enableWrapperField($field_name);
  }

  /**
   * Create view modes to see the content.
   */
  public function createViewMode($component_path, $config_values) {
    if (isset($config_values['display_modes'])) {
      $display_mode_styles = $config_values['display_modes'];
      if (is_array($display_mode_styles)) {
        $entity_view_mode_storage = $this->entityTypeManager->getStorage('entity_view_mode');
        foreach ($display_mode_styles as $display_mode_style) {
          $display_mode_path = $component_path . "/config/core.entity_view_mode.component_item.$display_mode_style.yml";
          $values = $this->parseYaml($display_mode_path);
          if (!empty($values)) {
            $entity_view_mode = $entity_view_mode_storage->create($values);
            $entity_view_mode->save();
          }
        }
      }
    }
  }

  /**
   * Set form display.
   */
  public function setFormDisplay($component_path, $entity_type, $bundle, $form_mode) {
    $entity_form_display_storage = $this->entityTypeManager->getStorage('entity_form_display');
    $entity_form_display = $entity_form_display_storage->load("$entity_type.$bundle.$form_mode");
    if (!$entity_form_display) {
      $path_config = $component_path . "/config/core.entity_form_display.$entity_type.$bundle.$form_mode.yml";
      $values = $this->parseYaml($path_config);
      if ($values) {
        $entity_form_display = $entity_form_display_storage->create($values);
        $entity_form_display->save();
      }
    }
  }

  /**
   * Set view display.
   */
  public function setViewDisplay($componentPath, $entityType, $bundle, $form_mode) {
    $entity_form_display_storage = $this->entityTypeManager->getStorage('entity_view_display');
    $entity_form_display = $entity_form_display_storage->load("$entityType.$bundle.$form_mode");
    if (!$entity_form_display) {
      $path_config = $componentPath . "/config/core.entity_view_display.$entityType.$bundle.$form_mode.yml";
      $values = $this->parseYaml($path_config);
      if ($values) {
        $entity_form_display = $entity_form_display_storage->create($values);
        $entity_form_display->save();
      }
    }
  }

  /**
   * Clear cache bin.
   *
   * @param string $cache_name
   *   Cache name.
   */
  public function flush(string $cache_name = '') {
    if (empty($cache_name)) {
      drupal_flush_all_caches();
    }
    else {
      foreach (Cache::getBins() as $service_id => $cache_backend) {
        if (is_array($cache_name)) {
          if (in_array($service_id, $cache_name)) {
            $cache_backend->deleteAll();
          }
        }
        else {
          if ($cache_name == 'twig') {
            PhpStorageFactory::get('twig')->deleteAll();
          }
          elseif ($service_id == $cache_name) {
            $cache_backend->deleteAll();
          }
        }
      }
    }
  }

  /**
   * Import field with yml.
   */
  public function importFieldYmlConfig($path_to_file) {
    $field_string = file_get_contents($path_to_file);
    $field_config = Yaml::parse($field_string);
    if ($field_config) {
      $field_config_storage = $this->entityTypeManager->getStorage('field_config');
      $fieldStorage = $field_config_storage->create($field_config);
      $fieldStorage->save();
      $this->flush('twig');
    }
  }

  /**
   * Create new term.
   *
   * @param string $label
   *   The label of term.
   */
  public function createTerm(string $label) {
    $vocabulary = $this->entityTypeManager->getStorage('taxonomy_vocabulary')
      ->load('component_types');
    if (!$vocabulary) {
      $this->createVocabulary('Component Types');
    }
    $term = $this->entityTypeManager->getStorage('taxonomy_term')->getQuery()
      ->accessCheck()
      ->condition('name', $label)
      ->condition('vid', 'component_types')
      ->execute();
    if (!$term) {
      $term = Term::create([
        'name' => $label,
        'description' => '',
        'parent' => [0],
        'vid' => 'component_types',
      ]);
      $term->save();
    }
  }

  /**
   * Load caregory.
   */
  public function createVocabulary(string $label) {
    $machine = preg_replace('@[^a-z0-9-]+@', '_', strtolower($label));
    $vocabulary = Vocabulary::create([
      'name' => $label,
      'description' => $label,
      'vid' => $machine,
    ]);
    $vocabulary->save();
  }

  /**
   * Get component item path.
   */
  public function getPathComponentItem(string $component_name) {
    $module_path = $this->moduleHandler->getModule($this->module)->getPath();
    return $module_path . '/components/' . $component_name;
  }

  /**
   * Load yml content with path.
   */
  public function parseYaml($path) {
    if (!file_exists($path)) {
      return [];
    }
    return Yaml::parse(file_get_contents($path)) ?? [];
  }

  /**
   * Import field storage.
   */
  public function importFieldStorageYmlConfig($path) {
    try {
      $field_config = $this->parseYaml($path);
      if (!empty($field_config)) {
        $storage = $this->entityTypeManager->getStorage('field_storage_config');
        $field_storage = $storage->createFromStorageRecord($field_config);
        $field_storage->save();
        $this->flush('twig');
      }
    }
    catch (\Exception $e) {
      \Drupal::logger('component_builder')->error($e->getMessage());
    }
  }

  /**
   * Create field for component wrapper.
   */
  public function createComponentWrapperField(string $bundle, bool $single_value = FALSE) {
    $component_item_type = $this->componentItemTypeStorage->load($bundle);
    if ($component_item_type) {
      $field_name = 'field_' . $component_item_type->id();
      $field_definitions = $this->entityFieldManager->getFieldDefinitions('component_wrapper', 'component_wrapper');

      if (!in_array($field_name, array_keys($field_definitions))) {
        if (!FieldStorageConfig::loadByName('component_wrapper', $field_name)) {
          FieldStorageConfig::create([
            'field_name' => $field_name,
            'type' => 'entity_reference',
            'entity_type' => 'component_wrapper',
            'cardinality' => $single_value ? 1 : FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
            'settings' => [
              'target_type' => 'component_item',
            ],
          ])->save();
        }

        if (!FieldConfig::loadByName('component_wrapper', 'component_wrapper', $field_name)) {
          FieldConfig::create([
            'field_name' => $field_name,
            'entity_type' => 'component_wrapper',
            'bundle' => 'component_wrapper',
            'label' => $component_item_type->label() . ' Items',
            'settings' => [
              'handler' => 'default',
              'handler_settings' => [
                'target_bundles' => [$component_item_type->id()],
              ],
            ],
          ])->save();
        }
        $this->entityFieldManager->clearCachedFieldDefinitions();

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

        $form_display = $display_repository->getFormDisplay('component_wrapper', 'component_wrapper');

        $form_display
          ->setComponent($field_name, [
            'type' => $single_value ? 'inline_component_item' : 'inline_entity_form_complex',
            'weight' => 5,
            'settings' => [
              'override_labels' => TRUE,
              'label_singular' => t('Item'),
              'label_plural' => t('Items'),
            ],
          ])
          ->save();

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

  /**
   * Create fields for component wrapper.
   */
  public function createComponentWrapperFields($bundle = '', $component_path = '', $config_values = []) {
    $entity = 'component_wrapper';
    if (isset($config_values['wrapper_fields'])) {
      $field_defines = $this->entityFieldManager->getFieldDefinitions($entity, $entity) ?? [];
      $fields = $config_values['wrapper_fields'];
      foreach ($fields as $field_name => $label) {
        if (!array_key_exists($field_name, $field_defines)) {
          $this->createField($field_name, $entity, $entity, $component_path);
        }
      }
    }
  }

  /**
   * Enable new field at component wrapper.
   */
  public function enableWrapperField(string $field_name) {
    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');
    $form_display = $display_repository->getFormDisplay('component_wrapper', 'component_wrapper');
    $form_display
      ->setComponent($field_name, [
        'type' => 'inline_entity_form_complex',
        'weight' => 5,
      ])
      ->save();

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

  /**
   * Create image style.
   */
  public function createImageStyle(string $name, string $label, array $effect_info) {
    $image_style_storage = $this->entityTypeManager->getStorage('image_style');
    $style = $image_style_storage->load($name);
    if (!$style) {
      $style = $image_style_storage->create([
        'name' => $name,
        'label' => $label,
      ]);
      $effect = \Drupal::service('plugin.manager.image.effect')
        ->createInstance($effect_info['id'], $effect_info);
      $style->addImageEffect($effect->getConfiguration());
      $style->save();
    }
  }

  /**
   * Create image styles for component.
   */
  public function createImageStylesComponent($componentPath = '', $config = []) {
    if (isset($config['image_styles'])) {
      $image_styles = $config['image_styles'];
      if (is_array($image_styles)) {
        $image_styles_definitions = $this->entityTypeManager->getStorage('image_style')
          ->getQuery()
          ->accessCheck()
          ->execute();
        foreach ($image_styles as $image_style) {
          $image_style_path = $componentPath . "/config/image.style.$image_style.yml";
          $yml = $this->parseYaml($image_style_path);
          if ($yml) {
            $name = $yml['name'];
            if ($name && !isset($image_styles_definitions[$name])) {
              $label = $yml['label'];
              $effect_info = $yml['effects'];
              if ($effect_info) {
                $effect_info = current($effect_info);
                if (isset($effect_info['uuid'])) {
                  unset($effect_info['uuid']);
                }
              }
              $this->createImageStyle($name, $label, $effect_info);
            }
          }
        }
      }
    }
  }

}
