part of 'youtube_info_controller.dart';

class _YoutubeCurrentInfoController {
  _YoutubeCurrentInfoController._();

  RelatedVideosRequestParams get _relatedVideosParams => const RelatedVideosRequestParams.allowAll(); // -- from settings
  bool get _canShowComments => settings.youtube.youtubeStyleMiniplayer.value;
  _RelatedVideosType _getRelatedVideosType([bool? preferMix]) {
    final preferMixRelatedVideos = (preferMix ?? settings.youtube.preferMixRelatedVideos.value) == true;
    if (preferMixRelatedVideos) {
      return settings.youtube.personalizedMixPlaylists.valueF ? .personalizedMixVideos : .nonPersonalizedMixVideos;
    } else {
      return settings.youtube.personalizedRelatedVideos.valueF ? .personalizedRelatedVideos : .nonPersonalizedRelatedVideos;
    }
  }

  bool get _dislikeCountEnabled => settings.youtube.ryd.value.enabled;

  RxBaseCore<YoutiPieVideoPageResult?> get currentVideoPage => _currentVideoPage;
  RxBaseCore<int?> get currentDislikeCount => _currentDislikeCount;
  RxBaseCore<VideoStreamInfo?> get currentStreamInfo => _currentStreamInfo;
  RxBaseCore<YoutiPieChannelPageResult?> get currentChannelPage => _currentChannelPage;
  RxBaseCore<YoutiPieRelatedVideosResult?> get currentRelatedVideos => _currentRelatedVideos;
  RxBaseCore<YoutiPieCommentResult?> get currentComments => _currentComments;
  RxBaseCore<bool> get isLoadingVideoPage => _isLoadingVideoPage;
  RxBaseCore<bool> get isLoadingInitialComments => _isLoadingInitialComments;
  RxBaseCore<bool> get isLoadingMoreComments => _isLoadingMoreComments;

  /// Used to keep track of current comments sources, mainly to
  /// prevent fetching next comments when cached version is loaded.
  RxBaseCore<bool?> get isCurrentCommentsFromCache => _isCurrentCommentsFromCache;

  /// Used as a backup in case of no connection.
  final currentCachedQualities = <NamidaVideo>[].obs;

  final _currentVideoPage = Rxn<YoutiPieVideoPageResult>();
  final _currentDislikeCount = Rxn<int>();
  final _currentStreamInfo = Rxn<VideoStreamInfo>();
  final _currentChannelPage = Rxn<YoutiPieChannelPageResult>();
  final _currentRelatedVideos = Rxn<YoutiPieRelatedVideosResult>();
  final _currentComments = Rxn<YoutiPieCommentResult>();
  final currentYTStreams = Rxn<VideoStreamsResult>();
  final _isLoadingVideoPage = false.obs;
  final _isLoadingInitialComments = false.obs;
  final _isLoadingMoreComments = false.obs;
  final _isCurrentCommentsFromCache = Rxn<bool>();

  String? _initialCommentsContinuation;

  /// Checks if the requested id is still playing, since most functions are async and will often
  /// take time to fetch from internet, and user may have played other vids, this covers such cases.
  bool _canSafelyModifyMetadata(String id) => Player.inst.currentVideo?.id == id;

  void Function()? onVideoPageReset;

  void resetAll() {
    currentCachedQualities.clear();
    _currentVideoPage.value = null;
    _currentDislikeCount.value = null;
    _currentStreamInfo.value = null;
    _currentChannelPage.value = null;
    _currentRelatedVideos.value = null;
    _currentComments.value = null;
    currentYTStreams.value = null;
    _isLoadingInitialComments.value = false;
    _isLoadingVideoPage.value = false;
    _isLoadingMoreComments.value = false;
    _isCurrentCommentsFromCache.value = null;
  }

