import 'dart:convert';

import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
    show ReplyInfo;
import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/browser_ua.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/login.dart';
import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/common/video/video_type.dart';
import 'package:PiliPlus/models/home/rcmd/result.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/models/pgc_lcf.dart';
import 'package:PiliPlus/models/video/play/url.dart';
import 'package:PiliPlus/models_new/pgc/pgc_rank/pgc_rank_item_model.dart';
import 'package:PiliPlus/models_new/popular/popular_precious/data.dart';
import 'package:PiliPlus/models_new/popular/popular_series_list/list.dart';
import 'package:PiliPlus/models_new/popular/popular_series_one/data.dart';
import 'package:PiliPlus/models_new/triple/pgc_triple.dart';
import 'package:PiliPlus/models_new/triple/ugc_triple.dart';
import 'package:PiliPlus/models_new/video/video_ai_conclusion/data.dart';
import 'package:PiliPlus/models_new/video/video_detail/data.dart';
import 'package:PiliPlus/models_new/video/video_detail/video_detail_response.dart';
import 'package:PiliPlus/models_new/video/video_note_list/data.dart';
import 'package:PiliPlus/models_new/video/video_play_info/data.dart';
import 'package:PiliPlus/models_new/video/video_relation/data.dart';
import 'package:PiliPlus/models_new/video/video_shot/data.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/app_sign.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/recommend_filter.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/utils/wbi_sign.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart' show compute;
import 'package:protobuf/protobuf.dart';

/// view层根据 status 判断渲染逻辑
abstract final class VideoHttp {
  static RegExp zoneRegExp = RegExp(Pref.banWordForZone, caseSensitive: false);
  static bool enableFilter = zoneRegExp.pattern.isNotEmpty;

  // 首页推荐视频
  static Future<LoadingState<List<RcmdVideoItemModel>>> rcmdVideoList({
    required int ps,
    required int freshIdx,
  }) async {
    final res = await Request().get(
      Api.recommendListWeb,
      queryParameters: await WbiSign.makSign({
        'version': 1,
        'feed_version': 'V8',
        'homepage_ver': 1,
        'ps': ps,
        'fresh_idx': freshIdx,
        'brush': freshIdx,
        'fresh_type': 4,
      }),
    );
    if (res.data['code'] == 0) {
      List<RcmdVideoItemModel> list = <RcmdVideoItemModel>[];
      for (final i in res.data['data']['item']) {
        //过滤掉live与ad，以及拉黑用户
        if (i['goto'] == 'av' &&
            (i['owner'] != null &&
                !GlobalData().blackMids.contains(i['owner']['mid']))) {
          RcmdVideoItemModel videoItem = RcmdVideoItemModel.fromJson(i);
          if (!RecommendFilter.filter(videoItem)) {
            list.add(videoItem);
          }
        }
      }
      return Success(list);
    } else {
      return Error(res.data['message']);
    }
  }

  // 添加额外的loginState变量模拟未登录状态
  static Future<LoadingState<List<RcmdVideoItemAppModel>>> rcmdVideoListApp({
    required int freshIdx,
  }) async {
    final params = {
      'build': 2001100,
      'c_locale': 'zh_CN',
      'channel': 'master',
      'column': 4,
      'device': 'pad',
      'device_name': 'android',
      'device_type': 0,
      'disable_rcmd': 0,
      'flush': 5,
      'fnval': 976,
      'fnver': 0,
      'force_host': 2, //使用https
      'fourk': 1,
      'guidance': 0,
      'https_url_req': 0,
      'idx': freshIdx,
      'mobi_app': 'android_hd',
      'network': 'wifi',
      'platform': 'android',
      'player_net': 1,
      'pull': freshIdx == 0 ? 'true' : 'false',
      'qn': 32,
      'recsys_mode': 0,
      's_locale': 'zh_CN',
      'splash_id': '',
      'statistics': Constants.statistics,
      'voice_balance': 0,
    };
    final res = await Request().get(
      Api.recommendListApp,
      queryParameters: params,
      options: Options(
        headers: {
          'buvid': LoginHttp.buvid,
          'fp_local':
              '1111111111111111111111111111111111111111111111111111111111111111',
          'fp_remote':
              '1111111111111111111111111111111111111111111111111111111111111111',
          'session_id': '11111111',
          'env': 'prod',
          'app-key': 'android_hd',
          'User-Agent': Constants.userAgent,
          'x-bili-trace-id': Constants.traceId,
          'x-bili-aurora-eid': '',
          'x-bili-aurora-zone': '',
          'bili-http-engine': 'cronet',
        },
      ),
    );
    if (res.data['code'] == 0) {
      List<RcmdVideoItemAppModel> list = <RcmdVideoItemAppModel>[];
      for (final i in res.data['data']['items']) {
        // 屏蔽推广和拉黑用户
        if (i['card_goto'] != 'ad_av' &&
            i['card_goto'] != 'ad_web_s' &&
            i['ad_info'] == null &&
            (i['args'] != null &&
                !GlobalData().blackMids.contains(i['args']['up_id']))) {
          if (enableFilter &&
              i['args']?['tname'] != null &&
              zoneRegExp.hasMatch(i['args']['tname'])) {
            continue;
          }
          RcmdVideoItemAppModel videoItem = RcmdVideoItemAppModel.fromJson(i);
          if (!RecommendFilter.filter(videoItem)) {
            list.add(videoItem);
          }
        }
      }
      return Success(list);
    } else {
      return Error(res.data['message']);
    }
  }

