<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media_advanced\Plugin\FFmpegProcessor;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ffmpeg_media\Attribute\FFmpegProcessor;
use Drupal\ffmpeg_media\Plugin\FFmpegProcessor\FFmpegProcessorBase;
use Drupal\ffmpeg_media\Service\FFmpegExecutorInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Processor plugin for HDR video handling.
 *
 * Supports:
 * - HDR10 passthrough (preserve HDR metadata)
 * - HDR to SDR tone mapping using zscale filter
 * - Color space handling (bt2020, smpte2084, bt709)
 *
 * Requires FFmpeg compiled with zscale filter support.
 */
#[FFmpegProcessor(
  id: 'hdr',
  label: new TranslatableMarkup('HDR Processor'),
  description: new TranslatableMarkup('Process HDR video content with passthrough or SDR tone mapping.'),
  outputType: 'video',
  outputExtension: 'mp4',
  requiredFilters: ['zscale'],
  weight: 15,
)]
final class HdrProcessor extends FFmpegProcessorBase implements ContainerFactoryPluginInterface {

  /**
   * Available tone mapping algorithms.
   */
  public const array TONE_MAP_ALGORITHMS = [
    'hable' => 'Hable (filmic, good for most content)',
    'reinhard' => 'Reinhard (simple, preserves colors)',
    'mobius' => 'Mobius (smooth transition)',
    'linear' => 'Linear (simple clipping)',
    'clip' => 'Clip (hard clipping)',
    'gamma' => 'Gamma (power function)',
  ];

  /**
   * Available color spaces.
   */
  public const array COLOR_SPACES = [
    'bt2020' => 'BT.2020 (HDR wide gamut)',
    'bt709' => 'BT.709 (SDR standard)',
    'smpte2084' => 'SMPTE ST 2084 (PQ/HDR10)',
    'arib-std-b67' => 'ARIB STD-B67 (HLG)',
  ];

  /**
   * Constructs an HdrProcessor object.
   *
   * @param array $configuration
   *   Plugin configuration.
   * @param string $plugin_id
   *   Plugin ID.
   * @param mixed $plugin_definition
   *   Plugin definition.
   * @param \Drupal\ffmpeg_media\Service\FFmpegExecutorInterface $ffmpegExecutor
   *   FFmpeg executor service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config factory.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    #[Autowire(service: FFmpegExecutorInterface::class)]
    FFmpegExecutorInterface $ffmpegExecutor,
    protected readonly ConfigFactoryInterface $configFactory,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $ffmpegExecutor);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function process(string $inputPath, string $outputPath, array $options = []): bool {
    $options = $this->mergeOptions($options);

    $command = $options['passthrough']
      ? $this->buildHdrPassthroughCommand($inputPath, $outputPath, $options)
      : $this->buildHdrToSdrCommand($inputPath, $outputPath, $options);

    return $this->ffmpegExecutor->execute($command, [
      'timeout' => $options['timeout'] ?? 3600,
    ]);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getDefaultOptions(): array {
    $config = $this->configFactory->get('ffmpeg_media_advanced.settings');

    return [
      'passthrough' => $config->get('hdr_passthrough_enabled') ?? FALSE,
      'algorithm' => $config->get('hdr_tone_mapping_algorithm') ?? 'hable',
      'peak_luminance' => $config->get('hdr_tone_mapping_peak') ?? 100.0,
      'desaturation' => $config->get('hdr_desaturation') ?? 0.0,
      'source_colorspace' => 'bt2020',
      'source_transfer' => 'smpte2084',
      'target_colorspace' => 'bt709',
      'target_transfer' => 'bt709',
      'pixel_format' => 'yuv420p',
      'pixel_format_hdr' => 'yuv420p10le',
      'video_codec' => 'libsvtav1',
      'crf' => 23,
      'audio_codec' => 'libopus',
      'audio_bitrate' => '128k',
      'timeout' => 3600,
    ];
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function generateOutputPath(string $inputPath, array $options = []): string {
    $pathInfo = pathinfo($inputPath);
    $baseName = $pathInfo['filename'];
    $directory = $pathInfo['dirname'] ?? dirname($inputPath);
    $suffix = ($options['passthrough'] ?? FALSE) ? '_hdr' : '_sdr';

    return $directory . DIRECTORY_SEPARATOR . $baseName . $suffix . '.mp4';
  }

  /**
   * Builds FFmpeg command array for HDR passthrough.
   *
   * Uses array-based command building for improved security -
   * avoids shell escaping issues by passing arguments directly to Process.
   *
   * Preserves HDR metadata for compatible displays.
   *
   * @param string $input
   *   Input file path.
   * @param string $output
   *   Output file path.
   * @param array<string, mixed> $options
   *   Processing options.
   *
   * @return array<int, string>
   *   Complete FFmpeg command as array of arguments.
   */
  protected function buildHdrPassthroughCommand(string $input, string $output, array $options): array {
    $ffmpegPath = $this->ffmpegExecutor->getFfmpegPath();

    $pixelFormat = $options['pixel_format_hdr'] ?? 'yuv420p10le';
    $videoCodec = $options['video_codec'] ?? 'libsvtav1';
    $crf = (string) (int) ($options['crf'] ?? 25);
    $audioCodec = $options['audio_codec'] ?? 'libopus';
    $audioBitrate = $options['audio_bitrate'] ?? '128k';

    // Build command array with HDR metadata preservation.
    return [
      $ffmpegPath,
      '-y',
      '-i', $input,
      '-c:v', $videoCodec,
      '-crf', $crf,
      '-pix_fmt', $pixelFormat,
      '-color_primaries', 'bt2020',
      '-color_trc', 'smpte2084',
      '-colorspace', 'bt2020nc',
      '-c:a', $audioCodec,
      '-b:a', $audioBitrate,
      '-movflags', '+faststart',
      $output,
    ];
  }