  Future<void> onPersonalizedRelatedVideosChanged({required bool? personalized, required bool? preferMix}) async {
    final didChangePreferMix = preferMix != null;
    preferMix ??= settings.youtube.preferMixRelatedVideos.value;
    final relatedVideosType = _getRelatedVideosType(preferMix);

    YoutiPieRelatedVideosResult? relatedResult;
    if (relatedVideosType == .personalizedRelatedVideos) {
      relatedResult = _currentVideoPage.value?.relatedVideosResult;
    }
    if (relatedResult == null || didChangePreferMix) {
      final videoId = Player.inst.currentVideo?.id ?? _currentVideoPage.value?.videoId;
      if (videoId != null) {
        final relatedRes = await YoutubeInfoController.video.fetchRelatedVideos(
          videoId,
          userPersonalized: relatedVideosType.isUserPersonalized,
          preferMix: preferMix,
          details: ExecuteDetails.normal(),
        );
        if (_canSafelyModifyMetadata(videoId)) {
          relatedResult = relatedRes;
        }
      }
    }
    _currentRelatedVideos.value = relatedResult;
  }

  Future<bool> updateVideoPageCache(String videoId) async {
    final vidcache = YoutiPie.cacheBuilder.forVideoPage(videoId: videoId);
    final vidPageCached = await vidcache.read();
    if (!_canSafelyModifyMetadata(videoId)) return false;
    _currentVideoPage.value = vidPageCached;

    if (_dislikeCountEnabled) {
      final ryd = YoutiPie.cacheBuilder.forReturnYoutubeDislike(videoId: videoId);
      final rydCached = await ryd.read();
      if (!_canSafelyModifyMetadata(videoId)) return false;
      _currentDislikeCount.value = rydCached?.dislikes;
    } else {
      _currentDislikeCount.value = null;
    }

    final streamInfo = YoutiPie.cacheBuilder.forVideoStreams(videoId: videoId);
    final streamInfoCached = await streamInfo.read();
    if (!_canSafelyModifyMetadata(videoId)) return false;
    _currentStreamInfo.value = streamInfoCached?.info ?? await YoutubeInfoController.utils.buildVideoStreamInfoFromCache(videoId);

    final chId = vidPageCached?.channelInfo?.id ?? await YoutubeInfoController.utils.getVideoChannelID(videoId);
    final chPage = chId == null ? null : await YoutiPie.cacheBuilder.forChannel(channelId: chId).read();
    if (!_canSafelyModifyMetadata(videoId)) return false;
    _currentChannelPage.value = chPage;

    YoutiPieRelatedVideosResult? relatedVideos = await _getRelatedVideosCache(videoId);
    if (relatedVideos == null) {
      final relatedVideosType = _getRelatedVideosType();
      final alreadyAnonymous = YoutubeAccountController.current.activeAccountChannel.value == null;
      if (relatedVideosType == .personalizedRelatedVideos || (alreadyAnonymous && relatedVideosType == .nonPersonalizedRelatedVideos)) {
        relatedVideos = vidPageCached?.relatedVideosResult;
      }
    }
    if (!_canSafelyModifyMetadata(videoId)) return false;
    _currentRelatedVideos.value = relatedVideos;
    return vidPageCached != null;
  }

  Future<YoutiPieRelatedVideosResult?> _getRelatedVideosCache(String videoId) async {
    final relatedVideosType = _getRelatedVideosType();

    if (relatedVideosType.isMixVideos) {
      final mixId = 'RD$videoId';
      final isPersonalized = relatedVideosType.isUserPersonalized;
      final mixCache = YoutiPie.cacheBuilder.forMixPlaylistVideos(
        mixId: mixId,
        userPersonalized: isPersonalized,
      );
      final mixVideos = await mixCache.read();
      final relatedVideos = mixVideos?.toRelatedVideosResult();
      return relatedVideos;
    } else {
      final relatedcache = YoutiPie.cacheBuilder.forRelatedVideos(
        videoId: videoId,
        userPersonalized: relatedVideosType.isUserPersonalized,
      );
      YoutiPieRelatedVideosResult? relatedVideos = await relatedcache.read();
      return relatedVideos;
    }
  }

