<?php

namespace Drupal\eb\Service;

use Drupal\eb\PluginManager\EbExtensionPluginManager;

/**
 * Builds operation data arrays from definition-format data.
 *
 * This service is the single source of truth for converting
 * definition data (from UI or EbDefinition entity)
 * into operation arrays for validation and execution.
 *
 * Core definition types (bundles, fields, displays, menus) are handled
 * directly. Extension-owned definition types (e.g., field_group_definitions)
 * are handled by calling buildOperations() on each extension plugin.
 */
class OperationDataBuilder implements OperationDataBuilderInterface {

  /**
   * Core definition format keys recognized by Entity Builder.
   *
   * Extension keys are retrieved dynamically from the extension plugin manager.
   *
   * @var array<string>
   */
  protected const CORE_DEFINITION_KEYS = [
    'bundle_definitions',
    'field_definitions',
    'display_field_definitions',
    'menu_definitions',
  ];

  /**
   * Constructor.
   *
   * @param \Drupal\eb\PluginManager\EbExtensionPluginManager $extensionManager
   *   The extension plugin manager.
   */
  public function __construct(
    protected EbExtensionPluginManager $extensionManager,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function isDefinitionFormat(array $data): bool {
    $allKeys = $this->getAllDefinitionKeys();

    // Check if any definition format keys exist.
    foreach ($allKeys as $key) {
      if (isset($data[$key]) && is_array($data[$key])) {
        return TRUE;
      }
    }

    // Also check if first element is an operation (has 'operation' key).
    if (isset($data[0]) && is_array($data[0]) && isset($data[0]['operation'])) {
      return FALSE;
    }

    // If there are string keys that match definition format, it's definition.
    return !empty(array_intersect_key($data, array_flip($allKeys)));
  }

  /**
   * Gets all definition keys (core + extensions).
   *
   * @return array<string>
   *   Array of all valid definition keys.
   */
  protected function getAllDefinitionKeys(): array {
    $extensionKeys = $this->extensionManager->getAllDefinitionKeys();
    return array_unique(array_merge(self::CORE_DEFINITION_KEYS, $extensionKeys));
  }

  /**
   * {@inheritdoc}
   */
  public function build(array $data): array {
    $operations = [];

    // Build core operations.
    $this->buildBundleOperations($data, $operations);
    $this->buildFieldOperations($data, $operations);
    $this->buildDisplayOperations($data, $operations);
    $this->buildMenuOperations($data, $operations);

    // Build extension operations.
    foreach ($this->extensionManager->getExtensions() as $extension) {
      $extensionOperations = $extension->buildOperations($data);
      if (!empty($extensionOperations)) {
        $operations = array_merge($operations, $extensionOperations);
      }
    }

    return $operations;
  }

  /**
   * Build bundle creation operations.
   *
   * @param array<string, mixed> $data
   *   The definition data.
   * @param array<int, array<string, mixed>> &$operations
   *   The operations array to append to.
   */
  protected function buildBundleOperations(array $data, array &$operations): void {
    if (empty($data['bundle_definitions']) || !is_array($data['bundle_definitions'])) {
      return;
    }

    foreach ($data['bundle_definitions'] as $bundle) {
      if (empty($bundle) || !is_array($bundle)) {
        continue;
      }
      $operations[] = [
        'operation' => 'create_bundle',
        'entity_type' => $bundle['entity_type'] ?? '',
        'bundle_id' => $bundle['bundle_id'] ?? $bundle['machine_name'] ?? '',
        'label' => $bundle['label'] ?? '',
        'description' => $bundle['description'] ?? '',
        'settings' => $bundle['settings'] ?? [],
      ];
    }
  }

  /**
   * Build field creation operations.
   *
   * @param array<string, mixed> $data
   *   The definition data.
   * @param array<int, array<string, mixed>> &$operations
   *   The operations array to append to.
   */
  protected function buildFieldOperations(array $data, array &$operations): void {
    if (empty($data['field_definitions']) || !is_array($data['field_definitions'])) {
      return;
    }

    foreach ($data['field_definitions'] as $field) {
      if (empty($field) || !is_array($field)) {
        continue;
      }
      $operationData = [
        'operation' => 'create_field',
        'entity_type' => $field['entity_type'] ?? '',
        'bundle' => $field['bundle'] ?? '',
        'field_name' => $field['field_name'] ?? $field['machine_name'] ?? '',
        'label' => $field['label'] ?? '',
        'field_type' => $field['field_type'] ?? $field['type'] ?? '',
        'required' => $field['required'] ?? FALSE,
        'translatable' => $field['translatable'] ?? FALSE,
        'cardinality' => $field['cardinality'] ?? 1,
        'description' => $field['description'] ?? '',
      ];

      // Add optional field properties.
      $this->addOptionalFieldProperties($field, $operationData);

      $operations[] = $operationData;
    }
  }

  /**
   * Add optional field properties to operation data.
   *
   * @param array<string, mixed> $field
   *   The field data.
   * @param array<string, mixed> &$operationData
   *   The operation data to modify.
   */
  protected function addOptionalFieldProperties(array $field, array &$operationData): void {
    $optionalKeys = [
      'widget',
      'formatter',
      'widget_settings',
      'formatter_settings',
      'field_storage_settings',
      'field_config_settings',
      'default_value',
      'weight',
      'label_display',
    ];

    foreach ($optionalKeys as $key) {
      if (!empty($field[$key]) || (isset($field[$key]) && $field[$key] === 0)) {
        $operationData[$key] = $field[$key];
      }
    }
  }

  /**
   * Build display configuration operations.
   *
   * @param array<string, mixed> $data
   *   The definition data.
   * @param array<int, array<string, mixed>> &$operations
   *   The operations array to append to.
   */
  protected function buildDisplayOperations(array $data, array &$operations): void {
    if (empty($data['display_field_definitions']) || !is_array($data['display_field_definitions'])) {
      return;
    }

    foreach ($data['display_field_definitions'] as $display) {
      if (empty($display) || !is_array($display)) {
        continue;
      }
      $displayType = $display['display_type'] ?? 'form';
      $operationId = $displayType === 'form' ? 'configure_form_mode' : 'configure_view_mode';

      $operationData = [
        'operation' => $operationId,
        'entity_type' => $display['entity_type'] ?? '',
        'bundle' => $display['bundle'] ?? '',
        'mode' => $display['mode'] ?? 'default',
        'field_name' => $display['field_name'] ?? '',
      ];

      $this->addDisplayTypeProperties($display, $displayType, $operationData);

      if (isset($display['weight'])) {
        $operationData['weight'] = $display['weight'];
      }
      if (!empty($display['group'])) {
        $operationData['group'] = $display['group'];
      }

      $operations[] = $operationData;
    }
  }

  /**
   * Add display type specific properties.
   *
   * @param array<string, mixed> $display
   *   The display data.
   * @param string $displayType
   *   The display type ('form' or 'view').
   * @param array<string, mixed> &$operationData
   *   The operation data to modify.
   */
  protected function addDisplayTypeProperties(array $display, string $displayType, array &$operationData): void {
    if ($displayType === 'form') {
      $operationData['widget'] = $display['widget'] ?? '';
      if (!empty($display['widget_settings'])) {
        $operationData['widget_settings'] = $display['widget_settings'];
      }
    }
    else {
      $operationData['formatter'] = $display['formatter'] ?? '';
      if (!empty($display['formatter_settings'])) {
        $operationData['formatter_settings'] = $display['formatter_settings'];
      }
      if (isset($display['label_display'])) {
        $operationData['label_display'] = $display['label_display'];
      }
    }
  }

  /**
   * Build menu creation operations.
   *
   * @param array<string, mixed> $data
   *   The definition data.
   * @param array<int, array<string, mixed>> &$operations
   *   The operations array to append to.
   */
  protected function buildMenuOperations(array $data, array &$operations): void {
    if (empty($data['menu_definitions']) || !is_array($data['menu_definitions'])) {
      return;
    }

    foreach ($data['menu_definitions'] as $menu) {
      if (empty($menu) || !is_array($menu)) {
        continue;
      }
      $operations[] = [
        'operation' => 'create_menu',
        'menu_id' => $menu['menu_id'] ?? $menu['machine_name'] ?? '',
        'label' => $menu['label'] ?? '',
        'description' => $menu['description'] ?? '',
        'settings' => $menu['settings'] ?? [],
      ];
    }
  }

}