  // 最热视频
  static Future<LoadingState<List<HotVideoItemModel>>> hotVideoList({
    required int pn,
    required int ps,
  }) async {
    final res = await Request().get(
      Api.hotList,
      queryParameters: {'pn': pn, 'ps': ps},
    );
    if (res.data['code'] == 0) {
      List<HotVideoItemModel> list = <HotVideoItemModel>[];
      for (final i in res.data['data']['list']) {
        if (!GlobalData().blackMids.contains(i['owner']['mid']) &&
            !RecommendFilter.filterTitle(i['title']) &&
            !RecommendFilter.filterLikeRatio(
              i['stat']['like'],
              i['stat']['view'],
            )) {
          if (enableFilter &&
              i['tname'] != null &&
              zoneRegExp.hasMatch(i['tname'])) {
            continue;
          }
          list.add(HotVideoItemModel.fromJson(i));
        }
      }
      return Success(list);
    } else {
      return Error(res.data['message']);
    }
  }

  // 视频流
  @pragma('vm:notify-debugger-on-exception')
  static Future<LoadingState<PlayUrlModel>> videoUrl({
    int? avid,
    String? bvid,
    required int cid,
    int? qn,
    dynamic epid,
    dynamic seasonId,
    required bool tryLook,
    required VideoType videoType,
    String? language,
    bool voiceBalance = false,
  }) async {
    final dmImgStr = Utils.base64EncodeRandomString(16, 64);
    final dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
    final params = await WbiSign.makSign({
      'avid': ?avid,
      'bvid': ?bvid,
      'ep_id': ?epid,
      'season_id': ?seasonId,
      'cid': cid,
      'qn': qn ?? 80,
      // 获取所有格式的视频
      'fnval': 4048,
      'fourk': 1,
      'fnver': 0,
      'voice_balance': voiceBalance ? 1 : 0,
      'gaia_source': 'pre-load',
      'isGaiaAvoided': true,
      'web_location': 1315873,
      // 免登录查看1080p
      if (tryLook) 'try_look': 1,
      'dm_img_list': '[]',
      'dm_img_str': dmImgStr,
      'dm_cover_img_str': dmCoverImgStr,
      'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
      'cur_language': ?language,
    });

    try {
      final res = await Request().get(videoType.api, queryParameters: params);

      if (res.data['code'] == 0) {
        late PlayUrlModel data;
        switch (videoType) {
          case .ugc:
            data = PlayUrlModel.fromJson(res.data['data']);

          case .pgc:
            final result = res.data['result'];
            data = PlayUrlModel.fromJson(result['video_info'])
              ..lastPlayTime =
                  result['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress'];

          case .pugv:
            final result = res.data['data'];
            data = PlayUrlModel.fromJson(result)
              ..lastPlayTime =
                  result['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress'];
        }
        return Success(data);
      } else if (epid != null && videoType == .ugc) {
        return await videoUrl(
          avid: avid,
          bvid: bvid,
          cid: cid,
          qn: qn,
          epid: epid,
          seasonId: seasonId,
          tryLook: tryLook,
          videoType: .pgc,
        );
      }
      return Error(_parseVideoErr(res.data['code'], res.data['message']));
    } catch (e, s) {
      return Error('$e\n\n$s');
    }
  }

