import 'package:freezed_annotation/freezed_annotation.dart';

part 'tmdb.freezed.dart';
part 'tmdb.g.dart';

@freezed
abstract class TmdbCastMember with _$TmdbCastMember {
  const factory TmdbCastMember({
    @JsonKey(fromJson: _intRequired) required int id,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String name,
    String? character,
    @JsonKey(name: 'profile_path') String? profilePath,
    @JsonKey(fromJson: _intOrZero) @Default(0) int order,
  }) = _TmdbCastMember;

  factory TmdbCastMember.fromJson(Map<String, dynamic> json) =>
      _$TmdbCastMemberFromJson(json);
}

@freezed
abstract class TmdbCrewMember with _$TmdbCrewMember {
  const factory TmdbCrewMember({
    @JsonKey(fromJson: _intRequired) required int id,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String name,
    String? job,
    String? department,
    @JsonKey(name: 'profile_path') String? profilePath,
  }) = _TmdbCrewMember;

  factory TmdbCrewMember.fromJson(Map<String, dynamic> json) =>
      _$TmdbCrewMemberFromJson(json);
}

@freezed
abstract class TmdbCreditsRes with _$TmdbCreditsRes {
  const factory TmdbCreditsRes({
    @Default(<TmdbCastMember>[]) List<TmdbCastMember> cast,
    @Default(<TmdbCrewMember>[]) List<TmdbCrewMember> crew,
  }) = _TmdbCreditsRes;

  factory TmdbCreditsRes.fromJson(Map<String, dynamic> json) =>
      _$TmdbCreditsResFromJson(json);
}

@freezed
abstract class TmdbRecommendation with _$TmdbRecommendation {
  const factory TmdbRecommendation({
    @JsonKey(fromJson: _intRequired) required int id,
    required String title,
    @JsonKey(name: 'poster_path') String? posterPath,
    @JsonKey(name: 'backdrop_path') String? backdropPath,
    String? overview,
    @JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
    @JsonKey(name: 'media_type') String? mediaType,
  }) = _TmdbRecommendation;

  factory TmdbRecommendation.fromJson(Map<String, dynamic> json) =>
      _$TmdbRecommendationFromJson({
        ...json,
        'title': json['title'] ?? json['name'] ?? '',
      });
}

@freezed
abstract class TmdbRecommendationsRes with _$TmdbRecommendationsRes {
  const factory TmdbRecommendationsRes({
    @Default(<TmdbRecommendation>[]) List<TmdbRecommendation> results,
    @JsonKey(name: 'total_results', fromJson: _intOrZero)
    @Default(0)
    int totalResults,
  }) = _TmdbRecommendationsRes;

  factory TmdbRecommendationsRes.fromJson(Map<String, dynamic> json) =>
      _$TmdbRecommendationsResFromJson(json);
}

@freezed
abstract class TmdbEpisode with _$TmdbEpisode {
  const factory TmdbEpisode({
    @JsonKey(name: 'episode_number', fromJson: _intRequired)
    required int episodeNumber,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String name,
    String? overview,
    @JsonKey(name: 'still_path') String? stillPath,
    @JsonKey(name: 'air_date') String? airDate,
    @JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
    @JsonKey(fromJson: _intOrNull) int? runtime,
  }) = _TmdbEpisode;

  factory TmdbEpisode.fromJson(Map<String, dynamic> json) =>
      _$TmdbEpisodeFromJson(json);
}

@freezed
abstract class TmdbSeasonDetail with _$TmdbSeasonDetail {
  const factory TmdbSeasonDetail({
    @JsonKey(name: 'season_number', fromJson: _intRequired)
    required int seasonNumber,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String name,
    String? overview,
    @JsonKey(name: 'poster_path') String? posterPath,
    @Default(<TmdbEpisode>[]) List<TmdbEpisode> episodes,
  }) = _TmdbSeasonDetail;

  factory TmdbSeasonDetail.fromJson(Map<String, dynamic> json) =>
      _$TmdbSeasonDetailFromJson(json);
}

@freezed
abstract class TmdbVideo with _$TmdbVideo {
  const TmdbVideo._();

