<?php

declare(strict_types=1);

namespace Drupal\eb\Entity;

use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\Attribute\ConfigEntityType;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\eb\Access\EbDefinitionAccessControlHandler;
use Drupal\user\Entity\User;

/**
 * Defines the Entity Builder Definition config entity.
 *
 * Stores entity and field definitions that can be applied to create
 * Drupal bundles, fields, and display configurations.
 */
#[ConfigEntityType(
  id: 'eb_definition',
  label: new TranslatableMarkup('Entity Builder Definition'),
  label_collection: new TranslatableMarkup('Entity Builder Definitions'),
  label_singular: new TranslatableMarkup('definition'),
  label_plural: new TranslatableMarkup('definitions'),
  config_prefix: 'definition',
  entity_keys: [
    'id' => 'id',
    'label' => 'label',
    'uuid' => 'uuid',
  ],
  handlers: [
    'access' => EbDefinitionAccessControlHandler::class,
    'list_builder' => 'Drupal\eb\EbDefinitionListBuilder',
    'form' => [
      'apply' => 'Drupal\eb\Form\EbDefinitionApplyForm',
      'delete' => EntityDeleteForm::class,
    ],
  ],
  links: [
    'collection' => '/admin/config/development/eb/definitions',
    'canonical' => '/admin/config/development/eb/definitions/{eb_definition}',
    'apply-form' => '/admin/config/development/eb/definitions/{eb_definition}/apply',
    'delete-form' => '/admin/config/development/eb/definitions/{eb_definition}/delete',
  ],
  admin_permission: 'administer entity builder',
  label_count: [
    'singular' => '@count definition',
    'plural' => '@count definitions',
  ],
  config_export: [
    'id',
    'uuid',
    'label',
    'description',
    'uid',
    'project',
    'dependencies_data',
    'bundle_definitions',
    'field_definitions',
    'field_group_definitions',
    'display_field_definitions',
    'menu_definitions',
    'application_status',
    'applied_date',
    'created',
    'changed',
  ],
)]
class EbDefinition extends ConfigEntityBase implements EntityChangedInterface {

  /**
   * Current YAML format version.
   *
   * Used for export compatibility and version tracking in YAML files.
   */
  public const FORMAT_VERSION = '1.0';

  /**
   * The definition ID.
   */
  protected ?string $id = NULL;

  /**
   * The UUID.
   *
   * @var string
   */
  protected $uuid;

  /**
   * The definition label.
   */
  protected ?string $label = NULL;

  /**
   * The definition description.
   *
   * @var string
   */
  protected string $description = '';

  /**
   * The owner user ID.
   *
   * @var int|null
   */
  protected ?int $uid = NULL;

  /**
   * The project this definition belongs to.
   *
   * @var string|null
   */
  protected ?string $project = NULL;

  /**
   * Module and other dependencies from YAML.
   *
   * @var array<string, mixed>
   */
  protected array $dependencies_data = [];

  /**
   * The bundle definitions (MULTIPLE bundles supported).
   *
   * Array of bundle configurations, each with machine_name, label, etc.
   *
   * @var array<int, array<string, mixed>>
   */
  protected array $bundle_definitions = [];

  /**
   * The field definitions (for ALL bundles in this definition).
   *
   * Array of field configurations including type, widget, formatter, etc.
   * Each field specifies which bundle it belongs to.
   *
   * @var array<int, array<string, mixed>>
   */
  protected array $field_definitions = [];

  /**
   * The field group definitions (visual groupings).
   *
   * Array of field group configurations for form and view displays.
   *
   * @var array<int, array<string, mixed>>
   */
  protected array $field_group_definitions = [];

  /**
   * The display field definitions (flat: one row per field-in-display).
   *
   * Form and view mode configurations for individual field displays.
   *
   * @var array<int, array<string, mixed>>
   */
  protected array $display_field_definitions = [];

  /**
   * The menu definitions.
   *
   * Optional menu structures.
   *
   * @var array<int, array<string, mixed>>
   */
  protected array $menu_definitions = [];

  /**
   * The definition application status.
   *
   * Possible values: draft, applied, outdated.
   *
   * @var string
   */
  protected string $application_status = 'draft';

  /**
   * The timestamp when this definition was applied.
   *
   * @var int
   */
  protected int $applied_date = 0;

  /**
   * The timestamp when this definition was created.
   *
   * @var int
   */
  protected int $created = 0;

  /**
   * The timestamp when this definition was changed.
   *
   * @var int
   */
  protected int $changed = 0;

  /**
   * Gets the definition description.
   */
  public function getDescription(): string {
    return $this->description;
  }

  /**
   * Sets the definition description.
   */
  public function setDescription(string $description): self {
    $this->description = $description;
    return $this;
  }

  /**
   * Gets the owner user ID.
   */
  public function getOwnerId(): ?int {
    return $this->uid;
  }

  /**
   * Sets the owner user ID.
   */
  public function setOwnerId(int $uid): self {
    $this->uid = $uid;
    return $this;
  }

