<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Plugin\FFmpegProcessor;

use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\ffmpeg_media\Service\FFmpegExecutorInterface;
use Drupal\ffmpeg_media\Trait\PluginDefinitionAccessTrait;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Base class for FFmpeg processor plugins.
 */
abstract class FFmpegProcessorBase extends PluginBase implements FFmpegProcessorInterface, ContainerFactoryPluginInterface {

  use PluginDefinitionAccessTrait;

  /**
   * Constructs an FFmpegProcessorBase 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.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    #[Autowire(service: FFmpegExecutorInterface::class)]
    protected readonly FFmpegExecutorInterface $ffmpegExecutor,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * Gets this plugin's configuration.
   *
   * @return array<string, mixed>
   *   An array of this plugin's configuration.
   */
  #[\NoDiscard]
  public function getConfiguration(): array {
    return $this->configuration;
  }

  /**
   * Sets the configuration for this plugin instance.
   *
   * @param array<string, mixed> $configuration
   *   An associative array containing the plugin's configuration.
   */
  public function setConfiguration(array $configuration): void {
    $this->configuration = $configuration;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getLabel(): string {
    return (string) $this->getDefinitionValue('label', $this->pluginId);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getOutputType(): string {
    return (string) $this->getDefinitionValue('outputType', 'video');
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getOutputExtension(): string {
    return (string) $this->getDefinitionValue('outputExtension', '');
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function isAvailable(): bool {
    $requiredFilters = $this->getDefinitionValue('requiredFilters', []);
    if (!is_array($requiredFilters)) {
      return TRUE;
    }

    foreach ($requiredFilters as $filter) {
      if (!$this->ffmpegExecutor->hasFilter($filter)) {
        return FALSE;
      }
    }

    return TRUE;
  }

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

    $requiredFilters = $this->getDefinitionValue('requiredFilters', []);
    if (!is_array($requiredFilters)) {
      return NULL;
    }

    $missing = [];
    foreach ($requiredFilters as $filter) {
      if (!$this->ffmpegExecutor->hasFilter($filter)) {
        $missing[] = $filter;
      }
    }

    if ($missing !== []) {
      return 'Missing required FFmpeg filter(s): ' . implode(', ', $missing) . '.';
    }

    return 'Processor is unavailable for an unknown reason.';
  }

  /**
   * {@inheritdoc}
   *
   * @return array<string, mixed>
   *   Default options.
   */
  #[\Override]
  #[\NoDiscard]
  public function getDefaultOptions(): array {
    return [];
  }

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

    return $directory . DIRECTORY_SEPARATOR . $baseName . '_' . $this->pluginId . '.' . $extension;
  }

  /**
   * Merges options with defaults.
   *
   * @param array<string, mixed> $options
   *   Provided options.
   *
   * @return array<string, mixed>
   *   Merged options.
   */
  protected function mergeOptions(array $options): array {
    return array_merge($this->getDefaultOptions(), $options);
  }

  /**
   * Builds a basic FFmpeg command as an array for secure Process execution.
   *
   * @param string $input
   *   Input path.
   * @param string $output
   *   Output path.
   * @param array<int, string> $arguments
   *   Additional arguments.
   *
   * @return array<int, string>
   *   Complete FFmpeg command as array of arguments.
   */
  protected function buildCommand(string $input, string $output, array $arguments = []): array {
    $ffmpegPath = $this->ffmpegExecutor->getFfmpegPath();

    return [
      $ffmpegPath,
      '-y',
      '-i', $input,
      ...$arguments,
      $output,
    ];
  }

}
