<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Drupal\Core\File\FileSystemInterface;
use Drupal\media\MediaInterface;
use Drupal\ffmpeg_media\ValueObject\ConversionContext;

/**
 * Inspects source/converted output state for media entities.
 *
 * @internal
 */
final class MediaConversionInspector implements MediaConversionInspectorInterface {

  /**
   * Constructs the inspector.
   */
  public function __construct(
    private readonly FormatterSettingsResolverInterface $formatterSettingsResolver,
    private readonly SourceFileResolverInterface $sourceFileResolver,
    private readonly OutputStorageServiceInterface $outputStorage,
    private readonly QueueManagerInterface $queueManager,
    private readonly ProgressTrackerInterface $progressTracker,
    private readonly MediaArtifactManagerInterface $artifactManager,
    private readonly FileSystemInterface $fileSystem,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function canReconvert(MediaInterface $media, string $fieldName = ''): array {
    $context = $this->resolveContext($media, $fieldName);

    if ($context === NULL) {
      return [
        'can_reconvert' => FALSE,
        'reason' => 'No formatter configuration found.',
        'existing_outputs' => [],
      ];
    }
    $sourcePath = $this->sourceFileResolver->getSourcePath($media, $context->fieldName);
    if ($sourcePath === NULL || !file_exists($sourcePath)) {
      return [
        'can_reconvert' => FALSE,
        'reason' => 'Source file not found.',
        'existing_outputs' => [],
      ];
    }

    $mediaId = (int) $media->id();
    $existingOutputs = $this->collectExistingOutputUris(
      $this->outputStorage->getAllOutputs($mediaId, $context->fieldName),
    );

    return [
      'can_reconvert' => TRUE,
      'reason' => NULL,
      'existing_outputs' => $existingOutputs,
    ];
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function deleteConvertedFiles(MediaInterface $media, string $fieldName = ''): array {
    $context = $this->resolveContext($media, $fieldName);
    if ($context === NULL) {
      return [];
    }

    return $this->artifactManager->deleteArtifacts($media, fieldName: $context->fieldName);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getStatus(MediaInterface $media, string $fieldName = ''): array {
    $mediaId = (int) $media->id();
    $context = $this->resolveContext($media, $fieldName);

    $status = [
      'has_source' => FALSE,
      'is_queued' => FALSE,
      'is_converting' => FALSE,
      'is_converted' => FALSE,
      'outputs' => [],
      'error' => NULL,
    ];

    if ($context === NULL) {
      return $status;
    }
    $sourcePath = $this->sourceFileResolver->getSourcePath($media, $context->fieldName);
    $status['has_source'] = $sourcePath !== NULL && file_exists($sourcePath);
    $status['is_queued'] = $this->queueManager->isQueued($mediaId, $context->fieldName);

    $progress = $this->progressTracker->getProgress($mediaId, $context->fieldName);
    if ($progress !== NULL) {
      $status['is_converting'] = $progress->isInProgress;
      if ($progress->hasFailed) {
        $status['error'] = $progress->error;
      }
    }

    $allOutputs = $this->outputStorage->getAllOutputs($mediaId, $context->fieldName);
    $status['outputs'] = $this->collectExistingOutputUris($allOutputs);

    $status['is_converted'] = $status['outputs'] !== [];

    return $status;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function isEligibleForConversion(MediaInterface $media, string $fieldName = ''): bool {
    $context = $this->resolveContext($media, $fieldName);
    if ($context === NULL) {
      return FALSE;
    }

    $sourcePath = $this->sourceFileResolver->getSourcePath($media, $context->fieldName);
    return $sourcePath !== NULL && file_exists($sourcePath);
  }

  /**
   * Extracts only output URIs that still resolve to an existing file.
   *
   * @param array<string, array<int, \Drupal\file\FileInterface>> $allOutputs
   *   Output files keyed by output type.
   *
   * @return array<string, string>
   *   Existing output URIs keyed by output type.
   */
  private function collectExistingOutputUris(array $allOutputs): array {
    $existingOutputs = [];
    foreach ($allOutputs as $type => $files) {
      $file = $files[0] ?? NULL;
      if ($file === NULL) {
        continue;
      }

      $uri = $file->getFileUri();
      if ($uri !== NULL && $this->outputExists($uri)) {
        $existingOutputs[$type] = $uri;
      }
    }

    return $existingOutputs;
  }

  /**
   * Checks whether a stored output URI still resolves to a physical file.
   */
  private function outputExists(string $uri): bool {
    $resolvedPath = $this->fileSystem->realpath($uri);
    if (is_string($resolvedPath) && $resolvedPath !== '') {
      return file_exists($resolvedPath);
    }

    return file_exists($uri);
  }

  /**
   * Resolves the conversion context for a preferred or explicit field.
   */
  #[\NoDiscard]
  private function resolveContext(MediaInterface $media, string $fieldName = ''): ?ConversionContext {
    if ($fieldName !== '') {
      return $this->formatterSettingsResolver->getContextForField($media, $fieldName);
    }

    return $this->formatterSettingsResolver->getPreferredContextForEntity($media);
  }

}
