<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\ValueObject;

/**
 * Immutable value object representing the result of a video conversion.
 */
final readonly class ConversionResult {

  /**
   * Constructs a ConversionResult object.
   *
   * @param int $mediaId
   *   The media entity ID.
   * @param bool $success
   *   Whether the conversion was successful.
   * @param array<string, string> $outputs
   *   Array of output file paths keyed by format.
   * @param string|null $error
   *   Error message if conversion failed.
   * @param float $duration
   *   Time taken for conversion in seconds.
   * @param array<string, mixed> $metadata
   *   Additional metadata about the conversion.
   */
  private function __construct(
    public int $mediaId,
    public bool $success,
    public array $outputs,
    public ?string $error = NULL,
    public float $duration = 0.0,
    public array $metadata = [],
  ) {}

  /**
   * Creates a successful conversion result.
   *
   * @param int $mediaId
   *   The media entity ID.
   * @param array<string, string> $outputs
   *   Array of output file paths keyed by format.
   * @param float $duration
   *   Time taken for conversion in seconds.
   * @param array<string, mixed> $metadata
   *   Additional metadata about the conversion.
   *
   * @return self
   *   New successful ConversionResult instance.
   */
  #[\NoDiscard]
  public static function success(
    int $mediaId,
    array $outputs,
    float $duration = 0.0,
    array $metadata = [],
  ): self {
    return new self(
      mediaId: $mediaId,
      success: TRUE,
      outputs: $outputs,
      error: NULL,
      duration: $duration,
      metadata: $metadata,
    );
  }

  /**
   * Creates a partial success conversion result.
   *
   * Used when some outputs succeeded but others failed.
   *
   * @param int $mediaId
   *   The media entity ID.
   * @param array<string, string> $outputs
   *   Array of successful output file paths keyed by format.
   * @param array<string, string> $failedOutputs
   *   Array of error messages keyed by failed format.
   * @param float $duration
   *   Time taken for conversion in seconds.
   * @param array<string, mixed> $metadata
   *   Additional metadata about the conversion.
   *
   * @return self
   *   New partial success ConversionResult instance.
   */
  #[\NoDiscard]
  public static function partialSuccess(
    int $mediaId,
    array $outputs,
    array $failedOutputs,
    float $duration = 0.0,
    array $metadata = [],
  ): self {
    return new self(
      mediaId: $mediaId,
      success: TRUE,
      outputs: $outputs,
      error: NULL,
      duration: $duration,
      metadata: array_merge($metadata, ['failed_outputs' => $failedOutputs]),
    );
  }

  /**
   * Creates a failed conversion result.
   *
   * @param int $mediaId
   *   The media entity ID.
   * @param string $error
   *   Error message describing the failure.
   * @param float $duration
   *   Time spent before failure in seconds.
   * @param array<string, mixed> $metadata
   *   Additional metadata about the failure.
   *
   * @return self
   *   New failed ConversionResult instance.
   */
  #[\NoDiscard]
  public static function failed(
    int $mediaId,
    string $error,
    float $duration = 0.0,
    array $metadata = [],
  ): self {
    return new self(
      mediaId: $mediaId,
      success: FALSE,
      outputs: [],
      error: $error,
      duration: $duration,
      metadata: $metadata,
    );
  }

  /**
   * Checks if the conversion was successful.
   *
   * @return bool
   *   TRUE if successful, FALSE otherwise.
   */
  #[\NoDiscard]
  public function isSuccess(): bool {
    return $this->success;
  }

  /**
   * Checks if the conversion was a partial success.
   *
   * A partial success means some outputs succeeded but others failed.
   *
   * @return bool
   *   TRUE if some outputs succeeded and others failed.
   */
  #[\NoDiscard]
  public function isPartialSuccess(): bool {
    return $this->success && !empty($this->metadata['failed_outputs']);
  }

  /**
   * Gets the failed outputs, if any.
   *
   * @return array<string, string>
   *   Array of error messages keyed by failed format, or empty array.
   */
  #[\NoDiscard]
  public function getFailedOutputs(): array {
    $failedOutputs = $this->metadata['failed_outputs'] ?? [];
    if (!is_array($failedOutputs)) {
      return [];
    }
    return $failedOutputs;
  }

  /**
   * Gets the error message, if any.
   *
   * @return string|null
   *   Error message or NULL if successful.
   */
  #[\NoDiscard]
  public function getError(): ?string {
    return $this->error;
  }

  /**
   * Gets the output path for a specific format.
   *
   * @param string $format
   *   The output format (e.g., 'webm', 'mp4').
   *
   * @return string|null
   *   Output file path or NULL if not available.
   */
  #[\NoDiscard]
  public function getOutput(string $format): ?string {
    return $this->outputs[$format] ?? NULL;
  }

  /**
   * Checks if a specific output format was generated.
   *
   * @param string $format
   *   The output format to check.
   *
   * @return bool
   *   TRUE if the format exists in outputs.
   */
  #[\NoDiscard]
  public function hasOutput(string $format): bool {
    return isset($this->outputs[$format]);
  }

  /**
   * Gets all output formats.
   *
   * @return array<string>
   *   List of output format keys.
   */
  #[\NoDiscard]
  public function getOutputFormats(): array {
    return array_keys($this->outputs);
  }

  /**
   * Gets a metadata value by key.
   *
   * @param string $key
   *   The metadata key.
   * @param mixed $default
   *   Default value if key doesn't exist.
   *
   * @return mixed
   *   The metadata value or default.
   */
  #[\NoDiscard]
  public function getMetadata(string $key, mixed $default = NULL): mixed {
    return $this->metadata[$key] ?? $default;
  }

  /**
   * Converts result to array format.
   *
   * @return array<string, bool|float|int|string|array<string, string>|array<string, mixed>|null>
   *   Associative array representation.
   */
  #[\NoDiscard]
  public function toArray(): array {
    return [
      'media_id' => $this->mediaId,
      'success' => $this->success,
      'outputs' => $this->outputs,
      'error' => $this->error,
      'duration' => $this->duration,
      'metadata' => $this->metadata,
    ];
  }

}
