content: // Copyright 2013 The Flutter Authors. 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:convert';
  import 'dart:io' as io;
  
  import 'package:file/file.dart';
  
  import 'core.dart';
  import 'process_runner.dart';
  
  const String _xcodeBuildCommand = 'xcodebuild';
  const String _xcRunCommand = 'xcrun';
  
  /// A utility class for interacting with the installed version of Xcode.
  class Xcode {
    /// Creates an instance that runs commands with the given [processRunner].
    ///
    /// If [log] is true, commands run by this instance will long various status
    /// messages.
    Xcode({
      this.processRunner = const ProcessRunner(),
      this.log = false,
    });
  
    /// The [ProcessRunner] used to run commands. Overridable for testing.
    final ProcessRunner processRunner;
  
    /// Whether or not to log when running commands.
    final bool log;
  
    /// Runs an `xcodebuild` in [directory] with the given parameters.
    Future<int> runXcodeBuild(
      Directory directory, {
      List<String> actions = const <String>['build'],
      required String workspace,
      required String scheme,
      String? configuration,
      List<String> extraFlags = const <String>[],
    }) {
      final List<String> args = <String>[
        _xcodeBuildCommand,
        ...actions,
        if (workspace != null) ...<String>['-workspace', workspace],
        if (scheme != null) ...<String>['-scheme', scheme],
        if (configuration != null) ...<String>['-configuration', configuration],
        ...extraFlags,
      ];
      final String completeTestCommand = '$_xcRunCommand ${args.join(' ')}';
      if (log) {
        print(completeTestCommand);
      }
      return processRunner.runAndStream(_xcRunCommand, args,
          workingDir: directory);
    }
  
    /// Returns true if [project], which should be an .xcodeproj directory,
    /// contains a target called [target], false if it does not, and null if the
    /// check fails (e.g., if [project] is not an Xcode project).
    Future<bool?> projectHasTarget(Directory project, String target) async {
      final io.ProcessResult result =
          await processRunner.run(_xcRunCommand, <String>[
        _xcodeBuildCommand,
        '-list',
        '-json',
        '-project',
        project.path,
      ]);
      if (result.exitCode != 0) {
        return null;
      }
      Map<String, dynamic>? projectInfo;
      try {
        projectInfo = (jsonDecode(result.stdout as String)
            as Map<String, dynamic>)['project'] as Map<String, dynamic>?;
      } on FormatException {
        return null;
      }
      if (projectInfo == null) {
        return null;
      }
      final List<String>? targets =
          (projectInfo['targets'] as List<dynamic>?)?.cast<String>();
      return targets?.contains(target) ?? false;
    }
  
    /// Returns the newest available simulator (highest OS version, with ties
    /// broken in favor of newest device), if any.
    Future<String?> findBestAvailableIphoneSimulator() async {
      final List<String> findSimulatorsArguments = <String>[
        'simctl',
        'list',
        'devices',
        'runtimes',
        'available',
        '--json',
      ];
      final String findSimulatorCompleteCommand =
          '$_xcRunCommand ${findSimulatorsArguments.join(' ')}';
      if (log) {
        print('Looking for available simulators...');
        print(findSimulatorCompleteCommand);
      }
      final io.ProcessResult findSimulatorsResult =
          await processRunner.run(_xcRunCommand, findSimulatorsArguments);
      if (findSimulatorsResult.exitCode != 0) {
        if (log) {
          printError(
              'Error occurred while running "$findSimulatorCompleteCommand":\n'
              '${findSimulatorsResult.stderr}');
        }
        return null;
      }
      final Map<String, dynamic> simulatorListJson =
          jsonDecode(findSimulatorsResult.stdout as String)
              as Map<String, dynamic>;
      final List<Map<String, dynamic>> runtimes =
          (simulatorListJson['runtimes'] as List<dynamic>)
              .cast<Map<String, dynamic>>();
      final Map<String, Object> devices =
          (simulatorListJson['devices'] as Map<String, dynamic>)
              .cast<String, Object>();
      if (runtimes.isEmpty || devices.isEmpty) {
        return null;
      }
      String? id;
      // Looking for runtimes, trying to find one with highest OS version.
      for (final Map<String, dynamic> rawRuntimeMap in runtimes.reversed) {
        final Map<String, Object> runtimeMap =
            rawRuntimeMap.cast<String, Object>();
        if ((runtimeMap['name'] as String?)?.contains('iOS') != true) {
          continue;
        }
        final String? runtimeID = runtimeMap['identifier'] as String?;
        if (runtimeID == null) {
          continue;
        }
        final List<Map<String, dynamic>>? devicesForRuntime =
            (devices[runtimeID] as List<dynamic>?)?.cast<Map<String, dynamic>>();
        if (devicesForRuntime == null || devicesForRuntime.isEmpty) {
          continue;
        }
        // Looking for runtimes, trying to find latest version of device.
        for (final Map<String, dynamic> rawDevice in devicesForRuntime.reversed) {
          final Map<String, Object> device = rawDevice.cast<String, Object>();
          id = device['udid'] as String?;
          if (id == null) {
            continue;
          }
          if (log) {
            print('device selected: $device');
          }
          return id;
        }
      }
      return null;
    }
  }
  