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

import 'package:devtools_shared/devtools_shared.dart' show DtdInfo;
import 'package:path/path.dart' as path;

import 'utils.dart';

Future<DtdInfo?> startDtd({
  required bool machineMode,
  required bool printDtdUri,
}) async {
  final sdkPath = File(Platform.resolvedExecutable).parent.parent.path;
  final dtdSnapshot = path.absolute(
    sdkPath,
    'bin',
    'snapshots',
    'dart_tooling_daemon.dart.snapshot',
  );

  final completer = Completer<DtdInfo?>();
  void completeForError() => completer.complete(null);

  final exitPort = ReceivePort()
    ..listen((_) {
      completeForError();
    });
  final errorPort = ReceivePort()
    ..listen((_) {
      completeForError();
    });
  final receivePort = ReceivePort()
    ..listen((message) {
      try {
        // [message] is a JSON encoded String from package:dtd_impl.
        final json = jsonDecode(message) as Map<String, Object?>;
        if (json
            case {
              'tooling_daemon_details': {
                'uri': String uri,
                'trusted_client_secret': String secret,
              }
            }) {
          if (printDtdUri || machineMode) {
            DevToolsUtils.printOutput(
              'Serving the Dart Tooling Daemon at $uri',
              {
                'event': 'server.dtdStarted',
                'params': {'uri': uri},
              },
              machineMode: machineMode,
            );
          }
          completer.complete(DtdInfo(Uri.parse(uri), secret: secret));
        }
      } catch (_) {
        completeForError();
      }
    });

  try {
    await Isolate.spawnUri(
      Uri.file(dtdSnapshot),
      ['--machine'],
      receivePort.sendPort,
      onExit: exitPort.sendPort,
      onError: errorPort.sendPort,
    );
  } catch (_, __) {
    completeForError();
  }

  final result = await completer.future.timeout(
    const Duration(seconds: 5),
    onTimeout: () => null,
  );
  receivePort.close();
  errorPort.close();
  exitPort.close();
  return result;
}
