<?php

namespace Drupal\eb\Service;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Field\FormatterPluginManager;
use Drupal\Core\Field\WidgetPluginManager;

/**
 * Discovery service for field types, widgets, formatters, and entity types.
 *
 * Provides a unified API for discovering Drupal plugin definitions and entity
 * type information needed by Entity Builder operations and validators.
 */
class DiscoveryService implements DiscoveryServiceInterface {

  /**
   * Cache IDs for different discovery types.
   */
  protected const CACHE_ID_FIELD_TYPES = 'eb:field_types';
  protected const CACHE_ID_WIDGETS = 'eb:widgets';
  protected const CACHE_ID_FORMATTERS = 'eb:formatters';
  protected const CACHE_ID_ENTITY_TYPES = 'eb:entity_types';
  protected const CACHE_ID_BUNDLES_PREFIX = 'eb:bundles';

  /**
   * Cache tags for discovery.
   *
   * @var array<string>
   */
  protected const CACHE_TAGS = ['eb_discovery'];

  /**
   * Constructor.
   *
   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $fieldTypeManager
   *   The field type plugin manager.
   * @param \Drupal\Core\Field\WidgetPluginManager $widgetManager
   *   The widget plugin manager.
   * @param \Drupal\Core\Field\FormatterPluginManager $formatterManager
   *   The formatter plugin manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundleInfo
   *   The entity type bundle info service.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   *   The cache backend.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory.
   */
  public function __construct(
    protected FieldTypePluginManagerInterface $fieldTypeManager,
    protected WidgetPluginManager $widgetManager,
    protected FormatterPluginManager $formatterManager,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected EntityTypeBundleInfoInterface $bundleInfo,
    protected CacheBackendInterface $cache,
    protected ConfigFactoryInterface $configFactory,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function getAllFieldTypes(): array {
    $cached = $this->cache->get(self::CACHE_ID_FIELD_TYPES);
    if ($cached !== FALSE) {
      return $cached->data;
    }

    $field_types = [];
    $definitions = $this->fieldTypeManager->getDefinitions();

    foreach ($definitions as $id => $definition) {
      $field_types[$id] = $this->normalizeFieldTypeDefinition($id, $definition);
    }

    $this->cache->set(
      self::CACHE_ID_FIELD_TYPES,
      $field_types,
      CacheBackendInterface::CACHE_PERMANENT,
      array_merge(self::CACHE_TAGS, ['config:field.storage'])
    );

    return $field_types;
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldTypeDefinition(string $fieldType): ?array {
    $all_types = $this->getAllFieldTypes();
    return $all_types[$fieldType] ?? NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function fieldTypeExists(string $fieldType): bool {
    return $this->getFieldTypeDefinition($fieldType) !== NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldTypesByCategory(): array {
    $all_types = $this->getAllFieldTypes();
    $by_category = [];

    foreach ($all_types as $id => $definition) {
      $category = $definition['category'] ?? 'General';
      $by_category[$category][$id] = $definition;
    }

    ksort($by_category);
    return $by_category;
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultWidget(string $fieldType): ?string {
    $definition = $this->getFieldTypeDefinition($fieldType);
    return $definition['default_widget'] ?? NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultFormatter(string $fieldType): ?string {
    $definition = $this->getFieldTypeDefinition($fieldType);
    return $definition['default_formatter'] ?? NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getRawFieldTypeDefaults(string $fieldType): ?array {
    $definition = $this->fieldTypeManager->getDefinition($fieldType, FALSE);
    if ($definition === NULL) {
      return NULL;
    }

    $class = $definition['class'] ?? NULL;
    if ($class === NULL || !class_exists($class)) {
      return NULL;
    }

    $storageSettings = method_exists($class, 'defaultStorageSettings')
      ? $class::defaultStorageSettings() : [];
    $configSettings = method_exists($class, 'defaultFieldSettings')
      ? $class::defaultFieldSettings() : [];

    if (empty($storageSettings) && empty($configSettings)) {
      return NULL;
    }

    return [
      'storage' => $storageSettings,
      'config' => $configSettings,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getAllWidgets(): array {
    $cached = $this->cache->get(self::CACHE_ID_WIDGETS);
    if ($cached !== FALSE) {
      return $cached->data;
    }

    $widgets = [];
    $definitions = $this->widgetManager->getDefinitions();

    foreach ($definitions as $id => $definition) {
      $widgets[$id] = $this->normalizeWidgetDefinition($id, $definition);
    }

    $this->cache->set(
      self::CACHE_ID_WIDGETS,
      $widgets,
      CacheBackendInterface::CACHE_PERMANENT,
      array_merge(self::CACHE_TAGS, ['config:core.entity_form_display'])
    );

    return $widgets;
  }

  /**
   * {@inheritdoc}
   */
  public function getWidgetsForFieldType(string $fieldType): array {
    $all_widgets = $this->getAllWidgets();
    $compatible = [];

    foreach ($all_widgets as $id => $definition) {
      if (in_array($fieldType, $definition['field_types'], TRUE)) {
        $compatible[$id] = $definition;
      }
    }

    return $compatible;
  }

  /**
   * {@inheritdoc}
   */
  public function getWidgetDefinition(string $widget): ?array {
    $all_widgets = $this->getAllWidgets();
    return $all_widgets[$widget] ?? NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function widgetExists(string $widget): bool {
    return $this->getWidgetDefinition($widget) !== NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function isWidgetCompatible(string $widget, string $fieldType): bool {
    $definition = $this->getWidgetDefinition($widget);
    if ($definition === NULL) {
      return FALSE;
    }

    return in_array($fieldType, $definition['field_types'], TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function getAllFormatters(): array {
    $cached = $this->cache->get(self::CACHE_ID_FORMATTERS);
    if ($cached !== FALSE) {
      return $cached->data;
    }

    $formatters = [];
    $definitions = $this->formatterManager->getDefinitions();

    foreach ($definitions as $id => $definition) {
      $formatters[$id] = $this->normalizeFormatterDefinition($id, $definition);
    }

    $this->cache->set(
      self::CACHE_ID_FORMATTERS,
      $formatters,
      CacheBackendInterface::CACHE_PERMANENT,
      array_merge(self::CACHE_TAGS, ['config:core.entity_view_display'])
    );

    return $formatters;
  }

  /**
   * {@inheritdoc}
   */
  public function getFormattersForFieldType(string $fieldType): array {
    $all_formatters = $this->getAllFormatters();
    $compatible = [];

    foreach ($all_formatters as $id => $definition) {
      if (in_array($fieldType, $definition['field_types'], TRUE)) {
        $compatible[$id] = $definition;
      }
    }

    return $compatible;
  }

  /**
   * {@inheritdoc}
   */
  public function getFormatterDefinition(string $formatter): ?array {
    $all_formatters = $this->getAllFormatters();
    return $all_formatters[$formatter] ?? NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function formatterExists(string $formatter): bool {
    return $this->getFormatterDefinition($formatter) !== NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function isFormatterCompatible(string $formatter, string $fieldType): bool {
    $definition = $this->getFormatterDefinition($formatter);
    if ($definition === NULL) {
      return FALSE;
    }

    return in_array($fieldType, $definition['field_types'], TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function getAllEntityTypes(): array {
    $cached = $this->cache->get(self::CACHE_ID_ENTITY_TYPES);
    if ($cached !== FALSE) {
      return $cached->data;
    }

    $entity_types = [];
    $definitions = $this->entityTypeManager->getDefinitions();

    foreach ($definitions as $id => $definition) {
      $entity_types[$id] = $this->normalizeEntityTypeDefinition($id, $definition);
    }

    $this->cache->set(
      self::CACHE_ID_ENTITY_TYPES,
      $entity_types,
      CacheBackendInterface::CACHE_PERMANENT,
      array_merge(self::CACHE_TAGS, ['entity_types'])
    );

    return $entity_types;
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldableEntityTypes(): array {
    $all_types = $this->getAllEntityTypes();
    return array_filter($all_types, fn($type) => $type['fieldable'] === TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedEntityTypes(): array {
    $fieldable_types = $this->getFieldableEntityTypes();

    // Get the configured allowlist.
    $config = $this->configFactory->get('eb.settings');
    $allowlist = $config->get('supported_entity_types');

    // If no allowlist configured, return all fieldable types as fallback.
    if (empty($allowlist) || !is_array($allowlist)) {
      return $fieldable_types;
    }

    // Filter to only include configured entity types that are also fieldable.
    return array_filter(
      $fieldable_types,
      fn($type, $id) => in_array($id, $allowlist, TRUE),
      ARRAY_FILTER_USE_BOTH
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getBundleableEntityTypes(): array {
    $all_types = $this->getAllEntityTypes();
    return array_filter($all_types, fn($type) => $type['bundle_entity_type'] !== NULL);
  }

  /**
   * {@inheritdoc}
   */
  public function getBundlesForEntityType(string $entityType): array {
    $cache_id = self::CACHE_ID_BUNDLES_PREFIX . ':' . $entityType;
    $cached = $this->cache->get($cache_id);
    if ($cached !== FALSE) {
      return $cached->data;
    }

    $bundles = [];
    $bundle_info = $this->bundleInfo->getBundleInfo($entityType);

    foreach ($bundle_info as $bundle_id => $info) {
      $bundles[$bundle_id] = [
        'id' => $bundle_id,
        'label' => $info['label'] ?? $bundle_id,
        'description' => $info['description'] ?? '',
        'translatable' => $info['translatable'] ?? FALSE,
      ];
    }

    $cache_tags = array_merge(self::CACHE_TAGS, ['entity_bundles', "config:{$entityType}_type_list"]);
    $this->cache->set(
      $cache_id,
      $bundles,
      CacheBackendInterface::CACHE_PERMANENT,
      $cache_tags
    );

    return $bundles;
  }

  /**
   * {@inheritdoc}
   */
  public function entityTypeExists(string $entityType): bool {
    $all_types = $this->getAllEntityTypes();
    return isset($all_types[$entityType]);
  }

  /**
   * {@inheritdoc}
   */
  public function bundleExists(string $entityType, string $bundle): bool {
    $bundles = $this->getBundlesForEntityType($entityType);
    return isset($bundles[$bundle]);
  }

  /**
   * {@inheritdoc}
   */
  public function isFieldable(string $entityType): bool {
    $definition = $this->getAllEntityTypes()[$entityType] ?? NULL;
    return $definition !== NULL && $definition['fieldable'] === TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function isBundleable(string $entityType): bool {
    $definition = $this->getAllEntityTypes()[$entityType] ?? NULL;
    return $definition !== NULL && $definition['bundle_entity_type'] !== NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function validateFieldConfiguration(
    string $fieldType,
    ?string $widget = NULL,
    ?string $formatter = NULL,
  ): array {
    $result = [
      'valid' => TRUE,
      'errors' => [],
      'warnings' => [],
      'suggestions' => [],
    ];

    // Validate field type exists.
    if (!$this->fieldTypeExists($fieldType)) {
      $result['valid'] = FALSE;
      $result['errors'][] = "Field type '$fieldType' does not exist.";
      return $result;
    }

    // Get defaults if not specified.
    $default_widget = $this->getDefaultWidget($fieldType);
    $default_formatter = $this->getDefaultFormatter($fieldType);

    // Validate widget.
    if ($widget !== NULL) {
      if (!$this->widgetExists($widget)) {
        $result['valid'] = FALSE;
        $result['errors'][] = "Widget '$widget' does not exist.";
      }
      elseif (!$this->isWidgetCompatible($widget, $fieldType)) {
        $result['valid'] = FALSE;
        $result['errors'][] = "Widget '$widget' is not compatible with field type '$fieldType'.";
        $compatible = $this->getWidgetsForFieldType($fieldType);
        $result['suggestions'] = array_keys($compatible);
      }
    }
    elseif ($default_widget === NULL) {
      $result['warnings'][] = "No default widget defined for field type '$fieldType'.";
    }

    // Validate formatter.
    if ($formatter !== NULL) {
      if (!$this->formatterExists($formatter)) {
        $result['valid'] = FALSE;
        $result['errors'][] = "Formatter '$formatter' does not exist.";
      }
      elseif (!$this->isFormatterCompatible($formatter, $fieldType)) {
        $result['valid'] = FALSE;
        $result['errors'][] = "Formatter '$formatter' is not compatible with field type '$fieldType'.";
        $compatible = $this->getFormattersForFieldType($fieldType);
        $result['suggestions'] = array_keys($compatible);
      }
    }
    elseif ($default_formatter === NULL) {
      $result['warnings'][] = "No default formatter defined for field type '$fieldType'.";
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function getCompleteFieldConfiguration(
    string $fieldType,
    ?string $widget = NULL,
    ?string $formatter = NULL,
  ): ?array {
    $field_definition = $this->getFieldTypeDefinition($fieldType);
    if ($field_definition === NULL) {
      return NULL;
    }

    // Use provided or default widget.
    $widget = $widget ?? $field_definition['default_widget'];
    $widget_definition = $widget !== NULL
      ? $this->getWidgetDefinition($widget)
      : NULL;

    // Use provided or default formatter.
    $formatter = $formatter ?? $field_definition['default_formatter'];
    $formatter_definition = $formatter !== NULL
      ? $this->getFormatterDefinition($formatter)
      : NULL;

    return [
      'field_type' => $field_definition,
      'widget' => $widget_definition,
      'formatter' => $formatter_definition,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function validateEntityBundle(string $entityType, string $bundle): array {
    $result = [
      'valid' => TRUE,
      'errors' => [],
    ];

    // Validate entity type exists.
    if (!$this->entityTypeExists($entityType)) {
      $result['valid'] = FALSE;
      $result['errors'][] = "Entity type '$entityType' does not exist.";
      return $result;
    }

    // Validate entity type is fieldable.
    if (!$this->isFieldable($entityType)) {
      $result['valid'] = FALSE;
      $result['errors'][] = "Entity type '$entityType' is not fieldable.";
      return $result;
    }

    // Validate bundle exists.
    if (!$this->bundleExists($entityType, $bundle)) {
      $result['valid'] = FALSE;
      $result['errors'][] = "Bundle '$bundle' does not exist for entity type '$entityType'.";
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function clearCache(): void {
    $this->cache->delete(self::CACHE_ID_FIELD_TYPES);
    $this->cache->delete(self::CACHE_ID_WIDGETS);
    $this->cache->delete(self::CACHE_ID_FORMATTERS);
    $this->cache->delete(self::CACHE_ID_ENTITY_TYPES);
    // Bundle caches are per entity type, invalidate by tag.
    Cache::invalidateTags(['eb_discovery']);
  }

  /**
   * Normalizes a field type definition.
   *
   * @param string $id
   *   The field type ID.
   * @param mixed $definition
   *   The raw field type definition.
   *
   * @return array<string, mixed>
   *   Normalized definition.
   */
  protected function normalizeFieldTypeDefinition(string $id, mixed $definition): array {
    $label = isset($definition['label']) ? (string) $definition['label'] : $id;
    $description = '';
    if (isset($definition['description'])) {
      $description = is_object($definition['description']) && method_exists($definition['description'], '__toString')
        ? (string) $definition['description']
        : (is_string($definition['description']) ? $definition['description'] : '');
    }

    return [
      'id' => $id,
      'label' => $label,
      'description' => $description,
      'category' => $definition['category'] ?? 'General',
      'default_widget' => $definition['default_widget'] ?? NULL,
      'default_formatter' => $definition['default_formatter'] ?? NULL,
      'class' => $definition['class'] ?? NULL,
      'provider' => $definition['provider'] ?? NULL,
      'cardinality' => $definition['cardinality'] ?? NULL,
      'list_class' => $definition['list_class'] ?? NULL,
    ];
  }

  /**
   * Normalizes a widget definition.
   *
   * @param string $id
   *   The widget ID.
   * @param mixed $definition
   *   The raw widget definition.
   *
   * @return array<string, mixed>
   *   Normalized definition.
   */
  protected function normalizeWidgetDefinition(string $id, mixed $definition): array {
    // Get default settings from the widget class if available.
    $defaultSettings = [];
    if (isset($definition['class']) && method_exists($definition['class'], 'defaultSettings')) {
      $defaultSettings = $definition['class']::defaultSettings();
    }

    return [
      'id' => $id,
      'label' => isset($definition['label']) ? (string) $definition['label'] : $id,
      'description' => isset($definition['description'])
        ? (string) $definition['description']
        : '',
      'field_types' => $definition['field_types'] ?? [],
      'class' => $definition['class'] ?? NULL,
      'provider' => $definition['provider'] ?? NULL,
      'weight' => $definition['weight'] ?? 0,
      'multiple_values' => $definition['multiple_values'] ?? FALSE,
      'default_settings' => $defaultSettings,
    ];
  }

  /**
   * Normalizes a formatter definition.
   *
   * @param string $id
   *   The formatter ID.
   * @param mixed $definition
   *   The raw formatter definition.
   *
   * @return array<string, mixed>
   *   Normalized definition.
   */
  protected function normalizeFormatterDefinition(string $id, mixed $definition): array {
    // Get default settings from the formatter class if available.
    $defaultSettings = [];
    if (isset($definition['class']) && method_exists($definition['class'], 'defaultSettings')) {
      $defaultSettings = $definition['class']::defaultSettings();
    }

    return [
      'id' => $id,
      'label' => isset($definition['label']) ? (string) $definition['label'] : $id,
      'description' => isset($definition['description'])
        ? (string) $definition['description']
        : '',
      'field_types' => $definition['field_types'] ?? [],
      'class' => $definition['class'] ?? NULL,
      'provider' => $definition['provider'] ?? NULL,
      'weight' => $definition['weight'] ?? 0,
      'quickedit' => $definition['quickedit'] ?? [],
      'default_settings' => $defaultSettings,
    ];
  }

  /**
   * Normalizes an entity type definition.
   *
   * @param string $id
   *   The entity type ID.
   * @param mixed $definition
   *   The raw entity type definition.
   *
   * @return array<string, mixed>
   *   Normalized definition.
   */
  protected function normalizeEntityTypeDefinition(string $id, mixed $definition): array {
    $label = $definition->getLabel();
    $label_singular = $definition->getSingularLabel();
    $label_plural = $definition->getPluralLabel();
    $group_label = $definition->getGroupLabel();
    $bundle_label = $definition->getBundleLabel();

    $entity_class = $definition->getClass();
    $is_fieldable = is_subclass_of($entity_class, '\Drupal\Core\Entity\FieldableEntityInterface');

    return [
      'id' => $id,
      'label' => $label ? (string) $label : $id,
      'label_singular' => $label_singular ? (string) $label_singular : '',
      'label_plural' => $label_plural ? (string) $label_plural : '',
      'group' => $definition->getGroup() ?? 'content',
      'group_label' => $group_label ? (string) $group_label : '',
      'fieldable' => $is_fieldable,
      'bundle_entity_type' => $definition->getBundleEntityType(),
      'bundle_label' => $bundle_label ? (string) $bundle_label : NULL,
      'base_table' => $definition->getBaseTable(),
      'data_table' => $definition->getDataTable(),
      'revision_table' => $definition->getRevisionTable(),
      'translatable' => $definition->isTranslatable(),
      'has_bundles' => $definition->getBundleEntityType() !== NULL,
      'class' => $definition->getClass(),
      'provider' => $definition->getProvider(),
    ];
  }

}
