<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\ffmpeg_media\ValueObject\ConversionContext;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;

/**
 * Resolves conversion settings from EntityViewDisplay formatter configuration.
 *
 * Scans the 'default' view mode for ffmpeg formatters and extracts
 * auto_convert and conversion_profile settings.
 *
 * Results are cached in memory for the duration of the current request.
 * In long-running processes (queue workers, Drush commands), call
 * resetCache() between iterations if display configuration may have changed.
 *
 * @internal
 */
final class FormatterSettingsResolver implements FormatterSettingsResolverInterface {

  /**
   * Formatter IDs that trigger conversion.
   *
   * @var array<int, string>
   */
  private const array FORMATTER_IDS = [
    'ffmpeg_media_converted_video',
    'ffmpeg_media_streaming_video',
  ];

  /**
   * Cached result of getAllContexts().
   *
   * @var array<\Drupal\ffmpeg_media\ValueObject\ConversionContext>|null
   */
  private ?array $cachedAllContexts = NULL;

  /**
   * Cached results of getContextsForBundle(), keyed by "entityType.bundle".
   *
   * @var array<string, array<\Drupal\ffmpeg_media\ValueObject\ConversionContext>>
   */
  private array $cachedBundleContexts = [];

  /**
   * Constructs a FormatterSettingsResolver object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager.
   */
  public function __construct(
    protected readonly EntityTypeManagerInterface $entityTypeManager,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getContextsForEntity(MediaInterface $entity): array {
    $entityType = $entity->getEntityTypeId();
    $bundle = $entity->bundle();

    return $this->getContextsForBundle($entityType, $bundle);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getContextForField(MediaInterface $entity, string $fieldName): ?ConversionContext {
    $contexts = $this->getContextsForEntity($entity);

    foreach ($contexts as $context) {
      if ($context->fieldName === $fieldName) {
        return $context;
      }
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getPreferredContextForEntity(MediaInterface $entity): ?ConversionContext {
    $contexts = $this->getContextsForEntity($entity);
    if ($contexts === []) {
      return NULL;
    }

    if (count($contexts) === 1) {
      return $contexts[0];
    }

    $sourceFieldName = $this->getSourceFieldName($entity);
    if ($sourceFieldName !== NULL) {
      foreach ($contexts as $context) {
        if ($context->fieldName === $sourceFieldName) {
          return $context;
        }
      }
    }

    return $contexts[0];
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getAllContexts(): array {
    if ($this->cachedAllContexts !== NULL) {
      return $this->cachedAllContexts;
    }

    $contexts = [];

    /** @var \Drupal\media\MediaTypeInterface[] $mediaTypes */
    $mediaTypes = $this->entityTypeManager->getStorage('media_type')->loadMultiple();

    if ($mediaTypes === []) {
      $this->cachedAllContexts = [];
      return [];
    }

    // Batch-load all default displays in a single query.
    $displayIds = [];
    foreach ($mediaTypes as $mediaType) {
      $displayIds[] = 'media.' . $mediaType->id() . '.default';
    }

    /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays */
    $displays = $this->entityTypeManager
      ->getStorage('entity_view_display')
      ->loadMultiple($displayIds);

    foreach ($mediaTypes as $mediaType) {
      $bundle = (string) $mediaType->id();
      $displayId = "media.{$bundle}.default";
      $display = $displays[$displayId] ?? NULL;

      if ($display === NULL) {
        $this->cachedBundleContexts["media.{$bundle}"] = [];
        continue;
      }

      $bundleContexts = $this->extractContextsFromDisplay($display, 'media', $bundle);
      $this->cachedBundleContexts["media.{$bundle}"] = $bundleContexts;
      $contexts = array_merge($contexts, $bundleContexts);
    }

    $this->cachedAllContexts = $contexts;

    return $contexts;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function hasAnyConfiguration(): bool {
    return $this->getAllContexts() !== [];
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function resetCache(): void {
    $this->cachedAllContexts = NULL;
    $this->cachedBundleContexts = [];
  }

  /**
   * Gets conversion contexts for a specific entity type and bundle.
   *
   * @param string $entityType
   *   The entity type ID.
   * @param string $bundle
   *   The bundle name.
   *
   * @return array<\Drupal\ffmpeg_media\ValueObject\ConversionContext>
   *   Array of conversion contexts.
   */
  protected function getContextsForBundle(string $entityType, string $bundle): array {
    $key = "{$entityType}.{$bundle}";
    if (isset($this->cachedBundleContexts[$key])) {
      return $this->cachedBundleContexts[$key];
    }

    $displayId = "{$entityType}.{$bundle}.default";

    /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface|null $display */
    $display = $this->entityTypeManager
      ->getStorage('entity_view_display')
      ->load($displayId);

    if ($display === NULL) {
      $this->cachedBundleContexts[$key] = [];
      return [];
    }

    $contexts = $this->extractContextsFromDisplay($display, $entityType, $bundle);
    $this->cachedBundleContexts[$key] = $contexts;

    return $contexts;
  }

  /**
   * Extracts conversion contexts from a loaded EntityViewDisplay.
   *
   * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
   *   The view display.
   * @param string $entityType
   *   The entity type ID.
   * @param string $bundle
   *   The bundle name.
   *
   * @return array<\Drupal\ffmpeg_media\ValueObject\ConversionContext>
   *   Array of conversion contexts.
   */
  private function extractContextsFromDisplay(
    EntityViewDisplayInterface $display,
    string $entityType,
    string $bundle,
  ): array {
    $contexts = [];

    foreach ($display->getComponents() as $fieldName => $component) {
      $formatterType = $component['type'] ?? NULL;

      if ($formatterType === NULL) {
        continue;
      }

      // Native ffmpeg_media formatters: read from formatter settings.
      if (in_array($formatterType, self::FORMATTER_IDS, TRUE)) {
        $settings = $component['settings'] ?? [];
        $autoConvert = (bool) ($settings['auto_convert'] ?? TRUE);
        $conversionProfile = $settings['conversion_profile'] ?? NULL;
        if ($conversionProfile === '') {
          $conversionProfile = NULL;
        }

        $contexts[] = new ConversionContext(
          entityType: $entityType,
          bundle: $bundle,
          fieldName: $fieldName,
          autoConvert: $autoConvert,
          conversionProfile: $conversionProfile,
        );
        continue;
      }

      // Third-party formatters: read from third_party_settings.ffmpeg_media.
      $thirdParty = $component['third_party_settings']['ffmpeg_media'] ?? NULL;
      if ($thirdParty === NULL) {
        continue;
      }

      $autoConvert = (bool) ($thirdParty['auto_convert'] ?? FALSE);

      $conversionProfile = $thirdParty['conversion_profile'] ?? NULL;
      if ($conversionProfile === '') {
        $conversionProfile = NULL;
      }

      $contexts[] = new ConversionContext(
        entityType: $entityType,
        bundle: $bundle,
        fieldName: $fieldName,
        autoConvert: $autoConvert,
        conversionProfile: $conversionProfile,
      );
    }

    return $contexts;
  }

  /**
   * Resolves the media source field name when available.
   */
  #[\NoDiscard]
  private function getSourceFieldName(MediaInterface $entity): ?string {
    $mediaType = $entity->get('bundle')->entity ?? NULL;
    if (!$mediaType instanceof MediaTypeInterface) {
      return NULL;
    }

    return $entity
      ->getSource()
      ->getSourceFieldDefinition($mediaType)
      ?->getName();
  }

}
