// Copyright (c) 2023, 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:async';
import 'dart:convert';
import 'dart:io';

import 'package:path/path.dart' as p;
import 'package:shelf/shelf.dart' as shelf;
import 'package:test_api/backend.dart' show StackTraceMapper, SuitePlatform;
import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
import 'package:web_socket_channel/web_socket_channel.dart';

/// The shared interface for all compiler support libraries.
abstract class CompilerSupport {
  /// The global test runner configuration.
  final Configuration config;

  /// The default template path.
  final String defaultTemplatePath;

  CompilerSupport(this.config, this.defaultTemplatePath);

  /// The URL at which this compiler serves its tests.
  ///
  /// Each compiler serves its tests under a different directory.
  Uri get serverUrl;

  /// Compiles [dartPath] using [suiteConfig] for [platform].
  ///
  /// [dartPath] is the path to the original `.dart` test suite, relative to the
  /// package root.
  Future<void> compileSuite(
    String dartPath,
    SuiteConfiguration suiteConfig,
    SuitePlatform platform,
  );

  /// Retrieves a stack trace mapper for [dartPath] if available.
  ///
  /// [dartPath] is the path to the original `.dart` test suite, relative to the
  /// package root.
  StackTraceMapper? stackTraceMapperForPath(String dartPath);

  /// Returns the eventual URI for the web socket, as well as the channel itself
  /// once the connection is established.
  (Uri uri, Future<WebSocketChannel> socket) get webSocket;

  /// Closes down anything necessary for this implementation.
  Future<void> close();

  /// A handler that serves html wrapper files used to bootstrap tests.
  shelf.Response htmlWrapperHandler(shelf.Request request);
}

mixin JsHtmlWrapper on CompilerSupport {
  @override
  shelf.Response htmlWrapperHandler(shelf.Request request) {
    var path = p.fromUri(request.url);

    if (path.endsWith('.html')) {
      var test = p.setExtension(path, '.dart');
      var scriptBase = htmlEscape.convert(p.basename(test));
      var link = '<link rel="x-dart-test" href="$scriptBase">';
      var testName = htmlEscape.convert(test);
      var template = config.customHtmlTemplatePath ?? defaultTemplatePath;
      var contents = File(template).readAsStringSync();
      var processedContents = contents
          // Checked during loading phase that there is only one {{testScript}} placeholder.
          .replaceFirst('{{testScript}}', link)
          .replaceAll('{{testName}}', testName);
      return shelf.Response.ok(
        processedContents,
        headers: {'Content-Type': 'text/html'},
      );
    }

    return shelf.Response.notFound('Not found.');
  }
}

mixin WasmHtmlWrapper on CompilerSupport {
  @override
  shelf.Response htmlWrapperHandler(shelf.Request request) {
    var path = p.fromUri(request.url);

    if (path.endsWith('.html')) {
      var test = '${p.withoutExtension(path)}.dart';
      var scriptBase = htmlEscape.convert(p.basename(test));
      var link = '<link rel="x-dart-test" href="$scriptBase">';
      var testName = htmlEscape.convert(test);
      var template = config.customHtmlTemplatePath ?? defaultTemplatePath;
      var contents = File(template).readAsStringSync();
      var jsRuntime = p.basename('$test.browser_test.dart.mjs');
      var wasmData =
          '<data id="WasmBootstrapInfo" '
          'data-wasmurl="${p.basename('$test.browser_test.dart.wasm')}" '
          'data-jsruntimeurl="$jsRuntime"></data>';
      var processedContents = contents
          // Checked during loading phase that there is only one {{testScript}} placeholder.
          .replaceFirst('{{testScript}}', '$link\n$wasmData')
          .replaceAll('{{testName}}', testName);
      return shelf.Response.ok(
        processedContents,
        headers: {'Content-Type': 'text/html'},
      );
    }

    return shelf.Response.notFound('Not found.');
  }
}