  const factory TmdbVideo({
    @JsonKey(fromJson: _strOrEmpty) @Default('') String id,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String key,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String name,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String site,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String type,
    @JsonKey(fromJson: _intOrZero) @Default(0) int size,
    @JsonKey(fromJson: _boolOrFalse) @Default(false) bool official,
  }) = _TmdbVideo;

  factory TmdbVideo.fromJson(Map<String, dynamic> json) =>
      _$TmdbVideoFromJson(json);

  String? get youtubeUrl => site == 'YouTube' && key.isNotEmpty
      ? 'https://www.youtube.com/watch?v=$key'
      : null;

  String? get youtubeThumbnailUrl => site == 'YouTube' && key.isNotEmpty
      ? 'https://img.youtube.com/vi/$key/mqdefault.jpg'
      : null;
}

@freezed
abstract class TmdbVideosRes with _$TmdbVideosRes {
  const factory TmdbVideosRes({
    @Default(<TmdbVideo>[]) List<TmdbVideo> results,
  }) = _TmdbVideosRes;

  factory TmdbVideosRes.fromJson(Map<String, dynamic> json) =>
      _$TmdbVideosResFromJson(json);
}

@freezed
abstract class TmdbPersonDetail with _$TmdbPersonDetail {
  const factory TmdbPersonDetail({
    @JsonKey(fromJson: _intRequired) required int id,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String name,
    String? biography,
    String? birthday,
    String? deathday,
    @JsonKey(name: 'place_of_birth') String? placeOfBirth,
    @JsonKey(name: 'profile_path') String? profilePath,
    @JsonKey(name: 'known_for_department') String? knownForDepartment,
    @JsonKey(fromJson: _doubleOrNull) double? popularity,
  }) = _TmdbPersonDetail;

  factory TmdbPersonDetail.fromJson(Map<String, dynamic> json) =>
      _$TmdbPersonDetailFromJson(json);
}

@freezed
abstract class TmdbPersonCredit with _$TmdbPersonCredit {
  const factory TmdbPersonCredit({
    @JsonKey(fromJson: _intRequired) required int id,
    required String title,
    String? character,
    @JsonKey(name: 'poster_path') String? posterPath,
    @JsonKey(name: 'release_date') String? releaseDate,
    @JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
    @JsonKey(name: 'media_type') String? mediaType,
  }) = _TmdbPersonCredit;

  factory TmdbPersonCredit.fromJson(Map<String, dynamic> json) =>
      _$TmdbPersonCreditFromJson({
        ...json,
        'title': json['title'] ?? json['name'] ?? '',
        'release_date': json['release_date'] ?? json['first_air_date'],
      });
}

@freezed
abstract class TmdbPersonCreditsRes with _$TmdbPersonCreditsRes {
  const factory TmdbPersonCreditsRes({
    @Default(<TmdbPersonCredit>[]) List<TmdbPersonCredit> cast,
  }) = _TmdbPersonCreditsRes;

  factory TmdbPersonCreditsRes.fromJson(Map<String, dynamic> json) =>
      _$TmdbPersonCreditsResFromJson(json);
}

@freezed
abstract class TmdbGenre with _$TmdbGenre {
  const factory TmdbGenre({
    @JsonKey(fromJson: _intRequired) required int id,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String name,
  }) = _TmdbGenre;

  factory TmdbGenre.fromJson(Map<String, dynamic> json) =>
      _$TmdbGenreFromJson(json);
}

@freezed
abstract class TmdbMediaDetail with _$TmdbMediaDetail {
  const factory TmdbMediaDetail({
    @JsonKey(fromJson: _intRequired) required int id,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String title,
    @JsonKey(name: 'poster_path') String? posterPath,
    @JsonKey(name: 'backdrop_path') String? backdropPath,
    @JsonKey(name: 'release_date') String? releaseDate,
    @Default(<TmdbGenre>[]) List<TmdbGenre> genres,
    @JsonKey(fromJson: _intOrNull) int? runtime,
    String? tagline,
    String? overview,
    String? status,
    String? homepage,
    @JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
    @JsonKey(name: 'vote_count', fromJson: _intOrNull) int? voteCount,
    @JsonKey(fromJson: _intOrNull) int? revenue,
    @JsonKey(fromJson: _intOrNull) int? budget,
  }) = _TmdbMediaDetail;


