// Copyright (c) 2022, 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:convert';

import 'package:analysis_server/src/analytics/percentile_calculator.dart';
import 'package:analysis_server/src/plugin/plugin_isolate.dart';
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:path/path.dart' as path;

/// Data about the plugins associated with the context roots.
class PluginData {
  /// The number of times that plugin information has been recorded.
  int recordCount = 0;

  /// A table mapping the IDs of running "new" plugin isolates to various
  /// percentile-based analytics for each plugin.
  Map<String, PluginDataPerIsolate> counts = {};

  /// A table mapping the IDs of running legacy plugins to the percentile-based
  /// analytics regarding the number of context roots associated with each of
  /// the plugins.
  Map<String, PercentileCalculator> usageCounts = {};

  String get usageCountData {
    return json.encode({
      'recordCount': recordCount,
      'rootCounts': _encodeUsageCounts(),
    });
  }

  /// Records data about the plugins that are currently running, via info from
  /// [pluginManager].
  Future<void> recordPlugins(PluginManager pluginManager) async {
    recordCount++;
    for (var isolate in pluginManager.legacyPluginIsolates) {
      usageCounts
          .putIfAbsent(isolate.safePluginId, () => PercentileCalculator())
          .addValue(isolate.contextRoots.length);
    }

    // Record a data point for each context root with _no_ configured new
    // plugins.
    for (var contextRootPath in pluginManager.contextRootsWithNoPlugins) {
      String pluginEntrypointPath;
      try {
        // Plugin analytics are keyed to safe plugin IDs, which are derived from
        // the context root path.
        pluginEntrypointPath = pluginManager.pluginStateFolderPath(
          contextRootPath,
        );
      } on PluginException {
        // No valid state location on this file system; don't log this data.
        continue;
      }
      var safePluginId = pluginEntrypointPath.asSafePluginId;
      counts.putIfAbsent(
        safePluginId,
        () => PluginDataPerIsolate(pluginCount: 0),
      );
    }

    for (var isolate in pluginManager.newPluginIsolates) {
      var details = await isolate.requestDetails();
      if (details == null) continue;

      var pluginDataPerIsolate = counts.putIfAbsent(
        isolate.safePluginId,
        () => PluginDataPerIsolate(pluginCount: details.plugins.length),
      );

      for (var plugin in details.plugins) {
        pluginDataPerIsolate.lintRuleCounts.addValue(plugin.lintRules.length);
        pluginDataPerIsolate.warningRuleCounts.addValue(
          plugin.warningRules.length,
        );
        pluginDataPerIsolate.fixCounts.addValue(plugin.fixes.length);
        pluginDataPerIsolate.assistCounts.addValue(plugin.assists.length);
      }
    }
  }

  /// Returns an encoding of the [usageCounts].
  Map<String, Object> _encodeUsageCounts() => {
    for (var entry in usageCounts.entries) entry.key: entry.value.toJson(),
  };
}

/// Plugin data (for "new" plugins) for a given plugin isolate.
///
/// Only one plugin isolate runs for a given analysis context.
class PluginDataPerIsolate {
  /// The number of running plugins.
  final int pluginCount;

  /// The percentile-based analytics regarding the number of lint rules which
  /// are registered in each plugin.
  PercentileCalculator lintRuleCounts = PercentileCalculator();

  /// The percentile-based analytics regarding the number of warning rules which
  /// are registered in each plugin.
  PercentileCalculator warningRuleCounts = PercentileCalculator();

  /// The percentile-based analytics regarding the number of quick fixes which
  /// are registered in each plugin.
  PercentileCalculator fixCounts = PercentileCalculator();

  /// The percentile-based analytics regarding the number of quick assists which
  /// are registered in each plugin.
  PercentileCalculator assistCounts = PercentileCalculator();

  new({required this.pluginCount});
}

extension on String {
  String get asSafePluginId {
    var components = path.split(this);
    if (components.contains('.pub-cache')) {
      var index = components.lastIndexOf('analyzer_plugin');
      if (index > 2 &&
          components[index - 1] == 'tools' &&
          components[index - 3] == 'pub.dev') {
        return components[index - 2];
      }
    }
    return 'unknown';
  }
}

extension PluginIsolateExtension on PluginIsolate {
  /// An ID for this plugin that doesn't contain any PII.
  ///
  /// If the plugin is installed in the pub cache and hosted on `pub.dev`, then
  /// the returned name will be the name and version of the containing package
  /// as listed on `pub.dev`. If not, then it might be an internal name so we
  /// default to 'unknown'.
  String get safePluginId => pluginId.asSafePluginId;
}