  Future<bool> updateCurrentCommentsCache(String videoId) async {
    final commcache = YoutiPie.cacheBuilder.forComments(videoId: videoId);
    final comms = await commcache.read();
    if (!_canSafelyModifyMetadata(videoId)) return false;
    _currentComments.value = comms;
    _isCurrentCommentsFromCache.value = _currentComments.value != null;
    return comms != null;
  }

  Future<void> updateVideoPage(String videoId, {required bool requestPage, required bool requestComments, CommentsSortType? commentsSort}) async {
    if (!ConnectivityController.inst.hasConnection) {
      snackyy(
        title: "${lang.error} (${lang.video})",
        message: lang.noNetworkAvailableToFetchData,
        isError: true,
        top: false,
      );
      return;
    }

    bool resetCurrentInfoOnFetch = true;

    final currentPage = _currentVideoPage.value;
    final maxCacheDurationMin = settings.youtube.maxPageCacheDurationMin;

    if (!requestPage) {
      // -- force set to true if cache duration invalid
      if (maxCacheDurationMin > 0) {
        if (maxCacheDurationMin > settings.youtube.kMinutesInMaxDaysForPageCache) {
          // -- always invalid
          requestPage = true;
          resetCurrentInfoOnFetch = false;
        } else {
          final timestamp = currentPage?.timestamp;
          if (timestamp == null || DateTime.now().difference(timestamp).inMinutes > maxCacheDurationMin) {
            requestPage = true;
            resetCurrentInfoOnFetch = false;
          }
        }
      }
    }

    final relatedVideosType = _getRelatedVideosType();

    if (!requestPage && !requestComments) {
      if (relatedVideosType != .personalizedRelatedVideos) _fetchAndUpdateRelatedVideos(videoId, relatedVideosType: relatedVideosType);
      if (_dislikeCountEnabled) fetchAndUpdateDislikeCount(videoId);
      return;
    }

    if (requestPage && resetCurrentInfoOnFetch) {
      if (onVideoPageReset != null) onVideoPageReset!(); // jumps miniplayer to top
      _currentVideoPage.value = null;
      _currentDislikeCount.value = null;
      _currentRelatedVideos.value = null;
      _currentChannelPage.value = null;
    }

    if (requestComments) {
      _currentComments.value = null;
      _initialCommentsContinuation = null;
    }

    commentsSort ??= YoutubeMiniplayerUiController.inst.currentCommentSort.value;

    if (resetCurrentInfoOnFetch) _isLoadingVideoPage.value = true;
    final page = await YoutubeInfoController.video.fetchVideoPage(videoId, details: ExecuteDetails.kForceRequest);
    if (resetCurrentInfoOnFetch) _isLoadingVideoPage.value = false;

    if (page != null) {
      final chId = page.channelInfo?.id ?? await YoutubeInfoController.utils.getVideoChannelID(videoId);
      if (chId != null) {
        YoutubeInfoController.channel.fetchChannelInfo(channelId: page.channelInfo?.id, details: ExecuteDetails.kForceRequest).then(
          (chPage) {
            if (_canSafelyModifyMetadata(videoId)) {
              _currentChannelPage.value = chPage;
            }
          },
        );
      }
    }

    if (_dislikeCountEnabled) {
      fetchAndUpdateDislikeCount(videoId);
    }

    if (_canSafelyModifyMetadata(videoId)) {
      if (requestPage) {
        _currentVideoPage.value = page; // page is still requested cuz comments need it
      }
      if (relatedVideosType == .personalizedRelatedVideos) {
        _currentRelatedVideos.value = page?.relatedVideosResult;
      } else {
        _fetchAndUpdateRelatedVideos(
          videoId,
          relatedVideosType: relatedVideosType,
        );
      }

      if (requestComments) {
        final commentsContinuation = page?.commentResult.continuation;
        if (commentsContinuation != null && _canShowComments) {
          _isLoadingInitialComments.value = true;
          final comm = await YoutubeInfoController.comment.fetchComments(
            videoId: videoId,
            continuationToken: commentsContinuation,
            details: ExecuteDetails.kForceRequest,
          );
          if (_canSafelyModifyMetadata(videoId)) {
            _isLoadingInitialComments.value = false;
            _currentVideoPage.refresh();
            _currentChannelPage.refresh();
            _currentComments.value = comm;
            _isCurrentCommentsFromCache.value = false;
            _initialCommentsContinuation = comm?.continuation;
          }
        }
      }
    }
  }

