// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi' show Abi;
import 'dart:io';

import 'package:code_assets/code_assets.dart';

final class Target implements Comparable<Target> {
  final Abi abi;

  const Target._(this.abi);

  factory Target.fromString(String target) => _stringToTarget[target]!;

  /// The [Target] corresponding the substring of [Platform.version]
  /// describing the [Target].
  ///
  /// The [Platform.version] strings are formatted as follows:
  /// `<version> (<date>) on "<Target>"`.
  factory Target.fromDartPlatform(String versionStringFull) {
    final split = versionStringFull.split('"');
    if (split.length < 2) {
      throw FormatException(
        "Unknown version from Platform.version '$versionStringFull'.",
      );
    }
    final versionString = split[1];
    final target = _dartVMstringToTarget[versionString];
    if (target == null) {
      throw FormatException(
        "Unknown ABI '$versionString' from Platform.version"
        " '$versionStringFull'.",
      );
    }
    return target;
  }

  factory Target.fromArchitectureAndOS(Architecture architecture, OS os) {
    for (final value in values) {
      if (value.os == os && value.architecture == architecture) {
        return value;
      }
    }
    throw ArgumentError(
      'Unsupported combination of OS and architecture: '
      "'${os}_$architecture'",
    );
  }

  static const androidArm = Target._(Abi.androidArm);
  static const androidArm64 = Target._(Abi.androidArm64);
  static const androidIA32 = Target._(Abi.androidIA32);
  static const androidX64 = Target._(Abi.androidX64);
  static const androidRiscv64 = Target._(Abi.androidRiscv64);
  static const fuchsiaArm64 = Target._(Abi.fuchsiaArm64);
  static const fuchsiaX64 = Target._(Abi.fuchsiaX64);
  static const iOSArm = Target._(Abi.iosArm);
  static const iOSArm64 = Target._(Abi.iosArm64);
  static const iOSX64 = Target._(Abi.iosX64);
  static const linuxArm = Target._(Abi.linuxArm);
  static const linuxArm64 = Target._(Abi.linuxArm64);
  static const linuxIA32 = Target._(Abi.linuxIA32);
  static const linuxRiscv32 = Target._(Abi.linuxRiscv32);
  static const linuxRiscv64 = Target._(Abi.linuxRiscv64);
  static const linuxX64 = Target._(Abi.linuxX64);
  static const macOSArm64 = Target._(Abi.macosArm64);
  static const macOSX64 = Target._(Abi.macosX64);
  static const windowsArm64 = Target._(Abi.windowsArm64);
  static const windowsIA32 = Target._(Abi.windowsIA32);
  static const windowsX64 = Target._(Abi.windowsX64);

  static const Set<Target> values = {
    androidArm,
    androidArm64,
    androidIA32,
    androidX64,
    androidRiscv64,
    fuchsiaArm64,
    fuchsiaX64,
    iOSArm,
    iOSArm64,
    iOSX64,
    linuxArm,
    linuxArm64,
    linuxIA32,
    linuxRiscv32,
    linuxRiscv64,
    linuxX64,
    macOSArm64,
    macOSX64,
    windowsArm64,
    windowsIA32,
    windowsX64,
  };

  /// Mapping from strings as used in [Target.toString] to [Target]s.
  static final Map<String, Target> _stringToTarget = Map.fromEntries(
    Target.values.map((target) => MapEntry(target.toString(), target)),
  );

  /// Mapping from lowercased strings as used in [Platform.version] to
  /// [Target]s.
  static final Map<String, Target> _dartVMstringToTarget = Map.fromEntries(
    Target.values.map((target) => MapEntry(target.dartVMToString(), target)),
  );

  /// The current [Target].
  ///
  /// Read from the [Platform.version] string.
  static final Target current = Target.fromDartPlatform(Platform.version);

  Architecture get architecture => Architecture.fromAbi(abi);

  OS get os => {
    Abi.androidArm: OS.android,
    Abi.androidArm64: OS.android,
    Abi.androidIA32: OS.android,
    Abi.androidX64: OS.android,
    Abi.androidRiscv64: OS.android,
    Abi.fuchsiaArm64: OS.fuchsia,
    Abi.fuchsiaX64: OS.fuchsia,
    Abi.iosArm: OS.iOS,
    Abi.iosArm64: OS.iOS,
    Abi.iosX64: OS.iOS,
    Abi.linuxArm: OS.linux,
    Abi.linuxArm64: OS.linux,
    Abi.linuxIA32: OS.linux,
    Abi.linuxRiscv32: OS.linux,
    Abi.linuxRiscv64: OS.linux,
    Abi.linuxX64: OS.linux,
    Abi.macosArm64: OS.macOS,
    Abi.macosX64: OS.macOS,
    Abi.windowsArm64: OS.windows,
    Abi.windowsIA32: OS.windows,
    Abi.windowsX64: OS.windows,
  }[abi]!;

  @override
  String toString() => dartVMToString();

  /// As used in [Platform.version].
  String dartVMToString() => '${os.name}_${architecture.name}';

  /// Compares `this` to [other].
  ///
  /// If [other] is also an [Target], consistent with sorting on [toString].
  @override
  int compareTo(Target other) => toString().compareTo(other.toString());

  /// A list of supported target [Target]s from this host [os].
  List<Target> supportedTargetTargets({
    Map<OS, List<OS>> osCrossCompilation = OS.osCrossCompilationDefault,
  }) => Target.values
      .where(
        (target) =>
            // Only valid cross compilation.
            osCrossCompilation[os]!.contains(target.os) &&
            // And no deprecated architectures.
            target != Target.iOSArm,
      )
      .sorted;
}

/// Common methods for manipulating iterables of [Target]s.
extension on Iterable<Target> {
  /// The [Target]s in `this` sorted by name alphabetically.
  List<Target> get sorted => [for (final target in this) target]..sort();
}
