<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media_image\Plugin\FFmpegProcessor;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\FileSystemInterface;
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 generating segment thumbnails at fixed intervals.
 *
 * Generates multiple thumbnail images throughout a video at configurable
 * intervals. Useful for video scrubbing previews and timeline thumbnails.
 */
#[FFmpegProcessor(
  id: 'segment_thumbnail',
  label: new TranslatableMarkup('Segment Thumbnails'),
  description: new TranslatableMarkup('Generates multiple thumbnails at fixed intervals throughout the video.'),
  outputType: 'image',
  outputExtension: 'jpg',
  requiredFilters: ['fps', 'scale'],
  weight: 5,
)]
final class SegmentThumbnailProcessor extends FFmpegProcessorBase {

  /**
   * Output filename pattern for segment thumbnails.
   */
  private const string OUTPUT_PATTERN = '_thumb_%04d.jpg';

  /**
   * Constructs a SegmentThumbnailProcessor object.
   *
   * @param array<string, mixed> $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 service.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   File system service.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    #[Autowire(service: FFmpegExecutorInterface::class)]
    FFmpegExecutorInterface $ffmpegExecutor,
    protected readonly ConfigFactoryInterface $configFactory,
    protected readonly FileSystemInterface $fileSystem,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $ffmpegExecutor);
  }

  /**
   * {@inheritdoc}
   *
   * @param string $inputPath
   *   Input file path.
   * @param string $outputPath
   *   Output file path or pattern.
   * @param array<string, mixed> $options
   *   Processing options.
   */
  #[\Override]
  #[\NoDiscard]
  public function process(string $inputPath, string $outputPath, array $options = []): bool {
    $options = $this->mergeOptions($options);

    $interval = $options['interval'] ?? 10;
    $width = $options['width'] ?? 320;
    $height = $options['height'] ?? -1;
    $quality = $options['quality'] ?? 3;
    $maxThumbnails = $options['max_thumbnails'] ?? 100;

    // Ensure output directory exists.
    $outputDir = dirname($outputPath);
    if (!$this->fileSystem->prepareDirectory($outputDir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
      return FALSE;
    }

    $command = $this->buildSegmentThumbnailCommand(
      $inputPath,
      $outputPath,
      $interval,
      $width,
      $height,
      $quality,
      $maxThumbnails,
    );

    return $this->ffmpegExecutor->execute($command);
  }

  /**
   * {@inheritdoc}
   *
   * @return array<string, int>
   *   Default option values.
   */
  #[\Override]
  #[\NoDiscard]
  public function getDefaultOptions(): array {
    $imageConfig = $this->configFactory->get('ffmpeg_media_image.settings');
    $coreConfig = $this->configFactory->get('ffmpeg_media.settings');

    return [
      'interval' => $imageConfig->get('segment_thumbnail_interval') ?? $coreConfig->get('segment_thumbnail_interval') ?? 10,
      'width' => $imageConfig->get('segment_thumbnail_width') ?? $coreConfig->get('segment_thumbnail_width') ?? 320,
      'height' => -1,
      'quality' => $imageConfig->get('segment_thumbnail_quality') ?? $coreConfig->get('segment_thumbnail_quality') ?? 3,
      'max_thumbnails' => $imageConfig->get('segment_thumbnail_max') ?? $coreConfig->get('segment_thumbnail_max') ?? 100,
    ];
  }

  /**
   * {@inheritdoc}
   *
   * @param string $inputPath
   *   Input file path.
   * @param array<string, mixed> $options
   *   Processing options.
   */
  #[\Override]
  #[\NoDiscard]
  public function generateOutputPath(string $inputPath, array $options = []): string {
    $baseName = pathinfo($inputPath, PATHINFO_FILENAME) ?: 'video';
    $directory = pathinfo($inputPath, PATHINFO_DIRNAME) ?: '';

    // Create subdirectory for segment thumbnails.
    $thumbDir = $directory . DIRECTORY_SEPARATOR . $baseName . '_thumbnails';

    return $thumbDir . DIRECTORY_SEPARATOR . $baseName . self::OUTPUT_PATTERN;
  }

  /**
   * Gets the list of generated thumbnail paths.
   *
   * Call this after process() to get actual generated file paths.
   *
   * @param string $outputPath
   *   The output path pattern used in process().
   *
   * @return array<string>
   *   Array of generated thumbnail file paths.
   */
  #[\NoDiscard]
  public function getGeneratedFiles(string $outputPath): array {
    $directory = dirname($outputPath);
    $pattern = basename($outputPath);
    // Convert FFmpeg pattern to glob pattern.
    $globPattern = preg_replace('/%\d*d/', '*', $pattern) ?? '*';

    $files = glob($directory . DIRECTORY_SEPARATOR . $globPattern);

    return is_array($files) ? array_filter($files, is_file(...)) : [];
  }

  /**
   * Builds the FFmpeg command array for segment thumbnail generation.
   *
   * @param string $input
   *   Input file path.
   * @param string $output
   *   Output file path pattern.
   * @param int $interval
   *   Interval between thumbnails in seconds.
   * @param int $width
   *   Thumbnail width.
   * @param int $height
   *   Thumbnail height (-1 for auto).
   * @param int $quality
   *   JPEG quality (2-31, lower is better).
   * @param int $maxThumbnails
   *   Maximum number of thumbnails to generate.
   *
   * @return array<int, string>
   *   Complete FFmpeg command as array of arguments.
   */
  protected function buildSegmentThumbnailCommand(
    string $input,
    string $output,
    int $interval,
    int $width,
    int $height,
    int $quality,
    int $maxThumbnails,
  ): array {
    $ffmpegPath = $this->ffmpegExecutor->getFfmpegPath();

    // Calculate fps value: 1 frame every N seconds.
    $fps = 1 / max(1, $interval);
    $fpsFormatted = number_format($fps, 6, '.', '');

    // Build filter chain.
    $scaleFilter = "scale={$width}:{$height}:force_original_aspect_ratio=decrease";
    $filterChain = "fps={$fpsFormatted},{$scaleFilter}";

    // Build command array.
    $command = [
      $ffmpegPath,
      '-y',
      '-i', $input,
      '-vf', $filterChain,
    ];

    // Add frame limit if max_thumbnails is set.
    if ($maxThumbnails > 0) {
      $command[] = '-vframes';
      $command[] = (string) $maxThumbnails;
    }

    $command[] = '-q:v';
    $command[] = (string) $quality;
    $command[] = $output;

    return $command;
  }

}
