// 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:async';
import 'dart:convert';
import 'dart:io';

import 'package:process/process.dart';

import 'base/common.dart';
import 'base/logger.dart';

/// Polls the flutter tool for details about the environment and project and exposes it as
/// a mapping of String keys to values.
///
/// This class is based on the `flutter analyze --suggestions --machine` flutter_tools command
/// which dumps various variables as JSON.
class FlutterToolsEnvironment {
  /// Constructs a tools environment out of a mapping of Strings to Object values.
  ///
  /// Each key is the String URI-style description of a value in the Flutter tool
  /// and is mapped to a String or boolean value. The mapping should align with the
  /// JSON output of `flutter analyze --suggestions --machine`.
  FlutterToolsEnvironment({
    required Map<String, Object?> mapping,
  }) : _mapping = mapping;

  /// Creates a FlutterToolsEnvironment instance by calling `flutter analyze --suggestions --machine`
  /// and parsing its output.
  static Future<FlutterToolsEnvironment> initializeFlutterToolsEnvironment(
      ProcessManager processManager, Logger logger) async {
    final ProcessResult result = await processManager
        .run(<String>['flutter', 'analyze', '--suggestions', '--machine']);
    if (result.exitCode != 0) {
      if ((result.stderr as String).contains(
          'The "--machine" flag is only valid with the "--version" flag.')) {
        logger.printError(
            'The migrate tool is only compatible with flutter tools 3.4.0 or newer (git hash: 21861423f25ad03c2fdb33854b53f195bc117cb3).');
      }
      throwToolExit(
          'Flutter tool exited while running `flutter analyze --suggestions --machine` with: ${result.stderr}');
    }
    String commandOutput = (result.stdout as String).trim();
    Map<String, Object?> mapping = <String, Object?>{};
    // minimally validate basic JSON format and trim away any accidental logging before.
    if (commandOutput.contains(RegExp(r'[\s\S]*{[\s\S]+}[\s\S]*'))) {
      commandOutput = commandOutput.substring(commandOutput.indexOf('{'));
      mapping = jsonDecode(commandOutput.replaceAll(r'\', r'\\'))
          as Map<String, Object?>;
    }
    return FlutterToolsEnvironment(mapping: mapping);
  }

  final Map<String, Object?> _mapping;

  Object? operator [](String key) {
    if (_mapping.containsKey(key)) {
      return _mapping[key];
    }
    return null;
  }

  /// Returns the String stored at the key and null if
  /// the key does not exist or is not a String.
  String? getString(String key) {
    final Object? value = _mapping[key];
    return value is String? ? value : null;
  }

  /// Returns the bool stored at the key and null if
  /// the key does not exist or is not a bool.
  bool? getBool(String key) {
    final Object? value = _mapping[key];
    return value is bool? ? value : null;
  }

  bool containsKey(String key) {
    return _mapping.containsKey(key);
  }
}