<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Drupal\ffmpeg_media\Exception\InvalidConfigurationException;
use Drupal\ffmpeg_media\ValueObject\ProfileOutput;
use Drupal\media\MediaInterface;

/**
 * Resolves conversion configuration from media entities and options.
 *
 * @internal
 */
final class ConversionConfigResolver implements ConversionConfigResolverInterface {

  /**
   * Constructs a ConversionConfigResolver object.
   *
   * @param \Drupal\ffmpeg_media\Service\FormatterSettingsResolverInterface $formatterSettingsResolver
   *   Formatter settings resolver service.
   * @param \Drupal\ffmpeg_media\Service\SourceFileResolverInterface $sourceFileResolver
   *   Source file resolver service.
   * @param \Drupal\ffmpeg_media\Service\ProfileManagerInterface $profileManager
   *   Profile manager service.
   */
  public function __construct(
    protected readonly FormatterSettingsResolverInterface $formatterSettingsResolver,
    protected readonly SourceFileResolverInterface $sourceFileResolver,
    protected readonly ProfileManagerInterface $profileManager,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function resolve(MediaInterface $media, ?string $profileId, array $options, string $fieldName = ''): array {
    if ($fieldName !== '') {
      $context = $this->formatterSettingsResolver->getContextForField($media, $fieldName);
    }
    else {
      $context = $this->formatterSettingsResolver->getPreferredContextForEntity($media);
    }

    if ($context === NULL) {
      throw InvalidConfigurationException::missingFormatterConfiguration(
        $media->getEntityTypeId(),
        $media->bundle(),
      );
    }

    $sourceFile = $this->sourceFileResolver->getSourceFile($media, $context->fieldName);

    $sourcePath = $this->sourceFileResolver->getSourcePath($media, $context->fieldName);
    if ($sourcePath === NULL) {
      $sourcePath = '';
    }

    $profileId = $profileId ?? $context->conversionProfile ?? $this->profileManager->getDefaultProfileId();
    $selectedOutputs = $options['selected_outputs'] ?? [];

    if (!empty($selectedOutputs)) {
      $profile = $this->profileManager->getProfile($profileId) ?? $this->profileManager->getDefaultProfile();
      if ($profile === NULL) {
        return [
          'context' => $context,
          'sourceFile' => $sourceFile,
          'sourcePath' => $sourcePath,
          'enabledOutputs' => [],
          'profileId' => $profileId,
        ];
      }
      $enabledOutputs = $this->filterSelectedOutputs($profile->getEnabledOutputs(), $selectedOutputs);
    }
    else {
      $enabledOutputs = $this->profileManager->getEnabledOutputs($profileId);
    }

    $streamingOptions = $options['streaming_options'] ?? [];
    if (!empty($streamingOptions)) {
      $enabledOutputs = $this->applyStreamingOverrides($enabledOutputs, $streamingOptions);
    }

    return [
      'context' => $context,
      'sourceFile' => $sourceFile,
      'sourcePath' => $sourcePath,
      'enabledOutputs' => $enabledOutputs,
      'profileId' => $profileId,
    ];
  }

  /**
   * Filters outputs by selected unique output keys.
   *
   * @param array<int, \Drupal\ffmpeg_media\ValueObject\ProfileOutput> $outputs
   *   Enabled outputs from the resolved profile.
   * @param array<int, string> $selectedOutputs
   *   Selected output keys from UI or queue payload.
   *
   * @return array<int, \Drupal\ffmpeg_media\ValueObject\ProfileOutput>
   *   Filtered enabled outputs.
   */
  #[\NoDiscard]
  private function filterSelectedOutputs(array $outputs, array $selectedOutputs): array {
    $selectedLookup = array_fill_keys($selectedOutputs, TRUE);
    $containerCounts = [];

    foreach ($outputs as $output) {
      $containerCounts[$output->container] = ($containerCounts[$output->container] ?? 0) + 1;
    }

    return array_values(array_filter($outputs, static function (ProfileOutput $output) use ($selectedLookup, $containerCounts): bool {
      $qualifiedKey = ($containerCounts[$output->container] ?? 0) > 1
        ? $output->container . ':' . $output->codec
        : $output->container;

      return isset($selectedLookup[$qualifiedKey]) || isset($selectedLookup[$output->container]);
    }));
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function applyStreamingOverrides(array $outputs, array $streamingOptions): array {
    $streamingFormats = ['hls', 'dash'];

    foreach ($outputs as $index => $output) {
      if (!in_array($output->container, $streamingFormats, TRUE)) {
        continue;
      }

      $mergedOptions = $output->options;

      // Apply segment duration override.
      if (isset($streamingOptions['segment_duration'])) {
        $mergedOptions['segment_duration'] = (int) $streamingOptions['segment_duration'];
      }

      // Apply playlist type override.
      if (isset($streamingOptions['playlist_type'])) {
        $mergedOptions['playlist_type'] = $streamingOptions['playlist_type'];
      }

      // Apply representations override (bitrate ladder).
      if (!empty($streamingOptions['representations'])) {
        $mergedOptions['representations'] = $this->buildRepresentationsFromSelection($streamingOptions['representations']);
      }

      $outputs[$index] = new ProfileOutput(
        container: $output->container,
        codec: $output->codec,
        enabled: $output->enabled,
        options: $mergedOptions,
      );
    }

    return $outputs;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function buildRepresentationsFromSelection(array $selectedLevels): array {
    $presets = [
      '360p' => ['width' => 640, 'height' => 360, 'video_bitrate' => 800, 'audio_bitrate' => 96],
      '480p' => ['width' => 854, 'height' => 480, 'video_bitrate' => 1400, 'audio_bitrate' => 128],
      '720p' => ['width' => 1280, 'height' => 720, 'video_bitrate' => 2500, 'audio_bitrate' => 128],
      '1080p' => ['width' => 1920, 'height' => 1080, 'video_bitrate' => 5000, 'audio_bitrate' => 192],
      '1440p' => ['width' => 2560, 'height' => 1440, 'video_bitrate' => 8000, 'audio_bitrate' => 192],
      '2160p' => ['width' => 3840, 'height' => 2160, 'video_bitrate' => 16000, 'audio_bitrate' => 256],
    ];

    $representations = [];
    foreach ($selectedLevels as $level) {
      if (isset($presets[$level])) {
        $representations[] = array_merge($presets[$level], ['label' => $level]);
      }
    }

    return $representations;
  }

}