  /**
   * Gets the owner user entity.
   */
  public function getOwner(): ?AccountInterface {
    if ($this->uid === NULL) {
      return NULL;
    }
    return User::load($this->uid);
  }

  /**
   * Gets the project this definition belongs to.
   */
  public function getProject(): ?string {
    return $this->project;
  }

  /**
   * Sets the project this definition belongs to.
   */
  public function setProject(?string $project): self {
    $this->project = $project;
    return $this;
  }

  /**
   * Gets the module and other dependencies.
   *
   * @return array<string, mixed>
   *   Dependencies array with optional 'module' key.
   */
  public function getDependenciesData(): array {
    return $this->dependencies_data;
  }

  /**
   * Sets the module and other dependencies.
   *
   * @param array<string, mixed> $dependenciesData
   *   Dependencies array with optional 'module' key.
   */
  public function setDependenciesData(array $dependenciesData): self {
    $this->dependencies_data = $dependenciesData;
    return $this;
  }

  /**
   * Gets module dependencies.
   *
   * @return array<string>
   *   Array of module machine names.
   */
  public function getModuleDependencies(): array {
    return $this->dependencies_data['module'] ?? [];
  }

  /**
   * Gets the bundle definitions (multiple bundles).
   *
   * @return array<int, array<string, mixed>>
   *   Array of bundle definitions.
   */
  public function getBundleDefinitions(): array {
    return $this->bundle_definitions;
  }

  /**
   * Sets the bundle definitions (multiple bundles).
   *
   * @param array<int, array<string, mixed>> $bundleDefinitions
   *   Array of bundle definitions.
   */
  public function setBundleDefinitions(array $bundleDefinitions): self {
    $this->bundle_definitions = $bundleDefinitions;
    return $this;
  }

  /**
   * Gets a single bundle definition by bundle ID.
   *
   * @param string $bundle_id
   *   Bundle ID.
   *
   * @return array<string, mixed>|null
   *   Bundle definition or NULL if not found.
   */
  public function getBundleDefinition(string $bundle_id): ?array {
    foreach ($this->bundle_definitions as $bundle) {
      if (($bundle['bundle_id'] ?? '') === $bundle_id) {
        return $bundle;
      }
    }
    return NULL;
  }

  /**
   * Adds a bundle definition.
   *
   * @param array<string, mixed> $bundleDefinition
   *   Bundle definition to add.
   */
  public function addBundleDefinition(array $bundleDefinition): self {
    $this->bundle_definitions[] = $bundleDefinition;
    return $this;
  }

  /**
   * Gets the field definitions.
   *
   * @return array<int, array<string, mixed>>
   *   Array of field definitions.
   */
  public function getFieldDefinitions(): array {
    return $this->field_definitions;
  }

  /**
   * Sets the field definitions.
   *
   * @param array<int, array<string, mixed>> $fieldDefinitions
   *   Array of field definitions.
   */
  public function setFieldDefinitions(array $fieldDefinitions): self {
    $this->field_definitions = $fieldDefinitions;
    return $this;
  }

  /**
   * Gets the field group definitions.
   *
   * @return array<int, array<string, mixed>>
   *   Array of field group definitions.
   */
  public function getFieldGroupDefinitions(): array {
    return $this->field_group_definitions;
  }

  /**
   * Sets the field group definitions.
   *
   * @param array<int, array<string, mixed>> $fieldGroupDefinitions
   *   Array of field group definitions.
   */
  public function setFieldGroupDefinitions(array $fieldGroupDefinitions): self {
    $this->field_group_definitions = $fieldGroupDefinitions;
    return $this;
  }

  /**
   * Gets the display field definitions.
   *
   * @return array<int, array<string, mixed>>
   *   Array of display field definitions.
   */
  public function getDisplayFieldDefinitions(): array {
    return $this->display_field_definitions;
  }

  /**
   * Sets the display field definitions.
   *
   * @param array<int, array<string, mixed>> $displayFieldDefinitions
   *   Array of display field definitions.
   */
  public function setDisplayFieldDefinitions(array $displayFieldDefinitions): self {
    $this->display_field_definitions = $displayFieldDefinitions;
    return $this;
  }

  /**
   * Gets the menu definitions.
   *
   * @return array<int, array<string, mixed>>
   *   Array of menu definitions.
   */
  public function getMenuDefinitions(): array {
    return $this->menu_definitions;
  }

  /**
   * Sets the menu definitions.
   *
   * @param array<int, array<string, mixed>> $menuDefinitions
   *   Array of menu definitions.
   */
  public function setMenuDefinitions(array $menuDefinitions): self {
    $this->menu_definitions = $menuDefinitions;
    return $this;
  }

  /**
   * Gets count of bundles in this definition.
   */
  public function getBundleCount(): int {
    return count($this->bundle_definitions);
  }

  /**
   * Gets count of fields in this definition.
   */
  public function getFieldCount(): int {
    return count($this->field_definitions);
  }