  static String _parseVideoErr(int? code, String? msg) {
    return switch (code) {
      -404 => '视频不存在或已被删除',
      87008 => '当前视频可能是专属视频，可能需包月充电观看($msg})',
      _ => '错误($code): $msg',
    };
  }

  // 视频信息 标题、简介
  static Future<LoadingState<VideoDetailData>> videoIntro({
    required String bvid,
  }) async {
    final res = await Request().get(
      Api.videoIntro,
      queryParameters: {'bvid': bvid},
    );
    VideoDetailResponse data = VideoDetailResponse.fromJson(res.data);
    if (data.code == 0) {
      return Success(data.data!);
    } else {
      return Error(data.message);
    }
  }

  static Future<LoadingState<VideoRelation>> videoRelation({
    required String bvid,
  }) async {
    final res = await Request().get(
      Api.videoRelation,
      queryParameters: {'aid': IdUtils.bv2av(bvid), 'bvid': bvid},
    );
    if (res.data['code'] == 0) {
      return Success(VideoRelation.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  // 相关视频
  static Future<LoadingState<List<HotVideoItemModel>?>> relatedVideoList({
    required String bvid,
  }) async {
    final res = await Request().get(
      Api.relatedList,
      queryParameters: {'bvid': bvid},
    );
    if (res.data['code'] == 0) {
      final items = (res.data['data'] as List?)?.map(
        (i) => HotVideoItemModel.fromJson(i),
      );
      final list = RecommendFilter.applyFilterToRelatedVideos
          ? items?.where((i) => !RecommendFilter.filterAll(i)).toList()
          : items?.toList();
      return Success(list);
    } else {
      return Error(res.data['message']);
    }
  }

  // 获取点赞/投币/收藏状态 pgc
  static Future<LoadingState<PgcLCF>> pgcLikeCoinFav({
    required Object epId,
  }) async {
    final res = await Request().get(
      Api.pgcLikeCoinFav,
      queryParameters: {'ep_id': epId},
    );
    if (res.data['code'] == 0) {
      return Success(PgcLCF.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  // 投币
  static Future<LoadingState<void>> coinVideo({
    required String bvid,
    required int multiply,
    int selectLike = 0,
  }) async {
    final res = await Request().post(
      Api.coinVideo,
      data: {
        'aid': IdUtils.bv2av(bvid).toString(),
        // 'bvid': bvid,
        'multiply': multiply.toString(),
        'select_like': selectLike.toString(),
        // 'csrf': Accounts.main.csrf,
      },
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
    if (res.data['code'] == 0) {
      return const Success(null);
    } else {
      return Error(res.data['message']);
    }
  }

  // 一键三连 pgc
  static Future<LoadingState<PgcTriple>> pgcTriple({
    required Object epId,
    Object? seasonId,
  }) async {
    final res = await Request().post(
      Api.pgcTriple,
      data: {'ep_id': epId, 'csrf': Accounts.main.csrf},
      options: Options(
        contentType: Headers.formUrlEncodedContentType,
        headers: {
          'origin': 'https://www.bilibili.com',
          'referer':
              'https://www.bilibili.com/bangumi/play/${seasonId == null ? "ep$epId" : "ss$seasonId"}',
          'user-agent': BrowserUa.pc,
        },
      ),
    );
    if (res.data['code'] == 0) {
      return Success(PgcTriple.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  // 一键三连
  static Future<LoadingState<UgcTriple>> ugcTriple({
    required String bvid,
  }) async {
    final res = await Request().post(
      Api.ugcTriple,
      data: {
        'aid': IdUtils.bv2av(bvid),
        'eab_x': 2,
        'ramval': 0,
        'source': 'web_normal',
        'ga': 1,
        'csrf': Accounts.main.csrf,
        'spmid': '333.788.0.0',
        'statistics': '{"appId":100,"platform":5}',
      },
      options: Options(
        contentType: Headers.formUrlEncodedContentType,
        headers: {
          'origin': 'https://www.bilibili.com',
          'referer': 'https://www.bilibili.com/video/$bvid',
          'user-agent': BrowserUa.pc,
        },
      ),
    );
    if (res.data['code'] == 0) {
      return Success(UgcTriple.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  // （取消）点赞
  static Future<LoadingState<String>> likeVideo({
    required String bvid,
    required bool type,
  }) async {
    final res = await Request().post(
      Api.likeVideo,
      data: {'aid': IdUtils.bv2av(bvid).toString(), 'like': type ? '0' : '1'},
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
    if (res.data['code'] == 0) {
      return Success(res.data['data']['toast']);
    } else {
      return Error(res.data['message']);
    }
  }

  // （取消）点踩
  static Future<LoadingState<void>> dislikeVideo({
    required String bvid,
    required bool type,
  }) async {
    if (Accounts.main.accessKey.isNullOrEmpty) {
      return const Error('请退出账号后重新登录');
    }
    final res = await Request().post(
      Api.dislikeVideo,
      data: {
        'aid': IdUtils.bv2av(bvid).toString(),
        'dislike': type ? '0' : '1',
      },
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
    if (res.data is! String && res.data['code'] == 0) {
      return const Success(null);
    } else {
      return Error(res.data is String ? res.data : res.data['message']);
    }
  }

  // 推送不感兴趣反馈
  static Future<LoadingState<void>> feedDislike({
    required String goto,
    required int id,
    int? reasonId,
    int? feedbackId,
  }) async {
    if (Accounts.get(AccountType.recommend).accessKey.isNullOrEmpty) {
      return const Error('请退出账号后重新登录');
    }
    assert((reasonId != null) ^ (feedbackId != null));
    final res = await Request().get(
      Api.feedDislike,
      queryParameters: {
        'goto': goto,
        'id': id,
        'reason_id': ?reasonId,
        'feedback_id': ?feedbackId,
        'build': 1,
        'mobi_app': 'android',
      },
    );
    if (res.data['code'] == 0) {
      return const Success(null);
    } else {
      return Error(res.data['message']);
    }
  }

  // 推送不感兴趣取消
  static Future<LoadingState<void>> feedDislikeCancel({
    required String goto,
    required int id,
    int? reasonId,
    int? feedbackId,
  }) async {
    if (Accounts.get(AccountType.recommend).accessKey.isNullOrEmpty) {
      return const Error('请退出账号后重新登录');
    }
    final res = await Request().get(
      Api.feedDislikeCancel,
      queryParameters: {
        'goto': goto,
        'id': id,
        'reason_id': ?reasonId,
        'feedback_id': ?feedbackId,
        'build': 1,
        'mobi_app': 'android',
      },
    );
    if (res.data['code'] == 0) {
      return const Success(null);
    } else {
      return Error(res.data['message']);
    }
  }

  // 发表评论 replyAdd

  // type	num	评论区类型代码	必要	类型代码见表
  // oid	num	目标评论区id	必要
  // root	num	根评论rpid	非必要	二级评论以上使用
  // parent	num	父评论rpid	非必要	二级评论同根评论id 大于二级评论为要回复的评论id
  // message	str	发送评论内容	必要	最大1000字符
  // plat	num	发送平台标识	非必要	1：web端 2：安卓客户端  3：ios客户端  4：wp客户端
  static Future<LoadingState<ReplyInfo?>> replyAdd({
    required int type,
    required int oid,
    required String message,
    int? root,
    int? parent,
    List? pictures,
    bool syncToDynamic = false,
    Map<String, int>? atNameToMid,
  }) async {
    final data = {
      'type': type,
      'oid': oid,
      if (root != null && root != 0) 'root': root,
      if (parent != null && parent != 0) 'parent': parent,
      'message': message,
      if (atNameToMid?.isNotEmpty == true)
        'at_name_to_mid': jsonEncode(atNameToMid), // {"name":uid}
      if (pictures != null) 'pictures': jsonEncode(pictures),
      if (syncToDynamic) 'sync_to_dynamic': 1,
      'csrf': Accounts.main.csrf,
    };
    final res = await Request().post(
      Api.replyAdd,
      data: data,
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
    if (res.data['code'] == 0) {
      try {
        final replyInfo = RequestUtils.replyCast(res.data['data']['reply']);
        GStorage.reply?.put(
          replyInfo.id.toString(),
          (replyInfo.deepCopy()
                ..unknownFields.clear()
                ..clearTrackInfo())
              .writeToBuffer(),
        );
        return Success(replyInfo);
      } catch (e, s) {
        Utils.reportError(e, s);
        return const Success(null);
      }
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<void>> replyDel({
    required int type, //replyType
    required int oid,
    required int rpid,
  }) async {
    final res = await Request().post(
      Api.replyDel,
      data: {
        'type': type, //type.index
        'oid': oid,
        'rpid': rpid,
        'csrf': Accounts.main.csrf,
      },
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
    if (res.data['code'] == 0) {
      GStorage.reply?.delete(rpid.toString());
      return const Success(null);
    } else {
      return const Error('请退出账号后重新登录');
    }
  }

  // 操作用户关系
  static Future<LoadingState<void>> relationMod({
    required int mid,
    required int act,
    required int reSrc,
  }) async {
    final res = await Request().post(
      Api.relationMod,
      queryParameters: {
        'statistics': '{"appId":100,"platform":5}',
        'x-bili-device-req-json':
            '{"platform":"web","device":"pc","spmid":"333.1387"}',
      },
      data: {
        'fid': mid,
        'act': act,
        're_src': reSrc,
        'gaia_source': 'web_main',
        'spmid': '333.1387',
        'extend_content': jsonEncode({
          "entity": "user",
          "entity_id": mid,
          'fp': BrowserUa.pc,
        }),
        'csrf': Accounts.main.csrf,
      },
      options: Options(
        contentType: Headers.formUrlEncodedContentType,
        headers: {
          'origin': 'https://space.bilibili.com',
          'referer': 'https://space.bilibili.com/$mid/dynamic',
          'user-agent': BrowserUa.pc,
        },
      ),
    );
    if (res.data['code'] == 0) {
      if (act == 5) {
        // block
        Pref.setBlackMid(mid);
      } else if (act == 6) {
        // unblock
        Pref.removeBlackMid(mid);
      }
      return const Success(null);
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<void> roomEntryAction({required Object roomId}) {
    return Request().post(
      Api.roomEntryAction,
      queryParameters: {'csrf': Accounts.heartbeat.csrf},
      data: {'room_id': roomId, 'platform': 'pc'},
    );
  }

  static Future<void> historyReport({
    required Object aid,
    required Object type,
  }) {
    return Request().post(
      Api.historyReport,
      data: {'aid': aid, 'type': type, 'csrf': Accounts.heartbeat.csrf},
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
  }

  // 视频播放进度
  static Future<void> heartBeat({
    Object? aid,
    Object? bvid,
    required Object cid,
    required Object progress,
    Object? epid,
    Object? seasonId,
    Object? subType,
    required VideoType videoType,
  }) {
    final isPugv = videoType == VideoType.pugv;
    return Request().post(
      Api.heartBeat,
      data: {
        if (isPugv) 'aid': ?aid else 'bvid': ?bvid,
        'cid': cid,
        'epid': ?epid,
        'sid': ?seasonId,
        'type': videoType.type,
        'sub_type': ?subType,
        'played_time': progress,
        'csrf': Accounts.heartbeat.csrf,
      },
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
  }

  static Future<void> medialistHistory({
    required int desc,
    required Object oid,
    required Object upperMid,
  }) {
    return Request().post(
      Api.mediaListHistory,
      data: {
        'desc': desc,
        'oid': oid,
        'upper_mid': upperMid,
        'csrf': Accounts.heartbeat.csrf,
      },
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
  }

  // 添加追番
  static Future<LoadingState<String>> pgcAdd({int? seasonId}) async {
    final res = await Request().post(
      Api.pgcAdd,
      data: {'season_id': seasonId, 'csrf': Accounts.main.csrf},
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
    if (res.data['code'] == 0) {
      return Success(res.data['result']['toast']);
    } else {
      return Error(res.data['message']);
    }
  }

  // 取消追番
  static Future<LoadingState<String>> pgcDel({int? seasonId}) async {
    final res = await Request().post(
      Api.pgcDel,
      data: {'season_id': seasonId, 'csrf': Accounts.main.csrf},
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
    if (res.data['code'] == 0) {
      return Success(res.data['result']['toast']);
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<String>> pgcUpdate({
    required String seasonId,
    required int status,
  }) async {
    final res = await Request().post(
      Api.pgcUpdate,
      data: {
        'season_id': seasonId,
        'status': status,
        'csrf': Accounts.main.csrf,
      },
      options: Options(contentType: Headers.formUrlEncodedContentType),
    );
    if (res.data['code'] == 0) {
      return Success(res.data['result']['toast']);
    } else {
      return Error(res.data['message']);
    }
  }

  // 查看视频同时在看人数
  static Future<LoadingState<String>> onlineTotal({
    int? aid,
    String? bvid,
    required int cid,
  }) async {
    assert(aid != null || bvid != null);
    final res = await Request().get(
      Api.onlineTotal,
      queryParameters: {'aid': aid, 'bvid': bvid, 'cid': cid},
    );
    if (res.data['code'] == 0) {
      return Success(res.data['data']['total']);
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<AiConclusionData>> aiConclusion({
    required String bvid,
    required int cid,
    int? upMid,
  }) async {
    final params = await WbiSign.makSign({
      'bvid': bvid,
      'cid': cid,
      'up_mid': ?upMid,
    });
    final res = await Request().get(Api.aiConclusion, queryParameters: params);
    final int? code = res.data['code'];
    if (code == 0) {
      final int? dataCode = res.data['data']?['code'];
      if (dataCode == 0) {
        return Success(AiConclusionData.fromJson(res.data['data']));
      } else {
        return Error(null, code: dataCode);
      }
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<PlayInfoData>> playInfo({
    String? aid,
    String? bvid,
    required int cid,
    dynamic seasonId,
    dynamic epId,
  }) async {
    assert(aid != null || bvid != null);
    final res = await Request().get(
      Api.playInfo,
      queryParameters: await WbiSign.makSign({
        'aid': ?aid,
        'bvid': ?bvid,
        'cid': cid,
        'season_id': ?seasonId,
        'ep_id': ?epId,
      }),
    );
    if (res.data['code'] == 0) {
      return Success(PlayInfoData.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  static String _subtitleTimecode(num seconds) {
    int h = seconds ~/ 3600;
    seconds %= 3600;
    int m = seconds ~/ 60;
    seconds %= 60;
    String sms = seconds.toStringAsFixed(3).padLeft(6, '0');
    return h == 0
        ? "${m.toString().padLeft(2, '0')}:$sms"
        : "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:$sms";
  }

  static String processList(List list) {
    final sb = StringBuffer('WEBVTT\n\n')
      ..writeAll(
        list.map(
          (item) =>
              '${_subtitleTimecode(item['from'])} --> ${_subtitleTimecode(item['to'])}\n${item['content'].trim()}',
        ),
        '\n\n',
      );
    return sb.toString();
  }

  static Future<String?> vttSubtitles(String subtitleUrl) async {
    final res = await Request().get("https:$subtitleUrl");
    if (res.data?['body'] case List list) {
      return compute<List, String>(processList, list);
    }
    return null;
  }

  static bool _canAddRank(Map i) {
    if (!GlobalData().blackMids.contains(i['owner']['mid']) &&
        !RecommendFilter.filterTitle(i['title']) &&
        !RecommendFilter.filterLikeRatio(
          i['stat']['like'],
          i['stat']['view'],
        )) {
      if (enableFilter &&
          i['tname'] != null &&
          zoneRegExp.hasMatch(i['tname'])) {
        return false;
      }
      return true;
    }
    return false;
  }

  // 视频排行
  static Future<LoadingState<List<HotVideoItemModel>>> getRankVideoList(
    int rid,
  ) async {
    final res = await Request().get(
      Api.getRankApi,
      queryParameters: await WbiSign.makSign({'rid': rid, 'type': 'all'}),
    );
    if (res.data['code'] == 0) {
      List<HotVideoItemModel> list = <HotVideoItemModel>[];
      for (final i in res.data['data']['list']) {
        if (_canAddRank(i)) {
          list.add(HotVideoItemModel.fromJson(i));
          // final List? others = i['others'];
          // if (others != null && others.isNotEmpty) {
          //   for (final j in others) {
          //     if (_canAddRank(j)) {
          //       list.add(HotVideoItemModel.fromJson(j));
          //     }
          //   }
          // }
        }
      }
      return Success(list);
    } else {
      return Error(res.data['message']);
    }
  }

  // pgc 排行
  static Future<LoadingState<List<PgcRankItemModel>?>> pgcRankList({
    int day = 3,
    required int seasonType,
  }) async {
    final res = await Request().get(
      Api.pgcRank,
      queryParameters: await WbiSign.makSign({
        'day': day,
        'season_type': seasonType,
      }),
    );
    if (res.data['code'] == 0) {
      return Success(
        (res.data['result']?['list'] as List?)
            ?.map((e) => PgcRankItemModel.fromJson(e))
            .toList(),
      );
    } else {
      return Error(res.data['message']);
    }
  }

  // pgc season 排行
  static Future<LoadingState<List<PgcRankItemModel>?>> pgcSeasonRankList({
    int day = 3,
    required int seasonType,
  }) async {
    final res = await Request().get(
      Api.pgcSeasonRank,
      queryParameters: await WbiSign.makSign({
        'day': day,
        'season_type': seasonType,
      }),
    );
    if (res.data['code'] == 0) {
      return Success(
        (res.data['data']?['list'] as List?)
            ?.map((e) => PgcRankItemModel.fromJson(e))
            .toList(),
      );
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<VideoNoteData>> getVideoNoteList({
    dynamic oid,
    dynamic uperMid,
    required int page,
  }) async {
    final res = await Request().get(
      Api.archiveNoteList,
      queryParameters: {
        'csrf': Accounts.main.csrf,
        'oid': oid,
        'oid_type': 0,
        'pn': page,
        'ps': 10,
        'uper_mid': ?uperMid,
      },
    );
    if (res.data['code'] == 0) {
      return Success(VideoNoteData.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<List<PopularSeriesListItem>?>>
  popularSeriesList() async {
    final res = await Request().get(
      Api.popularSeriesList,
      queryParameters: await WbiSign.makSign({'web_location': 333.934}),
    );
    if (res.data['code'] == 0) {
      return Success(
        (res.data['data']?['list'] as List<dynamic>?)
            ?.map(
              (e) => PopularSeriesListItem.fromJson(e as Map<String, dynamic>),
            )
            .toList(),
      );
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<PopularSeriesOneData>> popularSeriesOne({
    required int number,
  }) async {
    final res = await Request().get(
      Api.popularSeriesOne,
      queryParameters: await WbiSign.makSign({
        'number': number,
        'web_location': 333.934,
      }),
    );
    if (res.data['code'] == 0) {
      return Success(PopularSeriesOneData.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<PopularPreciousData>> popularPrecious({
    required int page,
  }) async {
    final res = await Request().get(
      Api.popularPrecious,
      queryParameters: await WbiSign.makSign({
        'page_size': 100,
        'page': page,
        'web_location': 333.934,
      }),
    );
    if (res.data['code'] == 0) {
      return Success(PopularPreciousData.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<PlayUrlModel>> tvPlayUrl({
    required int cid,
    required int objectId, // aid, epid
    required int playurlType, // ugc 1, pgc 2
    int? qn,
  }) async {
    final accessKey = Accounts.get(AccountType.video).accessKey;
    final params = {
      'access_key': ?accessKey,
      'actionKey': 'appkey',
      'cid': cid,
      'fourk': 1,
      'is_proj': 1,
      'mobile_access_key': ?accessKey,
      'object_id': objectId,
      'mobi_app': 'android',
      'platform': 'android',
      'playurl_type': playurlType,
      'protocol': 0,
      'qn': qn ?? 80,
    };
    AppSign.appSign(params);
    final res = await Request().get(Api.tvPlayUrl, queryParameters: params);
    if (res.data['code'] == 0) {
      return Success(PlayUrlModel.fromJson(res.data['data']));
    } else {
      return Error(res.data['message']);
    }
  }

  static Future<LoadingState<VideoShotData>> videoshot({
    required String bvid,
    required int cid,
  }) async {
    final res = await Request().get(
      Api.videoshot,
      queryParameters: {
        // 'aid': IdUtils.bv2av(_bvid),
        'bvid': bvid,
        'cid': cid,
        'index': 1,
      },
      options: Options(
        headers: {
          'user-agent': BrowserUa.pc,
          'referer': 'https://www.bilibili.com/video/$bvid',
        },
      ),
    );
    if (res.data['code'] == 0) {
      final data = VideoShotData.fromJson(res.data['data']);
      if (data.index.isNotEmpty) {
        return Success(data);
      } else {
        return const Error(null);
      }
    }
    return Error(res.data['message']);
  }
}
