<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Enum;

/**
 * Hardware acceleration types for FFmpeg encoding/decoding.
 *
 * Single source of truth for hardware type identification and
 * encoder mapping. Replaces HardwareDetectorInterface::HW_*
 * and HwAccelOptions::TYPE_* constants.
 */
enum HardwareType: string {

  case Nvenc = 'nvenc';
  case Vaapi = 'vaapi';
  case Qsv = 'qsv';
  case VideoToolbox = 'videotoolbox';
  case Amf = 'amf';
  case Vulkan = 'vulkan';
  case OpenHarmony = 'openharmony';
  case Cuda = 'cuda';
  case None = 'none';

  /**
   * Gets the hardware-accelerated encoder name for a given codec.
   *
   * Centralizes the encoder mapping that was previously duplicated across
   * HwAccelOptions::getHwEncoder() and VideoConverter::selectEncoder().
   *
   * @param string $codec
   *   Base codec name (h264, hevc, av1, vp9).
   *
   * @return string|null
   *   Hardware encoder name (e.g., 'h264_nvenc') or NULL if unsupported.
   */
  #[\NoDiscard]
  public function encoderForCodec(string $codec): ?string {
    $map = match ($this) {
      self::Nvenc, self::Cuda => [
        'h264' => 'h264_nvenc',
        'hevc' => 'hevc_nvenc',
        'av1' => 'av1_nvenc',
      ],
      self::Vaapi => [
        'h264' => 'h264_vaapi',
        'hevc' => 'hevc_vaapi',
        'av1' => 'av1_vaapi',
        'vp9' => 'vp9_vaapi',
      ],
      self::Qsv => [
        'h264' => 'h264_qsv',
        'hevc' => 'hevc_qsv',
        'av1' => 'av1_qsv',
        'vp9' => 'vp9_qsv',
      ],
      self::VideoToolbox => [
        'h264' => 'h264_videotoolbox',
        'hevc' => 'hevc_videotoolbox',
      ],
      self::Amf => [
        'h264' => 'h264_amf',
        'hevc' => 'hevc_amf',
        'av1' => 'av1_amf',
      ],
      self::Vulkan => [
        'h264' => 'h264_vulkan',
        'hevc' => 'hevc_vulkan',
        'av1' => 'av1_vulkan',
      ],
      self::OpenHarmony => [
        'h264' => 'h264_mediacodec_ndk',
        'hevc' => 'hevc_mediacodec_ndk',
      ],
      self::None => [],
    };

    return $map[$codec] ?? NULL;
  }

  /**
   * Gets the software fallback encoder for a codec.
   *
   * @param string $codec
   *   Base codec name (h264, hevc, av1, vp9).
   *
   * @return string|null
   *   Software encoder name or NULL if unsupported.
   */
  #[\NoDiscard]
  public static function softwareEncoder(string $codec): ?string {
    return match ($codec) {
      'h264' => 'libx264',
      'hevc' => 'libx265',
      'av1' => 'libsvtav1',
      'vp9' => 'libvpx-vp9',
      default => NULL,
    };
  }

  /**
   * Detects hardware type from an encoder name suffix.
   *
   * Replaces VideoConverter::extractHwTypeFromEncoder().
   *
   * @param string $encoder
   *   Encoder name (e.g., 'h264_nvenc', 'hevc_vaapi').
   *
   * @return self|null
   *   Hardware type or NULL if encoder is software-only.
   */
  #[\NoDiscard]
  public static function fromEncoderSuffix(string $encoder): ?self {
    $suffixes = [
      '_nvenc' => self::Nvenc,
      '_vaapi' => self::Vaapi,
      '_qsv' => self::Qsv,
      '_videotoolbox' => self::VideoToolbox,
      '_amf' => self::Amf,
      '_vulkan' => self::Vulkan,
      '_mediacodec_ndk' => self::OpenHarmony,
    ];

    foreach ($suffixes as $suffix => $type) {
      if (str_ends_with($encoder, $suffix)) {
        return $type;
      }
    }

    return NULL;
  }

  /**
   * Returns all types that represent real hardware (excludes None).
   *
   * @return array<int, self>
   *   Hardware types.
   */
  #[\NoDiscard]
  public static function hardwareTypes(): array {
    return array_filter(
      self::cases(),
      static fn(self $type): bool => $type !== self::None,
    );
  }

  /**
   * Returns hardware types in preferred selection order.
   *
   * Used by auto-detection to pick the best available hardware.
   *
   * @return array<int, self>
   *   Hardware types ordered by preference (best first).
   */
  #[\NoDiscard]
  public static function preferenceOrder(): array {
    return [
      self::Nvenc,
      self::Vaapi,
      self::Qsv,
      self::VideoToolbox,
      self::Amf,
      self::Vulkan,
    ];
  }

  /**
   * Gets the hardware-specific FFmpeg scale filter name.
   *
   * Used by adaptive streaming to replace CPU-only `scale` filters with
   * hardware-accelerated variants when frames are in GPU memory.
   *
   * @return string|null
   *   Scale filter name (e.g., 'scale_cuda') or NULL for software scale.
   */
  #[\NoDiscard]
  public function scaleFilterName(): ?string {
    return match ($this) {
      self::Nvenc, self::Cuda => 'scale_cuda',
      self::Vaapi => 'scale_vaapi',
      self::Qsv => 'scale_qsv',
      self::Vulkan => 'scale_vulkan',
      self::VideoToolbox, self::Amf, self::OpenHarmony, self::None => NULL,
    };
  }

  /**
   * Checks if this hardware type supports native multipass encoding.
   *
   * NVENC/CUDA support `-multipass fullres` as a single-invocation
   * alternative to traditional two-pass encoding with passlog files.
   *
   * @return bool
   *   TRUE if native multipass is supported.
   */
  #[\NoDiscard]
  public function supportsNativeMultipass(): bool {
    return match ($this) {
      self::Nvenc, self::Cuda => TRUE,
      default => FALSE,
    };
  }

  /**
   * Checks if this type represents real hardware acceleration.
   *
   * @return bool
   *   TRUE if hardware-accelerated, FALSE for None.
   */
  #[\NoDiscard]
  public function isHardware(): bool {
    return $this !== self::None;
  }

}