  /**
   * Gets fields for a specific bundle.
   *
   * @param string $bundle_name
   *   Bundle machine name.
   *
   * @return array<int, array<string, mixed>>
   *   Array of field definitions for this bundle.
   */
  public function getFieldsForBundle(string $bundle_name): array {
    return array_filter($this->field_definitions, function ($field) use ($bundle_name) {
      return ($field['bundle'] ?? '') === $bundle_name;
    });
  }

  /**
   * Gets all unique entity types from bundle definitions.
   *
   * @return array<string>
   *   Array of unique entity type machine names.
   */
  public function getEntityTypes(): array {
    $entityTypes = [];
    foreach ($this->bundle_definitions as $bundle) {
      $entityType = $bundle['entity_type'] ?? '';
      if ($entityType !== '' && !in_array($entityType, $entityTypes, TRUE)) {
        $entityTypes[] = $entityType;
      }
    }
    return $entityTypes;
  }

  /**
   * Gets the definition application status.
   */
  public function getApplicationStatus(): string {
    return $this->application_status;
  }

  /**
   * Sets the definition application status.
   */
  public function setApplicationStatus(string $applicationStatus): self {
    $this->application_status = $applicationStatus;
    return $this;
  }

  /**
   * Gets the applied date timestamp.
   */
  public function getAppliedDate(): int {
    return $this->applied_date;
  }

  /**
   * Sets the applied date timestamp.
   */
  public function setAppliedDate(int $appliedDate): self {
    $this->applied_date = $appliedDate;
    return $this;
  }

  /**
   * Gets the created timestamp.
   */
  public function getCreatedTime(): int {
    return $this->created;
  }

  /**
   * Sets the created timestamp.
   */
  public function setCreatedTime(int $created): self {
    $this->created = $created;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getChangedTime(): int {
    return $this->changed;
  }

  /**
   * {@inheritdoc}
   */
  public function setChangedTime($timestamp) {
    $this->changed = $timestamp;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getChangedTimeAcrossTranslations(): int {
    return $this->changed;
  }

  /**
   * Checks if this definition has been applied.
   */
  public function isApplied(): bool {
    return $this->application_status === 'applied';
  }

  /**
   * Checks if this definition is a draft.
   */
  public function isDraft(): bool {
    return $this->application_status === 'draft';
  }

  /**
   * Checks if this definition is outdated.
   */
  public function isOutdated(): bool {
    return $this->application_status === 'outdated';
  }

  /**
   * Marks this definition as applied.
   */
  public function markAsApplied(): self {
    $this->application_status = 'applied';
    $this->applied_date = time();
    return $this;
  }

  /**
   * Marks this definition as outdated.
   */
  public function markAsOutdated(): self {
    $this->application_status = 'outdated';
    return $this;
  }

  /**
   * Converts definition to exportable array.
   *
   * Returns a structured array suitable for YAML export. The output format
   * is compatible with import, enabling round-trip capability.
   *
   * @return array<string, mixed>
   *   Definition data suitable for YAML export with keys:
   *   - version: YAML format version
   *   - id: Definition machine name
   *   - label: Human-readable label
   *   - description: Optional description
   *   - bundle_definitions: Bundle configurations
   *   - field_definitions: Field configurations
   *   - field_group_definitions: Field group configurations
   *   - display_field_definitions: Display configurations
   *   - menu_definitions: Menu configurations
   *   Empty arrays are included but can be filtered by caller if needed.
   */
  public function toExportArray(): array {
    $data = [
      'version' => self::FORMAT_VERSION,
      'id' => $this->id(),
      'label' => $this->label(),
    ];

    // Add description if not empty.
    if (!empty($this->description)) {
      $data['description'] = $this->description;
    }

    // Add dependencies if present.
    if (!empty($this->dependencies_data)) {
      $data['dependencies'] = $this->dependencies_data;
    }

    // Add all definition arrays (include empty to maintain structure).
    $data['bundle_definitions'] = $this->bundle_definitions;
    $data['field_definitions'] = $this->field_definitions;

    // Only include optional arrays if they have data.
    if (!empty($this->field_group_definitions)) {
      $data['field_group_definitions'] = $this->field_group_definitions;
    }

    if (!empty($this->display_field_definitions)) {
      $data['display_field_definitions'] = $this->display_field_definitions;
    }

    if (!empty($this->menu_definitions)) {
      $data['menu_definitions'] = $this->menu_definitions;
    }

    return $data;
  }

  /**
   * {@inheritdoc}
   */
  public function preSave(EntityStorageInterface $storage): void {
    parent::preSave($storage);

    // Auto-set owner on creation if not explicitly set.
    if ($this->isNew() && $this->uid === NULL) {
      $this->uid = (int) \Drupal::currentUser()->id();
    }

    // Set created timestamp if not set.
    if ($this->created === 0) {
      $this->created = time();
    }

    // Update changed timestamp.
    $this->changed = time();
  }

}
