<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Plugin\FFmpegCodec;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ffmpeg_media\Attribute\FFmpegCodec;
use Drupal\ffmpeg_media\ValueObject\ConversionOptions;

/**
 * VVC/H.266 codec plugin using libvvenc.
 *
 * VVC (Versatile Video Coding), also known as H.266, is the successor to
 * HEVC/H.265 and offers approximately 50% better compression efficiency.
 *
 * Important browser support notes (as of January 2026):
 * - Safari: Supported (macOS 14+, iOS 17+)
 * - Chrome: Not supported
 * - Firefox: Not supported
 * - Edge: Not supported
 *
 * VVC is recommended only for specific use cases where Safari-only playback
 * is acceptable or for archival purposes. For general web delivery, consider
 * AV1 or VP9 instead.
 *
 * Unlike other codecs, VVC uses QP (Quantization Parameter) instead of CRF.
 * QP range: 0-63 (lower = better quality, larger file).
 *
 * @see https://www.itu.int/rec/T-REC-H.266
 */
#[FFmpegCodec(
  id: 'vvc',
  label: new TranslatableMarkup('VVC/H.266 (Experimental)'),
  description: new TranslatableMarkup('Next-generation codec with ~50%% better compression than HEVC. Limited browser support (Safari only as of Jan 2026).'),
  encoder: 'libvvenc',
  supportedContainers: ['mp4', 'mkv'],
  hardwareAccelerated: FALSE,
  twoPassSupported: TRUE,
  weight: 30,
)]
final class VVCCodec extends FFmpegCodecBase {

  /**
   * Default QP value for balanced quality/size.
   */
  private const int DEFAULT_QP = 32;

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function buildArguments(ConversionOptions $options): array {
    // Convert numeric preset (0-9) to VVC preset name.
    $presetName = $this->getPresetName($options->preset);

    // VVC uses QP instead of CRF - we map the CRF value to QP.
    // Both use 0-63 range, so we can use the CRF value directly as QP.
    $qp = $this->clampQp($options->crf);

    $args = [
      '-c:v', 'libvvenc',
      '-qp', (string) $qp,
      '-preset', $presetName,
    ];

    // Add common video arguments.
    $args = array_merge($args, $this->buildCommonArguments($options));

    // Audio arguments.
    $args = array_merge($args, $this->buildAudioArguments($options));

    // Add extra arguments.
    foreach ($options->extraArgs as $arg) {
      $args[] = $arg;
    }

    return $args;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getDefaultOptions(): ConversionOptions {
    return new ConversionOptions(
      crf: self::DEFAULT_QP,
      preset: 4,
      threads: 0,
      audioBitrate: '128k',
      audioCodec: 'libopus',
      pixelFormat: 'yuv420p',
    );
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getHighQualityOptions(): ConversionOptions {
    return new ConversionOptions(
      crf: 24,
      preset: 2,
      threads: 0,
      audioBitrate: '192k',
      audioCodec: 'libopus',
      pixelFormat: 'yuv420p10le',
      twoPass: TRUE,
    );
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getFastEncodingOptions(): ConversionOptions {
    return new ConversionOptions(
      crf: 38,
      preset: 8,
      threads: 0,
      audioBitrate: '128k',
      audioCodec: 'libopus',
      pixelFormat: 'yuv420p',
    );
  }

  /**
   * Converts numeric preset to VVC preset name.
   *
   * @param int $preset
   *   Numeric preset (0-9).
   *
   * @return string
   *   VVC preset name.
   */
  protected function getPresetName(int $preset): string {
    // Map 0-9 numeric preset to VVC presets.
    // 0-1 = slower, 2-3 = slow, 4-5 = medium, 6-7 = fast, 8-9 = faster.
    return match (TRUE) {
      $preset <= 1 => 'slower',
      $preset <= 3 => 'slow',
      $preset <= 5 => 'medium',
      $preset <= 7 => 'fast',
      default => 'faster',
    };
  }

  /**
   * Clamps QP value to valid range.
   *
   * @param int $qp
   *   Input QP/CRF value.
   *
   * @return int
   *   Clamped QP value (0-63).
   */
  protected function clampQp(int $qp): int {
    return max(0, min(63, $qp));
  }

  /**
   * Gets available VVC presets.
   *
   * @return array<string, string>
   *   Array of preset ID => label.
   */
  #[\NoDiscard]
  public static function getAvailablePresets(): array {
    return [
      'faster' => 'Faster (lowest quality)',
      'fast' => 'Fast',
      'medium' => 'Medium (balanced)',
      'slow' => 'Slow',
      'slower' => 'Slower (highest quality)',
    ];
  }

  /**
   * Gets recommended QP values for different use cases.
   *
   * @return array<string, array{qp: int, description: string}>
   *   QP recommendations.
   */
  #[\NoDiscard]
  public static function getQpRecommendations(): array {
    return [
      'archival' => [
        'qp' => 20,
        'description' => 'Archival quality - visually lossless',
      ],
      'high' => [
        'qp' => 26,
        'description' => 'High quality - minimal artifacts',
      ],
      'balanced' => [
        'qp' => 32,
        'description' => 'Balanced - good quality/size ratio',
      ],
      'low' => [
        'qp' => 38,
        'description' => 'Lower quality - smaller file size',
      ],
      'minimum' => [
        'qp' => 45,
        'description' => 'Minimum acceptable quality',
      ],
    ];
  }

}
