// Copyright (c) 2015, 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 'package:source_map_stack_trace/source_map_stack_trace.dart' as mapper;
import 'package:source_maps/source_maps.dart';
import 'package:test_api/backend.dart' show StackTraceMapper;

/// A class for mapping JS stack traces to Dart stack traces using source maps.
class JSStackTraceMapper implements StackTraceMapper {
  /// The parsed source map.
  ///
  /// This is initialized lazily in `mapStackTrace()`.
  Mapping? _mapping;

  /// The same package resolution information as was passed to dart2js.
  final Map<String, Uri>? _packageMap;

  /// The URL of the SDK root from which dart2js loaded its sources.
  final Uri? _sdkRoot;

  /// The contents of the source map.
  final String _mapContents;

  /// The URL of the source map.
  final Uri? _mapUrl;

  JSStackTraceMapper(this._mapContents,
      {Uri? mapUrl, Map<String, Uri>? packageMap, Uri? sdkRoot})
      : _mapUrl = mapUrl,
        _packageMap = packageMap,
        _sdkRoot = sdkRoot;

  /// Converts [trace] into a Dart stack trace.
  @override
  StackTrace mapStackTrace(StackTrace trace) {
    var mapping = _mapping ??= parseExtended(_mapContents, mapUrl: _mapUrl);
    return mapper.mapStackTrace(mapping, trace,
        packageMap: _packageMap, sdkRoot: _sdkRoot);
  }

  /// Returns a Map representation which is suitable for JSON serialization.
  @override
  Map<String, dynamic> serialize() {
    return {
      'mapContents': _mapContents,
      'sdkRoot': _sdkRoot?.toString(),
      'packageConfigMap': _serializePackageConfigMap(_packageMap),
      'mapUrl': _mapUrl?.toString(),
    };
  }

  /// Returns a [StackTraceMapper] contained in the provided serialized
  /// representation.
  static StackTraceMapper? deserialize(Map? serialized) {
    if (serialized == null) return null;
    var deserialized = _deserializePackageConfigMap(
        (serialized['packageConfigMap'] as Map).cast<String, String>());

    return JSStackTraceMapper(serialized['mapContents'] as String,
        sdkRoot: Uri.parse(serialized['sdkRoot'] as String),
        packageMap: deserialized,
        mapUrl: Uri.parse(serialized['mapUrl'] as String));
  }

  /// Converts a [packageConfigMap] into a format suitable for JSON
  /// serialization.
  static Map<String, String>? _serializePackageConfigMap(
      Map<String, Uri>? packageConfigMap) {
    if (packageConfigMap == null) return null;
    return packageConfigMap.map((key, value) => MapEntry(key, '$value'));
  }

  /// Converts a serialized package config map into a format suitable for
  /// the [PackageResolver]
  static Map<String, Uri>? _deserializePackageConfigMap(
      Map<String, String>? serialized) {
    if (serialized == null) return null;
    return serialized.map((key, value) => MapEntry(key, Uri.parse(value)));
  }
}