  factory TmdbMediaDetail.fromJson(Map<String, dynamic> json) =>
      _$TmdbMediaDetailFromJson({
        ...json,
        'title': json['title'] ?? json['name'] ?? '',
        'release_date': json['release_date'] ?? json['first_air_date'],
        'runtime':
            json['runtime'] ??
            (json['episode_run_time'] is List &&
                    (json['episode_run_time'] as List).isNotEmpty
                ? (json['episode_run_time'] as List).first
                : null),
      });
}

@freezed
abstract class TmdbImage with _$TmdbImage {
  const TmdbImage._();

  const factory TmdbImage({
    @JsonKey(name: 'file_path', fromJson: _strOrEmpty)
    @Default('')
    String filePath,
    @JsonKey(fromJson: _intOrZero) @Default(0) int width,
    @JsonKey(fromJson: _intOrZero) @Default(0) int height,
    @JsonKey(name: 'vote_average', fromJson: _doubleOrNull) double? voteAverage,
    @JsonKey(name: 'iso_639_1') String? iso639,
  }) = _TmdbImage;

  factory TmdbImage.fromJson(Map<String, dynamic> json) =>
      _$TmdbImageFromJson(json);

  bool get isLanguageNeutral => iso639 == null;
}

@freezed
abstract class TmdbImageSet with _$TmdbImageSet {
  const factory TmdbImageSet({
    @Default(<TmdbImage>[]) List<TmdbImage> backdrops,
    @Default(<TmdbImage>[]) List<TmdbImage> posters,
    @Default(<TmdbImage>[]) List<TmdbImage> logos,
  }) = _TmdbImageSet;

  factory TmdbImageSet.fromJson(Map<String, dynamic> json) =>
      _$TmdbImageSetFromJson(json);
}

@freezed
abstract class TmdbNetwork with _$TmdbNetwork {
  const factory TmdbNetwork({
    @JsonKey(fromJson: _intRequired) required int id,
    @JsonKey(fromJson: _strOrEmpty) @Default('') String name,
    @JsonKey(name: 'logo_path') String? logoPath,
    @JsonKey(name: 'origin_country') String? originCountry,
  }) = _TmdbNetwork;

  factory TmdbNetwork.fromJson(Map<String, dynamic> json) =>
      _$TmdbNetworkFromJson(json);
}

@freezed
abstract class TmdbEnrichedDetail with _$TmdbEnrichedDetail {
  const factory TmdbEnrichedDetail({
    required TmdbMediaDetail detail,
    TmdbVideosRes? videos,
    TmdbCreditsRes? credits,
    TmdbRecommendationsRes? recommendations,
    TmdbImageSet? images,


    String? imdbId,
    @Default(<TmdbNetwork>[]) List<TmdbNetwork> networks,
  }) = _TmdbEnrichedDetail;

  factory TmdbEnrichedDetail.fromJson(Map<String, dynamic> json) =>
      _$TmdbEnrichedDetailFromJson({
        'detail': json,
        'videos': json['videos'],
        'credits': json['credits'],
        'recommendations': json['recommendations'],
        'images': json['images'],
        'imdbId': _imdbIdOf(json['external_ids']),
        'networks': json['networks'],
      });
}


String? _imdbIdOf(Object? externalIds) {
  if (externalIds is! Map) return null;
  final v = externalIds['imdb_id'];
  if (v is! String) return null;
  final trimmed = v.trim();
  return trimmed.isEmpty ? null : trimmed;
}

int _intRequired(Object? v) => (v as num).toInt();
int _intOrZero(Object? v) => (v as num?)?.toInt() ?? 0;
int? _intOrNull(Object? v) => (v as num?)?.toInt();
double? _doubleOrNull(Object? v) => (v as num?)?.toDouble();
bool _boolOrFalse(Object? v) => (v as bool?) ?? false;
String _strOrEmpty(Object? v) => v?.toString() ?? '';
