content: import 'dart:async';
  import 'dart:convert';
  import 'dart:ffi';
  import 'dart:io';
  import 'dart:isolate';
  import 'dart:ui';
  
  import 'package:ffi/ffi.dart';
  import 'package:fl_clash/common/constant.dart';
  import 'package:fl_clash/enum/enum.dart';
  import 'package:fl_clash/models/models.dart';
  import 'package:fl_clash/plugins/service.dart';
  import 'package:fl_clash/state.dart';
  
  import 'generated/clash_ffi.dart';
  import 'interface.dart';
  
  class ClashLib extends ClashHandlerInterface with AndroidClashInterface {
    static ClashLib? _instance;
    Completer<bool> _canSendCompleter = Completer();
    SendPort? sendPort;
    final receiverPort = ReceivePort();
  
    ClashLib._internal() {
      _initService();
    }
  
    @override
    preload() {
      return _canSendCompleter.future;
    }
  
    _initService() async {
      await service?.destroy();
      _registerMainPort(receiverPort.sendPort);
      receiverPort.listen((message) {
        if (message is SendPort) {
          if (_canSendCompleter.isCompleted) {
            sendPort = null;
            _canSendCompleter = Completer();
          }
          sendPort = message;
          _canSendCompleter.complete(true);
        } else {
          handleResult(
            ActionResult.fromJson(json.decode(
              message,
            )),
          );
        }
      });
      await service?.init();
    }
  
    _registerMainPort(SendPort sendPort) {
      IsolateNameServer.removePortNameMapping(mainIsolate);
      IsolateNameServer.registerPortWithName(sendPort, mainIsolate);
    }
  
    factory ClashLib() {
      _instance ??= ClashLib._internal();
      return _instance!;
    }
  
    @override
    Future<bool> nextHandleResult(result, completer) async {
      switch (result.method) {
        case ActionMethod.setFdMap:
        case ActionMethod.setProcessMap:
        case ActionMethod.stopTun:
        case ActionMethod.updateDns:
          completer?.complete(result.data as bool);
          return true;
        case ActionMethod.getRunTime:
        case ActionMethod.startTun:
        case ActionMethod.getAndroidVpnOptions:
        case ActionMethod.getCurrentProfileName:
          completer?.complete(result.data as String);
          return true;
        default:
          return false;
      }
    }
  
    @override
    destroy() async {
      await service?.destroy();
      return true;
    }
  
    @override
    reStart() {
      _initService();
    }
  
    @override
    Future<bool> shutdown() async {
      await super.shutdown();
      destroy();
      return true;
    }
  
    @override
    sendMessage(String message) async {
      await _canSendCompleter.future;
      sendPort?.send(message);
    }
  
    @override
    Future<bool> setFdMap(int fd) {
      return invoke<bool>(
        method: ActionMethod.setFdMap,
        data: json.encode(fd),
      );
    }
  
    @override
    Future<bool> setProcessMap(item) {
      return invoke<bool>(
        method: ActionMethod.setProcessMap,
        data: item,
      );
    }
  
    // @override
    // Future<bool> stopTun() {
    //   return invoke<bool>(
    //     method: ActionMethod.stopTun,
    //   );
    // }
  
    @override
    Future<AndroidVpnOptions?> getAndroidVpnOptions() async {
      final res = await invoke<String>(
        method: ActionMethod.getAndroidVpnOptions,
      );
      if (res.isEmpty) {
        return null;
      }
      return AndroidVpnOptions.fromJson(json.decode(res));
    }
  
    @override
    Future<bool> updateDns(String value) {
      return invoke<bool>(
        method: ActionMethod.updateDns,
        data: value,
      );
    }
  
    @override
    Future<DateTime?> getRunTime() async {
      final runTimeString = await invoke<String>(
        method: ActionMethod.getRunTime,
      );
      if (runTimeString.isEmpty) {
        return null;
      }
      return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
    }
  
    @override
    Future<String> getCurrentProfileName() {
      return invoke<String>(
        method: ActionMethod.getCurrentProfileName,
      );
    }
  }
  
  class ClashLibHandler {
    static ClashLibHandler? _instance;
  
    late final ClashFFI clashFFI;
  
    late final DynamicLibrary lib;
  
    ClashLibHandler._internal() {
      lib = DynamicLibrary.open("libclash.so");
      clashFFI = ClashFFI(lib);
      clashFFI.initNativeApiBridge(
        NativeApi.initializeApiDLData,
      );
    }
  
    factory ClashLibHandler() {
      _instance ??= ClashLibHandler._internal();
      return _instance!;
    }
  
    Future<String> invokeAction(String actionParams) {
      final completer = Completer<String>();
      final receiver = ReceivePort();
      receiver.listen((message) {
        if (!completer.isCompleted) {
          completer.complete(message);
          receiver.close();
        }
      });
      final actionParamsChar = actionParams.toNativeUtf8().cast<Char>();
      clashFFI.invokeAction(
        actionParamsChar,
        receiver.sendPort.nativePort,
      );
      malloc.free(actionParamsChar);
      return completer.future;
    }
  
    attachMessagePort(int messagePort) {
      clashFFI.attachMessagePort(
        messagePort,
      );
    }
  
    updateDns(String dns) {
      final dnsChar = dns.toNativeUtf8().cast<Char>();
      clashFFI.updateDns(dnsChar);
      malloc.free(dnsChar);
    }
  
    setState(CoreState state) {
      final stateChar = json.encode(state).toNativeUtf8().cast<Char>();
      clashFFI.setState(stateChar);
      malloc.free(stateChar);
    }
  
    String getCurrentProfileName() {
      final currentProfileRaw = clashFFI.getCurrentProfileName();
      final currentProfile = currentProfileRaw.cast<Utf8>().toDartString();
      clashFFI.freeCString(currentProfileRaw);
      return currentProfile;
    }
  
    AndroidVpnOptions getAndroidVpnOptions() {
      final vpnOptionsRaw = clashFFI.getAndroidVpnOptions();
      final vpnOptions = json.decode(vpnOptionsRaw.cast<Utf8>().toDartString());
      clashFFI.freeCString(vpnOptionsRaw);
      return AndroidVpnOptions.fromJson(vpnOptions);
    }
  
    Traffic getTraffic() {
      final trafficRaw = clashFFI.getTraffic();
      final trafficString = trafficRaw.cast<Utf8>().toDartString();
      clashFFI.freeCString(trafficRaw);
      if (trafficString.isEmpty) {
        return Traffic();
      }
      return Traffic.fromMap(json.decode(trafficString));
    }
  
    Traffic getTotalTraffic(bool value) {
      final trafficRaw = clashFFI.getTotalTraffic();
      final trafficString = trafficRaw.cast<Utf8>().toDartString();
      clashFFI.freeCString(trafficRaw);
      if (trafficString.isEmpty) {
        return Traffic();
      }
      return Traffic.fromMap(json.decode(trafficString));
    }
  
    startListener() async {
      clashFFI.startListener();
      return true;
    }
  
    stopListener() async {
      clashFFI.stopListener();
      return true;
    }
  
    Future<String> quickStart(
      InitParams initParams,
      UpdateConfigParams updateConfigParams,
      CoreState state,
    ) {
      final completer = Completer<String>();
      final receiver = ReceivePort();
      receiver.listen((message) {
        if (!completer.isCompleted) {
          completer.complete(message);
          receiver.close();
        }
      });
      final params = json.encode(updateConfigParams);
      final initValue = json.encode(initParams);
      final stateParams = json.encode(state);
      final initParamsChar = initValue.toNativeUtf8().cast<Char>();
      final paramsChar = params.toNativeUtf8().cast<Char>();
      final stateParamsChar = stateParams.toNativeUtf8().cast<Char>();
      clashFFI.quickStart(
        initParamsChar,
        paramsChar,
        stateParamsChar,
        receiver.sendPort.nativePort,
      );
      malloc.free(initParamsChar);
      malloc.free(paramsChar);
      malloc.free(stateParamsChar);
      return completer.future;
    }
  
    DateTime? getRunTime() {
      final runTimeRaw = clashFFI.getRunTime();
      final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
      clashFFI.freeCString(runTimeRaw);
      if (runTimeString.isEmpty) return null;
      return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
    }
  }
  
  ClashLib? get clashLib =>
      Platform.isAndroid && !globalState.isService ? ClashLib() : null;
  