part of '../youtube_info_controller.dart';

class _VideoInfoController {
  const _VideoInfoController();

  /// tv_embedded can bypass age restricted and obtain higher quality streams,
  /// but doesnt work with many vids, web clients are more stable and can also obtain higher quality streams.
  /// internally, a set of fallbacks is used before returning null result.
  /// the fallback clients can be modified using [YoutiPie.setDefaultClients].
  ///
  /// even tho web clients are more prone to errors,
  /// they're the only clients that contain reliable info like date, comments & endcards.
  static final _defaultClient = InnertubeClients.tv;
  static const _defaultRequiresJSPlayer = true;

  InnertubeClients get _usedClient => settings.youtube.innertubeClient ?? _defaultClient;
  bool get _requiresJSPlayer {
    final userSpecified = settings.youtube.innertubeClient;
    if (userSpecified == null) return _defaultRequiresJSPlayer;
    return userSpecified.configuration.requireJSPlayer == true;
  }

  bool get jsPreparedIfRequired => _requiresJSPlayer ? YoutiPie.cipher.isPrepared : true;

  Future<bool> ensureJSPlayerInitialized() async {
    if (jsPreparedIfRequired) return true;
    return YoutiPie.cipher.prepareJSPlayer(cacheDirectoryPath: AppDirs.YOUTIPIE_CACHE);
  }

  Future<bool> forceRefreshJSPlayer() async {
    return YoutiPie.cipher.forceRefreshJSPlayer(cacheDirectoryPath: AppDirs.YOUTIPIE_CACHE);
  }

  String? getJSPlayerVersion() => YoutiPie.cipher.jsPlayerVersion;

  Future<LikeStatus?> fetchLikeStatusForVideoCardInstant(String videoId, {ExecuteDetails? details}) async {
    if (settings.youtube.showLikeStatusOnCards.valueF) {
      final page = await this.fetchVideoPage(videoId, details: details);
      return page?.videoInfo?.engagement?.likeStatus ?? LikeStatus.unknown;
    }
    return null;
  }

  Future<YoutiPieVideoPageResult?> fetchVideoPage(String videoId, {ExecuteDetails? details}) async {
    final relatedVideosParams = YoutubeInfoController.current._relatedVideosParams;
    final res = await YoutiPie.video.fetchVideoPage(videoId: videoId, relatedVideosParams: relatedVideosParams, details: details);
    if (res != null && res.videoInfo == null && res.channelInfo == null) {
      VideoController.inst.videosPriorityManager.setVideoPriority(videoId, CacheVideoPriority.VIP);
    }
    return res;
  }

  Future<YoutiPieRelatedVideosResult?> fetchRelatedVideos(
    String videoId, {
    required bool userPersonalized,
    required bool? preferMix,
    ExecuteDetails? details,
  }) async {
    if (preferMix == true) {
      final mixRes = await YoutiPie.playlist.getMixPlaylist(
        videoId: videoId,
        userPersonalized: userPersonalized,
        includeFirstVideo: false,
        details: details,
      );
      if (mixRes != null) {
        final items = mixRes.items;
        if (items.isNotEmpty) {
          return YoutiPieRelatedVideosResult(
            cacheKey: mixRes.cacheKey,
            items: items,
            continuation: mixRes.continuation,
            videoId: videoId,
            isPersonalized: userPersonalized,
          );
        }
      }
    }
    final relatedVideosParams = YoutubeInfoController.current._relatedVideosParams;
    final res = await YoutiPie.video.fetchRelatedVideos(
      videoId: videoId,
      userPersonalized: userPersonalized,
      preferMix: false, // -- we manually do the job here
      relatedVideosParams: relatedVideosParams,
      details: details,
    );
    return res;
  }

  Future<YoutiPieVideoPageResult?> fetchVideoPageCache(String videoId) {
    final res = YoutiPie.cacheBuilder.forVideoPage(videoId: videoId);
    return res.read();
  }

  YoutiPieVideoPageResult? fetchVideoPageCacheSync(String videoId) {
    final res = YoutiPie.cacheBuilder.forVideoPage(videoId: videoId);
    return res.readSync();
  }

  /// By default, this will force a network request since most implementations use this as fallback to [fetchVideoStreamsCache].
  Future<VideoStreamsResult?> fetchVideoStreams(String videoId, {bool forceRequest = true}) async {
    // -- preparing jsplayer *before* fetching streams is important, fetching *after* will return non-working urls.
    if (_requiresJSPlayer) {
      // -- await preparing before returning result
      if (!YoutiPie.cipher.isPrepared) {
        await ensureJSPlayerInitialized();
      }
    }
    final res = await YoutiPie.video.fetchVideoStreams(
      id: videoId,
      details: forceRequest ? ExecuteDetails.kForceRequest : null,
      client: _usedClient,
    );
    if (res != null && res.playability.status != VideoPlayabiltyStatus.ok) {
      VideoController.inst.videosPriorityManager.setVideoPriority(videoId, CacheVideoPriority.VIP);
    }
    return res;
  }

  Future<File?> fetchMusicVideoThumbnailToFile(String videoId, String filePath) {
    return YoutiPie.video.fetchMusicVideoThumbnailToFile(id: videoId, filePath: filePath);
  }

  /// Returns cached streams result if exist. you need to ensure that urls didn't expire ![VideoStreamsResult.hasExpired] before consuming them.
  ///
  /// Enable [bypassJSCheck] if you gurantee using [VideoStreamsResult.info] only.
  Future<VideoStreamsResult?> fetchVideoStreamsCache(String videoId, {bool bypassJSCheck = false, bool infoOnly = true}) async {
    final res = YoutiPie.cacheBuilder.forVideoStreams(videoId: videoId);
    final cached = await res.read();
    if (infoOnly) return cached;
    if (cached == null || cached.client != _usedClient) return null;
    if (_requiresJSPlayer && bypassJSCheck == false) {
      if (!YoutiPie.cipher.isPrepared) {
        ensureJSPlayerInitialized();
        return null; // the player is not prepared, hence the urls are just useless
      }
    }
    return cached;
  }

  VideoStreamsResult? fetchVideoStreamsCacheSync(String videoId, {bool bypassJSCheck = true, bool infoOnly = true}) {
    final res = YoutiPie.cacheBuilder.forVideoStreams(videoId: videoId);
    final cached = res.readSync();
    if (infoOnly) return cached;
    if (cached == null || cached.client != _usedClient) return null;
    if (_requiresJSPlayer && bypassJSCheck == false) {
      if (!YoutiPie.cipher.isPrepared) {
        ensureJSPlayerInitialized();
        return null; // the player is not prepared, hence the urls are just useless
      }
    }
    return cached;
  }
}
