<?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;

/**
 * JPEG XL codec plugin for animated and still image encoding.
 *
 * JPEG XL (JXL) is a next-generation image format offering both lossy and
 * lossless compression with excellent quality. FFmpeg 8.0+ with libjxl
 * supports animated JPEG XL output as a modern alternative to GIF/WebP.
 *
 * Quality is controlled by the distance parameter:
 * - 0.0: Mathematically lossless
 * - 1.0: Visually lossless (default)
 * - Higher values: More compression, smaller files
 *
 * Speed is controlled by the effort parameter (1-9):
 * - 1: Fastest encoding
 * - 7: Default balance
 * - 9: Best compression, slowest
 *
 * @see https://jpeg.org/jpegxl/
 */
#[FFmpegCodec(
  id: 'libjxl',
  label: new TranslatableMarkup('JPEG XL'),
  description: new TranslatableMarkup('Next-generation image codec with excellent compression. Supports animated and still images. Requires FFmpeg 8.0+ with libjxl.'),
  encoder: 'libjxl',
  supportedContainers: ['jxl'],
  hardwareAccelerated: FALSE,
  weight: 25,
)]
final class LibjxlCodec extends FFmpegCodecBase {

  /**
   * Default effort balancing speed and compression.
   */
  private const int DEFAULT_EFFORT = 7;

  /**
   * {@inheritdoc}
   *
   * @return array<int, string>
   *   FFmpeg arguments for libjxl encoding.
   */
  #[\Override]
  #[\NoDiscard]
  public function buildArguments(ConversionOptions $options): array {
    $args = ['-c:v', 'libjxl'];

    // Map CRF to JXL distance linearly.
    // CRF 0 → distance 0.0 (lossless), CRF 23 → 1.0 (visually lossless),
    // CRF 51 → ~2.22 (higher compression). Clamped to [0, 15].
    $distance = $options->crf <= 0
      ? 0.0
      : min(15.0, round($options->crf / 23.0, 2));
    $args[] = '-distance';
    $args[] = (string) $distance;

    // Map preset to effort: lower preset = higher quality = higher effort.
    $effort = max(1, min(9, 9 - $options->preset));
    $args[] = '-effort';
    $args[] = (string) $effort;

    if ($distance === 0.0) {
      $args[] = '-lossless';
      $args[] = '1';
    }

    // No audio for image formats.
    $args[] = '-an';

    foreach ($options->extraArgs as $arg) {
      $args[] = $arg;
    }

    return $args;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getDefaultOptions(): ConversionOptions {
    return new ConversionOptions(
      crf: 23,
      preset: 2,
    );
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getHighQualityOptions(): ConversionOptions {
    return new ConversionOptions(
      crf: 0,
      preset: 0,
    );
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getFastEncodingOptions(): ConversionOptions {
    return new ConversionOptions(
      crf: 23,
      preset: 8,
    );
  }

}