  /**
   * Builds FFmpeg command array for HDR to SDR tone mapping.
   *
   * Uses array-based command building for improved security -
   * avoids shell escaping issues by passing arguments directly to Process.
   *
   * Uses zscale filter for high-quality tone mapping.
   *
   * @param string $input
   *   Input file path.
   * @param string $output
   *   Output file path.
   * @param array<string, mixed> $options
   *   Processing options.
   *
   * @return array<int, string>
   *   Complete FFmpeg command as array of arguments.
   */
  protected function buildHdrToSdrCommand(string $input, string $output, array $options): array {
    $ffmpegPath = $this->ffmpegExecutor->getFfmpegPath();

    $algorithm = $options['algorithm'] ?? 'hable';
    $peakLuminance = (float) ($options['peak_luminance'] ?? 100.0);
    $desaturation = (float) ($options['desaturation'] ?? 0.0);
    $pixelFormat = $options['pixel_format'] ?? 'yuv420p';
    $videoCodec = $options['video_codec'] ?? 'libsvtav1';
    $crf = (string) (int) ($options['crf'] ?? 23);
    $audioCodec = $options['audio_codec'] ?? 'libopus';
    $audioBitrate = $options['audio_bitrate'] ?? '128k';

    // Build the tone mapping filter chain.
    $filterChain = $this->buildToneMappingFilter($algorithm, $peakLuminance, $desaturation, $pixelFormat);

    // Build command array.
    return [
      $ffmpegPath,
      '-y',
      '-i', $input,
      '-vf', $filterChain,
      '-c:v', $videoCodec,
      '-crf', $crf,
      '-c:a', $audioCodec,
      '-b:a', $audioBitrate,
      '-movflags', '+faststart',
      $output,
    ];
  }

  /**
   * Builds the zscale tone mapping filter chain.
   *
   * @param string $algorithm
   *   Tone mapping algorithm.
   * @param float $peakLuminance
   *   Peak luminance in nits.
   * @param float $desaturation
   *   Desaturation level.
   * @param string $pixelFormat
   *   Output pixel format.
   *
   * @return string
   *   Complete filter chain string.
   */
  protected function buildToneMappingFilter(
    string $algorithm,
    float $peakLuminance,
    float $desaturation,
    string $pixelFormat,
  ): string {
    // The filter chain:
    // 1. Convert to linear light (t=linear, npl=nominal peak luminance)
    // 2. Convert to float format for processing
    // 3. Convert color primaries to BT.709
    // 4. Apply tone mapping algorithm
    // 5. Convert transfer characteristics and matrix, then to target format.
    $filters = [
      sprintf('zscale=t=linear:npl=%d', (int) $peakLuminance),
      'format=gbrpf32le',
      'zscale=p=bt709',
      sprintf('tonemap=%s:desat=%s', $algorithm, number_format($desaturation, 2)),
      'zscale=t=bt709:m=bt709:r=tv',
      sprintf('format=%s', $pixelFormat),
    ];

    return implode(',', $filters);
  }

  /**
   * Gets available tone mapping algorithms.
   *
   * @return array<string, string>
   *   Algorithm ID => description.
   */
  #[\NoDiscard]
  public static function getAvailableAlgorithms(): array {
    return self::TONE_MAP_ALGORITHMS;
  }

  /**
   * Gets available color spaces.
   *
   * @return array<string, string>
   *   Color space ID => description.
   */
  #[\NoDiscard]
  public static function getAvailableColorSpaces(): array {
    return self::COLOR_SPACES;
  }

  /**
   * Creates options for HDR passthrough mode.
   *
   * @param string $videoCodec
   *   Video codec to use.
   * @param int $crf
   *   Quality parameter.
   *
   * @return array<string, bool|int|string>
   *   Options array for passthrough mode.
   */
  #[\NoDiscard]
  public static function passthroughOptions(string $videoCodec = 'libsvtav1', int $crf = 25): array {
    return [
      'passthrough' => TRUE,
      'video_codec' => $videoCodec,
      'crf' => $crf,
      'pixel_format_hdr' => 'yuv420p10le',
    ];
  }

  /**
   * Creates options for HDR to SDR conversion.
   *
   * @param string $algorithm
   *   Tone mapping algorithm.
   * @param string $videoCodec
   *   Video codec to use.
   * @param int $crf
   *   Quality parameter.
   *
   * @return array<string, bool|int|string>
   *   Options array for tone mapping mode.
   */
  #[\NoDiscard]
  public static function toneMappingOptions(
    string $algorithm = 'hable',
    string $videoCodec = 'libsvtav1',
    int $crf = 23,
  ): array {
    return [
      'passthrough' => FALSE,
      'algorithm' => $algorithm,
      'video_codec' => $videoCodec,
      'crf' => $crf,
      'pixel_format' => 'yuv420p',
    ];
  }

}
