import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:nut_player_platform_interface/nut_player_platform_interface.dart' show DurationRange;

/// The duration, current position, buffering state, error state and settings
/// of a [VideoPlayerController].
@immutable
class VideoPlayerValue {
  /// Constructs a video with the given values. Only [duration] is required. The
  /// rest will initialize with default values when unset.
  const VideoPlayerValue({
    required this.duration,
    this.size = Size.zero,
    this.position = Duration.zero,
    this.subtitles,
    this.qualities,
    this.buffered = const <DurationRange>[],
    this.isInitialized = false,
    this.isPlaying = false,
    this.isLooping = false,
    this.isBuffering = false,
    this.volume,
    this.playbackSpeed = 1.0,
    this.rotationCorrection = 0,
    this.errorDescription,
    this.isCompleted = false,
  });

  /// Returns an instance for a video that hasn't been loaded.
  const VideoPlayerValue.uninitialized()
      : this(duration: Duration.zero, isInitialized: false);

  /// Returns an instance with the given [errorDescription].
  const VideoPlayerValue.erroneous(String errorDescription)
      : this(
      duration: Duration.zero,
      isInitialized: false,
      errorDescription: errorDescription);

  /// This constant is just to indicate that parameter is not passed to [copyWith]
  /// workaround for this issue https://github.com/dart-lang/language/issues/2009
  static const String _defaultErrorDescription = 'defaultErrorDescription';

  /// The total duration of the video.
  ///
  /// The duration is [Duration.zero] if the video hasn't been initialized.
  final Duration duration;

  /// The current playback position.
  final Duration position;

  /// Current available subtitles where key is the id of the subtitles and value is their title.
  /// If no subtitles, the dictionary will be empty.
  final Map<String, String>? subtitles;

  /// Текущие доступные качества видео. Если их нет, массив словарей будет пустым.
  final List<Map<String, dynamic>>? qualities;

  /// The currently buffered ranges.
  final List<DurationRange> buffered;

  /// True if the video is playing. False if it's paused.
  final bool isPlaying;

  /// True if the video is looping.
  final bool isLooping;

  /// True if the video is currently buffering.
  final bool isBuffering;

  /// The current volume of the playback.
  final double? volume;

  /// The current speed of the playback.
  final double playbackSpeed;

  /// A description of the error if present.
  ///
  /// If [hasError] is false this is `null`.
  final String? errorDescription;

  final bool isCompleted;

  /// The [size] of the currently loaded video.
  final Size size;

  /// Degrees to rotate the video (clockwise) so it is displayed correctly.
  final int rotationCorrection;

  /// Indicates whether or not the video has been loaded and is ready to play.
  final bool isInitialized;

  /// Indicates whether or not the video is in an error state. If this is true
  /// [errorDescription] should have information about the problem.
  bool get hasError => errorDescription != null;

  /// Returns [size.width] / [size.height].
  ///
  /// Will return `1.0` if:
  /// * [isInitialized] is `false`
  /// * [size.width], or [size.height] is equal to `0.0`
  /// * aspect ratio would be less than or equal to `0.0`
  double get aspectRatio {
    if (!isInitialized || size.width == 0 || size.height == 0) {
      return 1.0;
    }
    final double aspectRatio = size.width / size.height;
    if (aspectRatio <= 0) {
      return 1.0;
    }
    return aspectRatio;
  }

  /// Returns a new instance that has the same values as this current instance,
  /// except for any overrides passed in as arguments to [copyWith].
  VideoPlayerValue copyWith({
    Duration? duration,
    Size? size,
    Duration? position,
    Map<String, String>? subtitles,
    List<Map<String, dynamic>>? qualities,
    List<DurationRange>? buffered,
    bool? isInitialized,
    bool? isPlaying,
    bool? isLooping,
    bool? isBuffering,
    double? volume,
    double? playbackSpeed,
    int? rotationCorrection,
    String? errorDescription = _defaultErrorDescription,
    bool? isCompleted,
  }) {
    return VideoPlayerValue(
      duration: duration ?? this.duration,
      size: size ?? this.size,
      position: position ?? this.position,
      subtitles: subtitles ?? this.subtitles,
      qualities: qualities ?? this.qualities,
      buffered: buffered ?? this.buffered,
      isInitialized: isInitialized ?? this.isInitialized,
      isPlaying: isPlaying ?? this.isPlaying,
      isLooping: isLooping ?? this.isLooping,
      isBuffering: isBuffering ?? this.isBuffering,
      volume: volume ?? this.volume,
      playbackSpeed: playbackSpeed ?? this.playbackSpeed,
      rotationCorrection: rotationCorrection ?? this.rotationCorrection,
      errorDescription: errorDescription != _defaultErrorDescription
          ? errorDescription
          : this.errorDescription,
      isCompleted: isCompleted ?? this.isCompleted,
    );
  }

  @override
  String toString() {
    return '${objectRuntimeType(this, 'VideoPlayerValue')}('
        'duration: $duration, '
        'size: $size, '
        'position: $position, '
        'subtitles: $subtitles, '
        'qualities: $qualities, '
        'buffered: [${buffered.join(', ')}], '
        'isInitialized: $isInitialized, '
        'isPlaying: $isPlaying, '
        'isLooping: $isLooping, '
        'isBuffering: $isBuffering, '
        'volume: $volume, '
        'playbackSpeed: $playbackSpeed, '
        'errorDescription: $errorDescription, '
        'isCompleted: $isCompleted), ';
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
          other is VideoPlayerValue &&
              runtimeType == other.runtimeType &&
              duration == other.duration &&
              position == other.position &&
              subtitles == other.subtitles &&
              listEquals(qualities, other.qualities) &&
              listEquals(buffered, other.buffered) &&
              isPlaying == other.isPlaying &&
              isLooping == other.isLooping &&
              isBuffering == other.isBuffering &&
              volume == other.volume &&
              playbackSpeed == other.playbackSpeed &&
              errorDescription == other.errorDescription &&
              size == other.size &&
              rotationCorrection == other.rotationCorrection &&
              isInitialized == other.isInitialized &&
              isCompleted == other.isCompleted;

  @override
  int get hashCode => Object.hash(
    duration,
    position,
    subtitles,
    qualities,
    buffered,
    isPlaying,
    isLooping,
    isBuffering,
    volume,
    playbackSpeed,
    errorDescription,
    size,
    rotationCorrection,
    isInitialized,
    isCompleted,
  );
}