  Future<void> _fetchAndUpdateRelatedVideos(String videoId, {required _RelatedVideosType relatedVideosType}) async {
    final relatedVideos = await YoutubeInfoController.video.fetchRelatedVideos(
      videoId,
      userPersonalized: relatedVideosType.isUserPersonalized,
      preferMix: relatedVideosType.isMixVideos,
      details: switch (relatedVideosType) {
        .personalizedRelatedVideos => ExecuteDetails.kForceRequest, // shouldn't even be here, personalized related videos are taken directly from video page
        .nonPersonalizedRelatedVideos => ExecuteDetails.kForceRequest,
        .personalizedMixVideos || .nonPersonalizedMixVideos => null,
      },
    );
    if (_canSafelyModifyMetadata(videoId)) {
      _currentRelatedVideos.value = relatedVideos;
    }
  }

  Future<void> fetchAndUpdateDislikeCount(String videoId) async {
    final dislikeInfo = await YoutubeInfoController.returnyoutubedislike.fetchDislikes(videoId);
    if (_canSafelyModifyMetadata(videoId)) {
      _currentDislikeCount.value = dislikeInfo?.dislikes;
    }
  }

  void clearCurrentDislikeCount() {
    _currentDislikeCount.value = null;
  }

  /// -- Specify [newSortType] to force refresh. otherwise fetches next.
  /// -- returns wether a new comment result is assigned or not. use to revert ui actions.
  Future<bool> updateCurrentComments(String videoId, {CommentsSortType? newSortType, bool initial = false}) async {
    final commentRes = _currentComments.value;
    if (commentRes == null) return false;
    if (initial == false && commentRes.canFetchNext == false) return false;

    bool fetchedSuccessfully = false;
    if (initial == false && commentRes.canFetchNext && newSortType == null) {
      _isLoadingMoreComments.value = true;
      final didFetch = await commentRes.fetchNext();
      if (didFetch) _currentComments.refresh();
      _isLoadingMoreComments.value = false;
    } else {
      // -- fetch initial.
      _isLoadingInitialComments.value = true;
      final initialContinuation = newSortType == null ? _initialCommentsContinuation : commentRes.sorters[newSortType] ?? _initialCommentsContinuation;
      if (initialContinuation != null) {
        final newRes = await YoutubeInfoController.comment.fetchComments(
          videoId: videoId,
          continuationToken: initialContinuation,
          details: ExecuteDetails.kForceRequest,
        );
        if (newRes != null && _canSafelyModifyMetadata(videoId)) {
          fetchedSuccessfully = true;
          _currentComments.value = newRes;
          _isCurrentCommentsFromCache.value = false;
        }
      }
      _isLoadingInitialComments.value = false;
    }
    return fetchedSuccessfully;
  }
}

enum _RelatedVideosType {
  personalizedRelatedVideos,
  nonPersonalizedRelatedVideos,
  personalizedMixVideos,
  nonPersonalizedMixVideos,
  ;

  bool get isUserPersonalized => switch (this) {
    .personalizedRelatedVideos || .personalizedMixVideos => true,
    .nonPersonalizedRelatedVideos || .nonPersonalizedMixVideos => false,
  };

  bool get isRelatedVideoVideos => switch (this) {
    .personalizedRelatedVideos || .nonPersonalizedRelatedVideos => true,
    .personalizedMixVideos || .nonPersonalizedMixVideos => false,
  };

  bool get isMixVideos => switch (this) {
    .personalizedMixVideos || .nonPersonalizedMixVideos => true,
    .personalizedRelatedVideos || .nonPersonalizedRelatedVideos => false,
  };
}
