// Copyright (c) 2020, 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 'dart:io';

import 'package:dwds/src/debugging/metadata/provider.dart';
import 'package:dwds/src/loaders/require.dart';
import 'package:dwds/src/loaders/strategy.dart';
import 'package:dwds/src/readers/asset_reader.dart';
import 'package:dwds/src/services/expression_compiler.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:shelf/shelf.dart';

/// Provides a [RequireStrategy] suitable for use with `package:build_runner`.
class BuildRunnerRequireStrategyProvider {
  final _logger = Logger('BuildRunnerRequireStrategyProvider');

  final Handler _assetHandler;
  final ReloadConfiguration _configuration;
  final AssetReader _assetReader;
  final BuildSettings _buildSettings;
  final String? _packageConfigPath;

  late final RequireStrategy _requireStrategy = RequireStrategy(
    _configuration,
    _moduleProvider,
    _digestsProvider,
    _moduleForServerPath,
    _serverPathForModule,
    _sourceMapPathForModule,
    _serverPathForAppUri,
    _moduleInfoForProvider,
    _assetReader,
    _buildSettings,
    packageConfigPath: _packageConfigPath,
  );

  BuildRunnerRequireStrategyProvider(
    this._assetHandler,
    this._configuration,
    this._assetReader,
    this._buildSettings, {
    String? packageConfigPath,
  }) : _packageConfigPath = packageConfigPath;

  RequireStrategy get strategy => _requireStrategy;

  Future<Map<String, String>> _digestsProvider(
    MetadataProvider metadataProvider,
  ) async {
    final modules = await metadataProvider.modulePathToModule;

    final digestsPath = metadataProvider.entrypoint.replaceAll(
      '.dart.bootstrap.js',
      '.digests',
    );
    final response = await _assetHandler(
      Request('GET', Uri.parse('http://foo:0000/$digestsPath')),
    );
    if (response.statusCode != HttpStatus.ok) {
      throw StateError('Could not read digests at path: $digestsPath');
    }
    final body = await response.readAsString();
    final digests = json.decode(body) as Map<String, dynamic>;

    for (final key in digests.keys) {
      if (!modules.containsKey(key)) {
        _logger.warning('Digest key $key is not a module name.');
      }
    }

    return {
      for (final entry in digests.entries)
        if (modules.containsKey(entry.key))
          modules[entry.key]!: entry.value as String,
    };
  }

  Future<Map<String, String>> _moduleProvider(
    MetadataProvider metadataProvider,
  ) async => (await metadataProvider.moduleToModulePath).map(
    (key, value) =>
        MapEntry(key, stripTopLevelDirectory(removeJsExtension(value))),
  );

  Future<String?> _moduleForServerPath(
    MetadataProvider metadataProvider,
    String serverPath,
  ) async {
    final modulePathToModule = await metadataProvider.modulePathToModule;
    final relativePath = stripLeadingSlashes(serverPath);
    for (final e in modulePathToModule.entries) {
      if (stripTopLevelDirectory(e.key) == relativePath) {
        return e.value;
      }
    }
    return null;
  }

  Future<String?> _serverPathForModule(
    MetadataProvider metadataProvider,
    String module,
  ) async {
    final modulePath = (await metadataProvider.moduleToModulePath)[module];
    return modulePath == null ? null : stripTopLevelDirectory(modulePath);
  }

  Future<String?> _sourceMapPathForModule(
    MetadataProvider metadataProvider,
    String module,
  ) async {
    final sourceMapPath = (await metadataProvider.moduleToSourceMap)[module];
    return sourceMapPath == null ? null : stripTopLevelDirectory(sourceMapPath);
  }

  String? _serverPathForAppUri(String appUrl) {
    final appUri = Uri.parse(appUrl);
    if (appUri.isScheme('org-dartlang-app')) {
      // We skip the root from which we are serving.
      return appUri.pathSegments.skip(1).join('/');
    }
    if (appUri.isScheme('package')) {
      return '/packages/${appUri.path}';
    }
    return null;
  }

  Future<Map<String, ModuleInfo>> _moduleInfoForProvider(
    MetadataProvider metadataProvider,
  ) async {
    final modules = await metadataProvider.modules;
    final result = <String, ModuleInfo>{};
    for (final module in modules) {
      final serverPath = await _serverPathForModule(metadataProvider, module);
      if (serverPath == null) {
        _logger.warning('No module info found for module $module');
      } else {
        result[module] = ModuleInfo(
          // TODO: Save locations of full kernel files in ddc metadata.
          // Issue: https://github.com/dart-lang/sdk/issues/43684
          // TODO: Change these to URIs instead of paths when the SDK supports
          // it.
          p.setExtension(serverPath, '.full.dill'),
          p.setExtension(serverPath, '.dill'),
        );
      }
    }
    return result;
  }
}
