<?php

namespace Drupal\dkan_datastore\DataDictionary;

use Drupal\Component\Uuid\UuidInterface;

use Drupal\dkan_common\Storage\DatabaseConnectionFactoryInterface;
use Drupal\Core\Config\ConfigFactory;

use PDLT\ConverterInterface;
use RootedData\RootedJsonData;

/**
 * Alter table query builder.
 */
abstract class AlterTableQueryBuilderBase implements AlterTableQueryBuilderInterface {

  /**
   * Database connection factory.
   */
  protected DatabaseConnectionFactoryInterface $databaseConnectionFactory;

  /**
   * Internal to SQL date format converter.
   */
  protected ConverterInterface $dateFormatConverter;

  /**
   * Uuid service.
   *
   * @var \Drupal\Component\Uuid\UuidInterface
   */
  protected $uuid;

  /**
   * Alter query class name.
   */
  protected string $queryClass;

  /**
   * Alter query table name.
   */
  protected string $table;

  /**
   * Alter query field names, types, and formats.
   *
   * @var array[]
   *
   * Example format:
   * @code
   * [
   *   [
   *     'name' => 'some_date_field',
   *     'title' => 'Some Date Field', // optional
   *     'type' => 'date',
   *     'format' => '%Y-%m-%d' // optional
   *   ],
   * ]
   * @endcode
   */
  protected array $fields = [];

  /**
   * Alter query index names, types, and formats.
   *
   * @var array[]
   *
   * Example format:
   * @code
   * [
   *   [
   *     'name' => 'some_date_field', // optional
   *     'type' => 'fulltext', // optional
   *     'fields' => [
   *       [
   *         'name' => 'field_a',
   *         'length' => 10 // optional
   *       ],
   *       [
   *         'name' => 'field_b',
   *         'length' => 8 // optional
   *       ],
   *     ]
   *   ]
   * ]
   * @endcode
   */
  protected array $indexes = [];

  /**
   * ConfigFactory object.
   *
   * @var \Drupal\Core\Config\ConfigFactory
   */
  protected $configFactory;

  /**
   * Create an alter table query factory.
   *
   * @param \Drupal\dkan_common\Storage\DatabaseConnectionFactoryInterface $database_connection_factory
   *   Database connection factory.
   * @param \PDLT\ConverterInterface $date_format_converter
   *   PHP Date Language Tool Converter.
   * @param \Drupal\Component\Uuid\UuidInterface $uuid
   *   Uuid generator service.
   * @param \Drupal\Core\Config\ConfigFactory $configFactory
   *   ConfigFactory service.
   */
  public function __construct(
    DatabaseConnectionFactoryInterface $database_connection_factory,
    ConverterInterface $date_format_converter,
    UuidInterface $uuid,
    ConfigFactory $configFactory,
  ) {
    $this->databaseConnectionFactory = $database_connection_factory;
    $this->dateFormatConverter = $date_format_converter;
    $this->uuid = $uuid;
    $this->configFactory = $configFactory;
  }

  /**
   * {@inheritdoc}
   */
  public function setConnectionTimeout(int $timeout): self {
    $this->databaseConnectionFactory->setConnectionTimeout($timeout);

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setTable(string $table): self {
    $this->table = $table;

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function addDataDictionary(RootedJsonData $dictionary): self {
    $this->addFields($dictionary->{'$.data.fields'} ?? []);
    $this->addIndexes($dictionary->{'$.data.indexes'} ?? []);

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function addFields(array $fields): self {
    // Validate and set fields.
    $this->fields = array_map([$this, 'validateField'], $fields);

    return $this;
  }

  /**
   * Validate data-dictionary field.
   *
   * Validate required properties, and fill empty optional properties.
   *
   * @param array $field
   *   Field to validate.
   *
   * @return array
   *   Validated field.
   */
  protected function validateField(array $field): array {
    // Validate required properties.
    if (empty($field['name']) || empty($field['type'])) {
      throw new \UnexpectedValueException('"name" and "type" are required properties for data-dictionary fields.');
    }

    // Provide default values for optional properties.
    $field['title'] ??= '';
    $field['format'] ??= '';

    return $field;
  }

  /**
   * {@inheritdoc}
   */
  public function addIndexes(array $indexes): self {
    // Validate and set indexes.
    $this->indexes = array_map($this->validateIndex(...), $indexes);

    return $this;
  }

  /**
   * Validate data-dictionary index.
   *
   * Validate required properties, and fill empty optional properties.
   *
   * @param array $index
   *   Index to validate.
   *
   * @return array
   *   Validated index.
   */
  public function validateIndex(array $index): array {
    // Validate nested properties.
    foreach (array_keys($index['fields']) as $key) {
      // Validate required properties.
      if (empty($index['fields'][$key]['name'])) {
        throw new \UnexpectedValueException('"name" is a required property for data-dictionary index fields.');
      }
      // Provide default values for optional properties.
      $index['fields'][$key]['length'] ??= NULL;
    }

    // Provide default values for optional properties.
    $index['name'] ??= $this->generateUniqueId();
    $index['type'] ??= 'index';
    $index['description'] ??= '';

    return $index;
  }

  /**
   * Generate a unique alphanumeric identifier.
   *
   * @return string
   *   Unique identifier.
   */
  protected function generateUniqueId(): string {
    return str_replace('-', '', $this->uuid->generate());
  }

  /**
   * {@inheritdoc}
   */
  abstract public function getQuery(): AlterTableQueryInterface;

}
