<?php

namespace Drupal\Tests\eb\Traits;

use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;

/**
 * Provides helper methods for creating fields in tests.
 */
trait FieldCreationTrait {

  /**
   * Creates a field storage configuration.
   *
   * @param string $entityType
   *   The entity type ID.
   * @param string $fieldName
   *   The field name.
   * @param string $fieldType
   *   The field type (text, integer, entity_reference, etc.).
   * @param array<string, mixed> $settings
   *   Additional settings.
   *
   * @return \Drupal\field\Entity\FieldStorageConfig
   *   The created field storage.
   */
  protected function createFieldStorage(
    string $entityType,
    string $fieldName,
    string $fieldType,
    array $settings = [],
  ): FieldStorageConfig {
    $storage = FieldStorageConfig::create([
      'field_name' => $fieldName,
      'entity_type' => $entityType,
      'type' => $fieldType,
      'cardinality' => $settings['cardinality'] ?? 1,
      'settings' => $settings['settings'] ?? [],
    ]);
    $storage->save();
    return $storage;
  }

  /**
   * Creates a field configuration (instance).
   *
   * @param string $entityType
   *   The entity type ID.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param string $label
   *   The field label.
   * @param array<string, mixed> $settings
   *   Additional settings.
   *
   * @return \Drupal\field\Entity\FieldConfig
   *   The created field config.
   */
  protected function createFieldConfig(
    string $entityType,
    string $bundle,
    string $fieldName,
    string $label,
    array $settings = [],
  ): FieldConfig {
    $config = FieldConfig::create([
      'field_name' => $fieldName,
      'entity_type' => $entityType,
      'bundle' => $bundle,
      'label' => $label,
      'required' => $settings['required'] ?? FALSE,
      'settings' => $settings['settings'] ?? [],
    ]);
    $config->save();
    return $config;
  }

  /**
   * Creates a complete field (storage + config).
   *
   * @param string $entityType
   *   The entity type ID.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param string $fieldType
   *   The field type.
   * @param string $label
   *   The field label.
   * @param array<string, mixed> $options
   *   Additional options (cardinality, required, settings).
   *
   * @return \Drupal\field\Entity\FieldConfig
   *   The created field config.
   */
  protected function createCompleteField(
    string $entityType,
    string $bundle,
    string $fieldName,
    string $fieldType,
    string $label,
    array $options = [],
  ): FieldConfig {
    // Check if storage already exists.
    $storage = FieldStorageConfig::loadByName($entityType, $fieldName);
    if (!$storage) {
      $storage = $this->createFieldStorage($entityType, $fieldName, $fieldType, $options);
    }

    return $this->createFieldConfig($entityType, $bundle, $fieldName, $label, $options);
  }

  /**
   * Creates a text field.
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param string $label
   *   The field label.
   * @param array<string, mixed> $options
   *   Additional options.
   *
   * @return \Drupal\field\Entity\FieldConfig
   *   The field config.
   */
  protected function createTextField(
    string $entityType,
    string $bundle,
    string $fieldName,
    string $label,
    array $options = [],
  ): FieldConfig {
    return $this->createCompleteField($entityType, $bundle, $fieldName, 'text', $label, $options);
  }

  /**
   * Creates a long text field (text_long).
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param string $label
   *   The field label.
   * @param array<string, mixed> $options
   *   Additional options.
   *
   * @return \Drupal\field\Entity\FieldConfig
   *   The field config.
   */
  protected function createLongTextField(
    string $entityType,
    string $bundle,
    string $fieldName,
    string $label,
    array $options = [],
  ): FieldConfig {
    return $this->createCompleteField($entityType, $bundle, $fieldName, 'text_long', $label, $options);
  }

  /**
   * Creates an integer field.
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param string $label
   *   The field label.
   * @param array<string, mixed> $options
   *   Additional options.
   *
   * @return \Drupal\field\Entity\FieldConfig
   *   The field config.
   */
  protected function createIntegerField(
    string $entityType,
    string $bundle,
    string $fieldName,
    string $label,
    array $options = [],
  ): FieldConfig {
    return $this->createCompleteField($entityType, $bundle, $fieldName, 'integer', $label, $options);
  }

  /**
   * Creates a boolean field.
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param string $label
   *   The field label.
   * @param array<string, mixed> $options
   *   Additional options.
   *
   * @return \Drupal\field\Entity\FieldConfig
   *   The field config.
   */
  protected function createBooleanField(
    string $entityType,
    string $bundle,
    string $fieldName,
    string $label,
    array $options = [],
  ): FieldConfig {
    return $this->createCompleteField($entityType, $bundle, $fieldName, 'boolean', $label, $options);
  }

