<?php

namespace Drupal\Core\Config\Entity;

use Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException;
use Drupal\Core\Entity\EntityType;
use Drupal\Core\Config\ConfigPrefixLengthException;

/**
 * Provides an implementation of a configuration entity type and its metadata.
 */
class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {

  /**
   * The config prefix set in the configuration entity type annotation.
   *
   * @var string
   *
   * @see \Drupal\Core\Config\Entity\ConfigEntityTypeInterface::getConfigPrefix()
   */
  protected $config_prefix;

  /**
   * {@inheritdoc}
   */
  protected $static_cache = FALSE;

  /**
   * Keys that are stored key value store for fast lookup.
   *
   * @var array
   */
  protected $lookup_keys = [];

  /**
   * The list of configuration entity properties to export from the annotation.
   *
   * @var array
   */
  protected $config_export = [];

  /**
   * The result of merging config_export annotation with the defaults.
   *
   * This is stored on the class so that it does not have to be recalculated.
   *
   * @var array
   */
  protected $mergedConfigExport = [];

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException
   *   Exception thrown when the provided class is not an instance of
   *   \Drupal\Core\Config\Entity\ConfigEntityStorage.
   */
  public function __construct($definition) {
    // Ensure a default list cache tag is set; do this before calling the parent
    // constructor, because we want "Configuration System style" cache tags.
    if (empty($this->list_cache_tags)) {
      $this->list_cache_tags = ['config:' . $definition['id'] . '_list'];
    }

    parent::__construct($definition);
    // Always add a default 'uuid' key.
    $this->entity_keys['uuid'] = 'uuid';
    $this->entity_keys['langcode'] = 'langcode';
    $this->handlers += [
      'storage' => 'Drupal\Core\Config\Entity\ConfigEntityStorage',
    ];
    $this->lookup_keys[] = 'uuid';
  }

  /**
   * {@inheritdoc}
   */
  public function getConfigPrefix() {
    // Ensure that all configuration entities are prefixed by the name of the
    // module that provides the configuration entity type.
    if (isset($this->config_prefix)) {
      $config_prefix = $this->provider . '.' . $this->config_prefix;
    }
    else {
      $config_prefix = $this->provider . '.' . $this->id();
    }

    if (strlen($config_prefix) > static::PREFIX_LENGTH) {
      throw new ConfigPrefixLengthException("The configuration file name prefix $config_prefix exceeds the maximum character limit of " . static::PREFIX_LENGTH);
    }
    return $config_prefix;
  }

  /**
   * {@inheritdoc}
   */
  public function getBaseTable() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getRevisionDataTable() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getRevisionTable() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getDataTable() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getConfigDependencyKey() {
    return 'config';
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException
   *   Exception thrown when the provided class is not an instance of
   *   \Drupal\Core\Config\Entity\ConfigEntityStorage.
   *
   * @see \Drupal\Core\Config\Entity\ConfigEntityStorage
   */
  protected function checkStorageClass($class) {
    if (!is_a($class, 'Drupal\Core\Config\Entity\ConfigEntityStorage', TRUE)) {
      throw new ConfigEntityStorageClassException("$class is not \\Drupal\\Core\\Config\\Entity\\ConfigEntityStorage or it does not extend it");
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertiesToExport($id = NULL) {
    if (!empty($this->mergedConfigExport)) {
      return $this->mergedConfigExport;
    }
    if (!empty($this->config_export)) {
      // Always add default properties to be exported.
      $this->mergedConfigExport = [
        'uuid' => 'uuid',
        'langcode' => 'langcode',
        'status' => 'status',
        'dependencies' => 'dependencies',
        'third_party_settings' => 'third_party_settings',
        '_core' => '_core',
      ];
      foreach ($this->config_export as $property => $name) {
        if (is_numeric($property)) {
          $this->mergedConfigExport[$name] = $name;
        }
        else {
          $this->mergedConfigExport[$property] = $name;
        }
      }
    }
    else {
      // @todo https://www.drupal.org/project/drupal/issues/2949021 Deprecate
      //   fallback to schema.
      $config_name = $this->getConfigPrefix() . '.' . $id;
      $definition = \Drupal::service('config.typed')->getDefinition($config_name);
      if (!isset($definition['mapping'])) {
        return NULL;
      }
      @trigger_error(sprintf('Entity type "%s" is using config schema as a fallback for a missing `config_export` definition is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. See https://www.drupal.org/node/2949023.', $this->id()), E_USER_DEPRECATED);
      $this->mergedConfigExport = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
    }
    return $this->mergedConfigExport;
  }

  /**
   * {@inheritdoc}
   */
  public function getLookupKeys() {
    return $this->lookup_keys;
  }

}
