<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\ffmpeg_media\Enum\OutputType;
use Drupal\ffmpeg_media\Exception\FFmpegException;
use Drupal\ffmpeg_media\ValueObject\ConversionContext;
use Drupal\ffmpeg_media\ValueObject\SubtitleTrack;
use Drupal\media\MediaInterface;

/**
 * Handles subtitle track preparation and conversion.
 *
 * Manages extracting subtitle files from media entities, converting
 * to WebVTT format via FFmpeg, and language code detection.
 *
 * @internal
 */
final class SubtitleHandler implements SubtitleHandlerInterface {

  /**
   * Constructs a SubtitleHandler.
   *
   * @param \Drupal\ffmpeg_media\Service\OutputStorageServiceInterface $outputStorage
   *   Output storage service.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   File system service.
   * @param \Drupal\ffmpeg_media\Service\FFmpegExecutorInterface $ffmpegExecutor
   *   FFmpeg executor service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config factory service.
   * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
   *   Logger channel.
   */
  public function __construct(
    protected readonly OutputStorageServiceInterface $outputStorage,
    protected readonly FileSystemInterface $fileSystem,
    protected readonly FFmpegExecutorInterface $ffmpegExecutor,
    protected readonly ConfigFactoryInterface $configFactory,
    protected readonly LoggerChannelInterface $logger,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function prepareSubtitleTracks(
    MediaInterface $media,
    ConversionContext $context,
  ): array {
    $files = $this->outputStorage->getOutputs((int) $media->id(), $context->fieldName, OutputType::SubtitleTracks->value);
    if (empty($files)) {
      return ['tracks' => [], 'tempFiles' => []];
    }

    $tracks = [];
    $tempFiles = [];

    foreach ($files as $file) {
      $uri = $file->getFileUri();
      if ($uri === NULL) {
        continue;
      }
      $path = $this->fileSystem->realpath($uri);
      if ($path === FALSE || !file_exists($path)) {
        $this->logger->warning('Subtitle file not accessible: @uri', ['@uri' => $uri]);
        continue;
      }

      $filename = $file->getFilename();
      $language = $filename !== NULL ? ($this->extractLanguageFromFilename($filename) ?? 'und') : 'und';
      $language = $this->normalizeLanguageCode($language);
      $label = $this->buildSubtitleLabel($language);

      try {
        $track = new SubtitleTrack($language, $label, $path);
      }
      catch (\InvalidArgumentException $e) {
        $this->logger->warning('Skipping subtitle track: @error', ['@error' => $e->getMessage()]);
        continue;
      }

      if ($track->needsWebVttConversion()) {
        $converted = $this->convertSubtitleToWebVtt($track->path);
        if ($converted === NULL) {
          continue;
        }
        $tempFiles[] = $converted;
        $track = $track->withPath($converted);
      }

      $tracks[] = $track;
    }

    $hasDefault = FALSE;
    foreach ($tracks as $track) {
      if ($track->isDefault) {
        $hasDefault = TRUE;
        break;
      }
    }

    if (!$hasDefault && !empty($tracks)) {
      $tracks[0] = $tracks[0]->asDefault();
    }

    return ['tracks' => $tracks, 'tempFiles' => $tempFiles];
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function convertSubtitleToWebVtt(string $inputPath): ?string {
    $config = $this->configFactory->get('ffmpeg_media.settings');
    $timeout = (int) ($config->get('ffmpeg_timeout') ?? 300);

    $tempDir = $this->fileSystem->getTempDirectory();
    $this->fileSystem->prepareDirectory($tempDir, FileSystemInterface::CREATE_DIRECTORY);
    $realTempDir = $this->fileSystem->realpath($tempDir) ?: $tempDir;
    $outputPath = $realTempDir . DIRECTORY_SEPARATOR . 'ffmpeg_sub_' . bin2hex(random_bytes(8)) . '.vtt';

    $command = [
      $this->ffmpegExecutor->getFfmpegPath(),
      '-y',
      '-i',
      $inputPath,
      '-c:s',
      'webvtt',
      $outputPath,
    ];

    try {
      (void) $this->ffmpegExecutor->execute($command, ['timeout' => $timeout]);
    }
    catch (FFmpegException $e) {
      $this->logger->warning('Subtitle conversion failed: @error', ['@error' => $e->getMessage()]);
      return NULL;
    }

    if (!file_exists($outputPath) || filesize($outputPath) === 0) {
      try {
        $this->fileSystem->delete($outputPath);
      }
      catch (\Throwable) {
        // File already gone — safe to ignore.
      }
      throw new FFmpegException('Subtitle conversion produced empty output.');
    }

    return $outputPath;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function extractLanguageFromFilename(string $filename): ?string {
    $baseName = pathinfo($filename, PATHINFO_FILENAME);
    $parts = preg_split('/[._-]+/', $baseName);
    if ($parts === FALSE) {
      return NULL;
    }

    $candidate = array_find(
      array_reverse($parts),
      static function (string $part): bool {
        if ($part === '') {
          return FALSE;
        }
        $normalized = str_replace('_', '-', $part);
        return (bool) preg_match('/^[a-z]{2,3}(-[A-Za-z]{2})?$/', $normalized);
      },
    );

    return $candidate !== NULL ? str_replace('_', '-', $candidate) : NULL;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function normalizeLanguageCode(string $language): string {
    $normalized = str_replace('_', '-', $language);
    $parts = explode('-', $normalized, 2);
    $lang = strtolower($parts[0]);

    if (isset($parts[1]) && $parts[1] !== '') {
      return $lang . '-' . strtoupper($parts[1]);
    }

    return $lang;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function buildSubtitleLabel(string $language): string {
    if (class_exists(\Locale::class)) {
      $label = \Locale::getDisplayLanguage($language, $language);
      if (!empty($label)) {
        return $label;
      }
    }

    return strtoupper($language);
  }

}
