content: // ignore_for_file: avoid_print
  
  import 'dart:convert';
  import 'dart:io';
  
  import 'package:args/command_runner.dart';
  import 'package:path/path.dart';
  import 'package:crypto/crypto.dart';
  
  enum Target {
    windows,
    linux,
    android,
    macos,
  }
  
  extension TargetExt on Target {
    String get os {
      if (this == Target.macos) {
        return "darwin";
      }
      return name;
    }
  
    bool get same {
      if (this == Target.android) {
        return true;
      }
      if (Platform.isWindows && this == Target.windows) {
        return true;
      }
      if (Platform.isLinux && this == Target.linux) {
        return true;
      }
      if (Platform.isMacOS && this == Target.macos) {
        return true;
      }
      return false;
    }
  
    String get dynamicLibExtensionName {
      final String extensionName;
      switch (this) {
        case Target.android || Target.linux:
          extensionName = ".so";
          break;
        case Target.windows:
          extensionName = ".dll";
          break;
        case Target.macos:
          extensionName = ".dylib";
          break;
      }
      return extensionName;
    }
  
    String get executableExtensionName {
      final String extensionName;
      switch (this) {
        case Target.windows:
          extensionName = ".exe";
          break;
        default:
          extensionName = "";
          break;
      }
      return extensionName;
    }
  }
  
  enum Mode { core, lib }
  
  enum Arch { amd64, arm64, arm }
  
  class BuildItem {
    Target target;
    Arch? arch;
    String? archName;
  
    BuildItem({
      required this.target,
      this.arch,
      this.archName,
    });
  
    @override
    String toString() {
      return 'BuildLibItem{target: $target, arch: $arch, archName: $archName}';
    }
  }
  
  class Build {
    static List<BuildItem> get buildItems => [
          BuildItem(
            target: Target.macos,
            arch: Arch.arm64,
          ),
          BuildItem(
            target: Target.macos,
            arch: Arch.amd64,
          ),
          BuildItem(
            target: Target.linux,
            arch: Arch.arm64,
          ),
          BuildItem(
            target: Target.linux,
            arch: Arch.amd64,
          ),
          BuildItem(
            target: Target.windows,
            arch: Arch.amd64,
          ),
          BuildItem(
            target: Target.windows,
            arch: Arch.arm64,
          ),
          BuildItem(
            target: Target.android,
            arch: Arch.arm,
            archName: 'armeabi-v7a',
          ),
          BuildItem(
            target: Target.android,
            arch: Arch.arm64,
            archName: 'arm64-v8a',
          ),
          BuildItem(
            target: Target.android,
            arch: Arch.amd64,
            archName: 'x86_64',
          ),
        ];
  
    static String get appName => "FlClash";
  
    static String get coreName => "FlClashCore";
  
    static String get libName => "libclash";
  
    static String get outDir => join(current, libName);
  
    static String get _coreDir => join(current, "core");
  
    static String get _servicesDir => join(current, "services", "helper");
  
    static String get distPath => join(current, "dist");
  
    static String _getCc(BuildItem buildItem) {
      final environment = Platform.environment;
      if (buildItem.target == Target.android) {
        final ndk = environment["ANDROID_NDK"];
        assert(ndk != null);
        final prebuiltDir =
            Directory(join(ndk!, "toolchains", "llvm", "prebuilt"));
        final prebuiltDirList = prebuiltDir.listSync();
        final map = {
          "armeabi-v7a": "armv7a-linux-androideabi21-clang",
          "arm64-v8a": "aarch64-linux-android21-clang",
          "x86": "i686-linux-android21-clang",
          "x86_64": "x86_64-linux-android21-clang"
        };
        return join(
          prebuiltDirList.first.path,
          "bin",
          map[buildItem.archName],
        );
      }
      return "gcc";
    }
  
    static get tags => "with_gvisor";
  
    static Future<void> exec(
      List<String> executable, {
      String? name,
      Map<String, String>? environment,
      String? workingDirectory,
      bool runInShell = true,
    }) async {
      if (name != null) print("run $name");
      final process = await Process.start(
        executable[0],
        executable.sublist(1),
        environment: environment,
        workingDirectory: workingDirectory,
        runInShell: runInShell,
      );
      process.stdout.listen((data) {
        print(utf8.decode(data));
      });
      process.stderr.listen((data) {
        print(utf8.decode(data));
      });
      final exitCode = await process.exitCode;
      if (exitCode != 0 && name != null) throw "$name error";
    }
  
    static Future<String> calcSha256(String filePath) async {
      final file = File(filePath);
      if (!await file.exists()) {
        throw "File not exists";
      }
      final stream = file.openRead();
      return sha256.convert(await stream.reduce((a, b) => a + b)).toString();
    }
  
    static Future<List<String>> buildCore({
      required Mode mode,
      required Target target,
      Arch? arch,
    }) async {
      final isLib = mode == Mode.lib;
  
      final items = buildItems.where(
        (element) {
          return element.target == target &&
              (arch == null ? true : element.arch == arch);
        },
      ).toList();
  
      final List<String> corePaths = [];
  
      for (final item in items) {
        final outFileDir = join(
          outDir,
          item.target.name,
          item.archName,
        );
  
        final file = File(outFileDir);
        if (file.existsSync()) {
          file.deleteSync(recursive: true);
        }
  
        final fileName = isLib
            ? "$libName${item.target.dynamicLibExtensionName}"
            : "$coreName${item.target.executableExtensionName}";
        final outPath = join(
          outFileDir,
          fileName,
        );
        corePaths.add(outPath);
  
        final Map<String, String> env = {};
        env["GOOS"] = item.target.os;
        if (item.arch != null) {
          env["GOARCH"] = item.arch!.name;
        }
        if (isLib) {
          env["CGO_ENABLED"] = "1";
          env["CC"] = _getCc(item);
          env["CFLAGS"] = "-O3 -Werror";
        } else {
          env["CGO_ENABLED"] = "0";
        }
  
        final execLines = [
          "go",
          "build",
          "-ldflags=-w -s",
          "-tags=$tags",
          if (isLib) "-buildmode=c-shared",
          "-o",
          outPath,
        ];
        await exec(
          execLines,
          name: "build core",
          environment: env,
          workingDirectory: _coreDir,
        );
      }
  
      return corePaths;
    }
  
    static buildHelper(Target target, String token) async {
      await exec(
        [
          "cargo",
          "build",
          "--release",
          "--features",
          "windows-service",
        ],
        environment: {
          "TOKEN": token,
        },
        name: "build helper",
        workingDirectory: _servicesDir,
      );
      final outPath = join(
        _servicesDir,
        "target",
        "release",
        "helper${target.executableExtensionName}",
      );
      final targetPath = join(
        outDir,
        target.name,
        "FlClashHelperService${target.executableExtensionName}",
      );
      await File(outPath).copy(targetPath);
    }
  
    static List<String> getExecutable(String command) {
      return command.split(" ");
    }
  
    static getDistributor() async {
      final distributorDir = join(
        current,
        "plugins",
        "flutter_distributor",
        "packages",
        "flutter_distributor",
      );
  
      await exec(
        name: "clean distributor",
        Build.getExecutable("flutter clean"),
        workingDirectory: distributorDir,
      );
      await exec(
        name: "upgrade distributor",
        Build.getExecutable("flutter pub upgrade"),
        workingDirectory: distributorDir,
      );
      await exec(
        name: "get distributor",
        ["dart", "pub", "global", "activate", "-s", "path", distributorDir],
      );
    }
  
    static copyFile(String sourceFilePath, String destinationFilePath) {
      final sourceFile = File(sourceFilePath);
      if (!sourceFile.existsSync()) {
        throw "SourceFilePath not exists";
      }
      final destinationFile = File(destinationFilePath);
      final destinationDirectory = destinationFile.parent;
      if (!destinationDirectory.existsSync()) {
        destinationDirectory.createSync(recursive: true);
      }
      try {
        sourceFile.copySync(destinationFilePath);
        print("File copied successfully!");
      } catch (e) {
        print("Failed to copy file: $e");
      }
    }
  }
  
  class BuildCommand extends Command {
    Target target;
  
    BuildCommand({
      required this.target,
    }) {
      if (target == Target.android || target == Target.linux) {
        argParser.addOption(
          "arch",
          valueHelp: arches.map((e) => e.name).join(','),
          help: 'The $name build desc',
        );
      } else {
        argParser.addOption(
          "arch",
          help: 'The $name build archName',
        );
      }
      argParser.addOption(
        "out",
        valueHelp: [
          if (target.same) "app",
          "core",
        ].join(','),
        help: 'The $name build arch',
      );
      argParser.addOption(
        "env",
        valueHelp: [
          "pre",
          "stable",
        ].join(','),
        help: 'The $name build env',
      );
    }
  
    @override
    String get description => "build $name application";
  
    @override
    String get name => target.name;
  
    List<Arch> get arches => Build.buildItems
        .where((element) => element.target == target && element.arch != null)
        .map((e) => e.arch!)
        .toList();
  
    _getLinuxDependencies(Arch arch) async {
      await Build.exec(
        Build.getExecutable("sudo apt update -y"),
      );
      await Build.exec(
        Build.getExecutable("sudo apt install -y ninja-build libgtk-3-dev"),
      );
      await Build.exec(
        Build.getExecutable("sudo apt install -y libayatana-appindicator3-dev"),
      );
      await Build.exec(
        Build.getExecutable("sudo apt-get install -y libkeybinder-3.0-dev"),
      );
      await Build.exec(
        Build.getExecutable("sudo apt install -y locate"),
      );
      if (arch == Arch.amd64) {
        await Build.exec(
          Build.getExecutable("sudo apt install -y rpm patchelf"),
        );
        await Build.exec(
          Build.getExecutable("sudo apt install -y libfuse2"),
        );
  
        final downloadName = arch == Arch.amd64 ? "x86_64" : "aarch64";
        await Build.exec(
          Build.getExecutable(
            "wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$downloadName.AppImage",
          ),
        );
        await Build.exec(
          Build.getExecutable(
            "chmod +x appimagetool",
          ),
        );
        await Build.exec(
          Build.getExecutable(
            "sudo mv appimagetool /usr/local/bin/",
          ),
        );
      }
    }
  
    _getMacosDependencies() async {
      await Build.exec(
        Build.getExecutable("npm install -g appdmg"),
      );
    }
  
    _buildDistributor({
      required Target target,
      required String targets,
      String args = '',
      required String env,
    }) async {
      await Build.getDistributor();
      await Build.exec(
        name: name,
        Build.getExecutable(
          "flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose$args --build-dart-define=APP_ENV=$env",
        ),
      );
    }
  
    Future<String?> get systemArch async {
      if (Platform.isWindows) {
        return Platform.environment["PROCESSOR_ARCHITECTURE"];
      } else if (Platform.isLinux || Platform.isMacOS) {
        final result = await Process.run('uname', ['-m']);
        return result.stdout.toString().trim();
      }
      return null;
    }
  
    @override
    Future<void> run() async {
      final mode = target == Target.android ? Mode.lib : Mode.core;
      final String out = argResults?["out"] ?? (target.same ? "app" : "core");
      final archName = argResults?["arch"];
      final env = argResults?["env"] ?? "pre";
      final currentArches =
          arches.where((element) => element.name == archName).toList();
      final arch = currentArches.isEmpty ? null : currentArches.first;
  
      if (arch == null && target != Target.android) {
        throw "Invalid arch parameter";
      }
  
      final corePaths = await Build.buildCore(
        target: target,
        arch: arch,
        mode: mode,
      );
  
      if (out != "app") {
        return;
      }
  
      switch (target) {
        case Target.windows:
          final token = target != Target.android
              ? await Build.calcSha256(corePaths.first)
              : null;
          Build.buildHelper(target, token!);
          _buildDistributor(
            target: target,
            targets: "exe,zip",
            args:
                " --description $archName --build-dart-define=CORE_SHA256=$token",
            env: env,
          );
          return;
        case Target.linux:
          final targetMap = {
            Arch.arm64: "linux-arm64",
            Arch.amd64: "linux-x64",
          };
          final targets = [
            "deb",
            if (arch == Arch.amd64) "appimage",
            if (arch == Arch.amd64) "rpm",
          ].join(",");
          final defaultTarget = targetMap[arch];
          await _getLinuxDependencies(arch!);
          _buildDistributor(
            target: target,
            targets: targets,
            args:
                " --description $archName --build-target-platform $defaultTarget",
            env: env,
          );
          return;
        case Target.android:
          final targetMap = {
            Arch.arm: "android-arm",
            Arch.arm64: "android-arm64",
            Arch.amd64: "android-x64",
          };
          final defaultArches = [Arch.arm, Arch.arm64, Arch.amd64];
          final defaultTargets = defaultArches
              .where((element) => arch == null ? true : element == arch)
              .map((e) => targetMap[e])
              .toList();
          _buildDistributor(
            target: target,
            targets: "apk",
            args:
                ",split-per-abi --build-target-platform ${defaultTargets.join(",")}",
            env: env,
          );
          return;
        case Target.macos:
          await _getMacosDependencies();
          _buildDistributor(
            target: target,
            targets: "dmg",
            args: " --description $archName",
            env: env,
          );
          return;
      }
    }
  }
  
  main(args) async {
    final runner = CommandRunner("setup", "build Application");
    runner.addCommand(BuildCommand(target: Target.android));
    runner.addCommand(BuildCommand(target: Target.linux));
    runner.addCommand(BuildCommand(target: Target.windows));
    runner.addCommand(BuildCommand(target: Target.macos));
    runner.run(args);
  }
  