content: // ignore_for_file: invalid_annotation_target
  import 'dart:convert';
  import 'dart:io';
  import 'dart:typed_data';
  
  import 'package:fl_clash/clash/core.dart';
  import 'package:fl_clash/common/common.dart';
  import 'package:fl_clash/enum/enum.dart';
  import 'package:freezed_annotation/freezed_annotation.dart';
  
  import 'clash_config.dart';
  
  part 'generated/profile.freezed.dart';
  
  part 'generated/profile.g.dart';
  
  typedef SelectedMap = Map<String, String>;
  
  @freezed
  class SubscriptionInfo with _$SubscriptionInfo {
    const factory SubscriptionInfo({
      @Default(0) int upload,
      @Default(0) int download,
      @Default(0) int total,
      @Default(0) int expire,
    }) = _SubscriptionInfo;
  
    factory SubscriptionInfo.fromJson(Map<String, Object?> json) =>
        _$SubscriptionInfoFromJson(json);
  
    factory SubscriptionInfo.formHString(String? info) {
      if (info == null) return const SubscriptionInfo();
      final list = info.split(";");
      Map<String, int?> map = {};
      for (final i in list) {
        final keyValue = i.trim().split("=");
        map[keyValue[0]] = int.tryParse(keyValue[1]);
      }
      return SubscriptionInfo(
        upload: map["upload"] ?? 0,
        download: map["download"] ?? 0,
        total: map["total"] ?? 0,
        expire: map["expire"] ?? 0,
      );
    }
  }
  
  @freezed
  class Profile with _$Profile {
    const factory Profile({
      required String id,
      String? label,
      String? currentGroupName,
      @Default("") String url,
      DateTime? lastUpdateDate,
      required Duration autoUpdateDuration,
      SubscriptionInfo? subscriptionInfo,
      @Default(true) bool autoUpdate,
      @Default({}) SelectedMap selectedMap,
      @Default({}) Set<String> unfoldSet,
      @Default(OverrideData()) OverrideData overrideData,
      @JsonKey(includeToJson: false, includeFromJson: false)
      @Default(false)
      bool isUpdating,
    }) = _Profile;
  
    factory Profile.fromJson(Map<String, Object?> json) =>
        _$ProfileFromJson(json);
  
    factory Profile.normal({
      String? label,
      String url = '',
    }) {
      return Profile(
        label: label,
        url: url,
        id: DateTime.now().millisecondsSinceEpoch.toString(),
        autoUpdateDuration: defaultUpdateDuration,
      );
    }
  }
  
  @freezed
  class OverrideData with _$OverrideData {
    const factory OverrideData({
      @Default(false) bool enable,
      @Default(OverrideRule()) OverrideRule rule,
    }) = _OverrideData;
  
    factory OverrideData.fromJson(Map<String, Object?> json) =>
        _$OverrideDataFromJson(json);
  }
  
  extension OverrideDataExt on OverrideData {
    List<String> get runningRule {
      if (!enable) {
        return [];
      }
      return rule.rules.map((item) => item.value).toList();
    }
  }
  
  @freezed
  class OverrideRule with _$OverrideRule {
    const factory OverrideRule({
      @Default(OverrideRuleType.added) OverrideRuleType type,
      @Default([]) List<Rule> overrideRules,
      @Default([]) List<Rule> addedRules,
    }) = _OverrideRule;
  
    factory OverrideRule.fromJson(Map<String, Object?> json) =>
        _$OverrideRuleFromJson(json);
  }
  
  extension OverrideRuleExt on OverrideRule {
    List<Rule> get rules => switch (type == OverrideRuleType.override) {
          true => overrideRules,
          false => addedRules,
        };
  
    OverrideRule updateRules(List<Rule> Function(List<Rule> rules) builder) {
      if (type == OverrideRuleType.added) {
        return copyWith(addedRules: builder(addedRules));
      }
      return copyWith(overrideRules: builder(overrideRules));
    }
  }
  
  extension ProfilesExt on List<Profile> {
    Profile? getProfile(String? profileId) {
      final index = indexWhere((profile) => profile.id == profileId);
      return index == -1 ? null : this[index];
    }
  }
  
  extension ProfileExtension on Profile {
    ProfileType get type =>
        url.isEmpty == true ? ProfileType.file : ProfileType.url;
  
    bool get realAutoUpdate => url.isEmpty == true ? false : autoUpdate;
  
    Future<void> checkAndUpdate() async {
      final isExists = await check();
      if (!isExists) {
        if (url.isNotEmpty) {
          await update();
        }
      }
    }
  
    Future<bool> check() async {
      final profilePath = await appPath.getProfilePath(id);
      return await File(profilePath!).exists();
    }
  
    Future<File> getFile() async {
      final path = await appPath.getProfilePath(id);
      final file = File(path!);
      final isExists = await file.exists();
      if (!isExists) {
        await file.create(recursive: true);
      }
      return file;
    }
  
    Future<int> get profileLastModified async {
      final file = await getFile();
      return (await file.lastModified()).microsecondsSinceEpoch;
    }
  
    Future<Profile> update() async {
      final response = await request.getFileResponseForUrl(url);
      final disposition = response.headers.value("content-disposition");
      final userinfo = response.headers.value('subscription-userinfo');
      return await copyWith(
        label: label ?? utils.getFileNameForDisposition(disposition) ?? id,
        subscriptionInfo: SubscriptionInfo.formHString(userinfo),
      ).saveFile(response.data);
    }
  
    Future<Profile> saveFile(Uint8List bytes) async {
      final message = await clashCore.validateConfig(utf8.decode(bytes));
      if (message.isNotEmpty) {
        throw message;
      }
      final file = await getFile();
      await file.writeAsBytes(bytes);
      return copyWith(lastUpdateDate: DateTime.now());
    }
  
    Future<Profile> saveFileWithString(String value) async {
      final message = await clashCore.validateConfig(value);
      if (message.isNotEmpty) {
        throw message;
      }
      final file = await getFile();
      await file.writeAsString(value);
      return copyWith(lastUpdateDate: DateTime.now());
    }
  }
  