<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Symfony\Component\Process\Process;

/**
 * Inspects FFmpeg version and available encoders and filters.
 *
 * @internal
 */
final class FFmpegCapabilityInspector implements FFmpegCapabilityInspectorInterface {

  /**
   * Cached list of available encoders.
   *
   * @var array<string, bool>|null
   */
  private ?array $encoders = NULL;

  /**
   * Cached list of available filters.
   *
   * @var array<string, bool>|null
   */
  private ?array $filters = NULL;

  /**
   * Cached FFmpeg version.
   *
   * @var string|null
   */
  private ?string $version = NULL;

  /**
   * Constructs an FFmpegCapabilityInspector object.
   *
   * @param \Drupal\ffmpeg_media\Service\FFmpegBinaryPathResolverInterface $binaryPathResolver
   *   Binary path resolver for configured or auto-detected executables.
   */
  public function __construct(
    private readonly FFmpegBinaryPathResolverInterface $binaryPathResolver,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getVersion(): ?string {
    if ($this->version !== NULL) {
      return $this->version;
    }

    $output = $this->executeQuery(['-version']);
    if ($output !== NULL && preg_match('/ffmpeg version (\S+)/', $output, $matches)) {
      $this->version = $matches[1];
      return $this->version;
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function hasEncoder(string $encoder): bool {
    $this->loadEncoders();
    return $this->encoders[$encoder] ?? FALSE;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function hasFilter(string $filter): bool {
    $this->loadFilters();
    return $this->filters[$filter] ?? FALSE;
  }

  /**
   * Loads available encoders.
   */
  private function loadEncoders(): void {
    if ($this->encoders !== NULL) {
      return;
    }

    $this->encoders = [];
    $output = $this->executeQuery(['-encoders', '-hide_banner']);
    if ($output !== NULL && preg_match_all('/^\s*V..... (\S+)/m', $output, $matches)) {
      foreach ($matches[1] as $encoder) {
        $this->encoders[$encoder] = TRUE;
      }
    }
  }

  /**
   * Loads available filters.
   */
  private function loadFilters(): void {
    if ($this->filters !== NULL) {
      return;
    }

    $this->filters = [];
    $output = $this->executeQuery(['-filters', '-hide_banner']);
    if ($output !== NULL && preg_match_all('/^\s*[T.][S.][C.]\s+(\S+)/m', $output, $matches)) {
      foreach ($matches[1] as $filter) {
        $this->filters[$filter] = TRUE;
      }
    }
  }

  /**
   * Executes an FFmpeg metadata query and returns stdout on success.
   *
   * @param array<int, string> $arguments
   *   Command arguments appended to the ffmpeg binary path.
   * @param int $timeout
   *   Command timeout in seconds.
   *
   * @return string|null
   *   Process output or NULL on failure.
   */
  private function executeQuery(array $arguments, int $timeout = 10): ?string {
    $process = new Process([
      $this->binaryPathResolver->getFfmpegPath(),
      ...$arguments,
    ]);
    $process->setTimeout($timeout);
    $process->run();

    return $process->isSuccessful() ? $process->getOutput() : NULL;
  }

}