  /**
   * Creates an entity reference field.
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param string $label
   *   The field label.
   * @param string $targetType
   *   The target entity type.
   * @param array<string> $targetBundles
   *   Target bundles (empty for all).
   * @param array<string, mixed> $options
   *   Additional options.
   *
   * @return \Drupal\field\Entity\FieldConfig
   *   The field config.
   */
  protected function createEntityReferenceField(
    string $entityType,
    string $bundle,
    string $fieldName,
    string $label,
    string $targetType,
    array $targetBundles = [],
    array $options = [],
  ): FieldConfig {
    $options['settings'] = array_merge($options['settings'] ?? [], [
      'target_type' => $targetType,
    ]);

    $fieldConfig = $this->createCompleteField(
      $entityType,
      $bundle,
      $fieldName,
      'entity_reference',
      $label,
      $options
    );

    // Set handler settings.
    $handlerSettings = [];
    if ($targetBundles) {
      $handlerSettings['target_bundles'] = array_combine($targetBundles, $targetBundles);
    }
    $fieldConfig->setSetting('handler_settings', $handlerSettings);
    $fieldConfig->save();

    return $fieldConfig;
  }

  /**
   * Creates a list (select) field.
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param string $label
   *   The field label.
   * @param array<string, string> $allowedValues
   *   Allowed values (key => label).
   * @param array<string, mixed> $options
   *   Additional options.
   *
   * @return \Drupal\field\Entity\FieldConfig
   *   The field config.
   */
  protected function createListField(
    string $entityType,
    string $bundle,
    string $fieldName,
    string $label,
    array $allowedValues,
    array $options = [],
  ): FieldConfig {
    $options['settings'] = array_merge($options['settings'] ?? [], [
      'allowed_values' => $allowedValues,
    ]);

    return $this->createCompleteField(
      $entityType,
      $bundle,
      $fieldName,
      'list_string',
      $label,
      $options
    );
  }

  /**
   * Asserts that a field storage exists.
   *
   * @param string $entityType
   *   The entity type.
   * @param string $fieldName
   *   The field name.
   */
  protected function assertFieldStorageExistsForType(string $entityType, string $fieldName): void {
    $storage = FieldStorageConfig::loadByName($entityType, $fieldName);
    $this->assertNotNull($storage, "Field storage '$entityType.$fieldName' should exist.");
  }

  /**
   * Asserts that a field config exists.
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   */
  protected function assertFieldConfigExistsForBundle(string $entityType, string $bundle, string $fieldName): void {
    $config = FieldConfig::loadByName($entityType, $bundle, $fieldName);
    $this->assertNotNull($config, "Field config '$entityType.$bundle.$fieldName' should exist.");
  }

  /**
   * Asserts field properties match expected values.
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   * @param array<string, mixed> $expected
   *   Expected properties.
   */
  protected function assertFieldProperties(
    string $entityType,
    string $bundle,
    string $fieldName,
    array $expected,
  ): void {
    $config = FieldConfig::loadByName($entityType, $bundle, $fieldName);
    $this->assertNotNull($config, "Field '$fieldName' should exist.");

    if (isset($expected['label'])) {
      $this->assertEquals($expected['label'], $config->getLabel());
    }

    if (isset($expected['required'])) {
      $this->assertEquals($expected['required'], $config->isRequired());
    }

    if (isset($expected['description'])) {
      $this->assertEquals($expected['description'], $config->getDescription());
    }
  }

  /**
   * Deletes a field completely (config and storage).
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle.
   * @param string $fieldName
   *   The field name.
   */
  protected function deleteField(string $entityType, string $bundle, string $fieldName): void {
    $config = FieldConfig::loadByName($entityType, $bundle, $fieldName);
    if ($config) {
      $config->delete();
    }

    // Check if storage should be deleted (no other instances).
    $storage = FieldStorageConfig::loadByName($entityType, $fieldName);
    if ($storage) {
      $bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entityType);
      $hasOtherInstances = FALSE;

      foreach (array_keys($bundles) as $otherBundle) {
        if ($otherBundle !== $bundle && FieldConfig::loadByName($entityType, $otherBundle, $fieldName)) {
          $hasOtherInstances = TRUE;
          break;
        }
      }

      if (!$hasOtherInstances) {
        $storage->delete();
      }
    }
  }

}
