// Copyright 2023 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.

// Code needs to match API from VmService.
// ignore_for_file: avoid-dynamic

import 'dart:async';

import 'package:vm_service/utils.dart';
import 'package:vm_service/vm_service.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import '../sse/sse_shim.dart';
import '../utils/utils.dart';

Future<T> _connectWithSse<T extends VmService>({
  required Uri uri,
  required void Function(Object?) onError,
  required Completer<void> finishedCompleter,
  required VmServiceFactory<T> serviceFactory,
}) {
  final serviceCompleter = Completer<T>();

  uri = uri.scheme == 'sse'
      ? uri.replace(scheme: 'http')
      : uri.replace(scheme: 'https');
  final client = SseClient('$uri', debugKey: 'DevToolsService');
  final stream = client.stream!.asBroadcastStream() as Stream<String>;
  final service = serviceFactory(
    inStream: stream,
    writeMessage: client.sink!.add,
    wsUri: uri.toString(),
  );

  unawaited(
    client.sink!.done.whenComplete(() async {
      finishedCompleter.complete();
      await service.dispose();
    }),
  );
  serviceCompleter.complete(service);

  unawaited(stream.drain<void>().catchError(onError));
  return serviceCompleter.future;
}

Future<T> _connectWithWebSocket<T extends VmService>({
  required Uri uri,
  required void Function(Object?) onError,
  required Completer<void> finishedCompleter,
  required VmServiceFactory<T> serviceFactory,
}) async {
  // Map the URI (which may be Observatory web app) to a WebSocket URI for
  // the VM service.
  uri = convertToWebSocketUrl(serviceProtocolUrl: uri);
  final ws = WebSocketChannel.connect(uri);
  final stream = ws.stream.handleError(onError);
  final service = serviceFactory(
    inStream: stream,
    writeMessage: (String message) {
      ws.sink.add(message);
    },
    wsUri: uri.toString(),
  );

  if (ws.closeCode != null) {
    onError(null);
    return service;
  }
  unawaited(
    ws.sink.done.then(
      (_) async {
        finishedCompleter.complete();
        await service.dispose();
      },
      onError: onError,
    ),
  );
  return service;
}

Future<T> connect<T extends VmService>({
  required Uri uri,
  required Completer<void> finishedCompleter,
  required VmServiceFactory<T> serviceFactory,
}) {
  final connectedCompleter = Completer<T>();

  void onError(Object? error) => connectedCompleter.safeCompleteError(error!);

  // Connects to a VM Service but does not verify the connection was fully
  // successful.
  Future<T> connectHelper() async {
    final useSse = uri.scheme == 'sse' || uri.scheme == 'sses';
    final T service = useSse
        ? await _connectWithSse<T>(
            uri: uri,
            onError: onError,
            finishedCompleter: finishedCompleter,
            serviceFactory: serviceFactory,
          )
        : await _connectWithWebSocket<T>(
            uri: uri,
            onError: onError,
            finishedCompleter: finishedCompleter,
            serviceFactory: serviceFactory,
          );
    // Verify that the VM is alive enough to actually get the version before
    // considering it successfully connected. Otherwise, VMService instances
    // that failed part way through the connection may appear to be connected.
    await service.getVersion();
    return service;
  }

  unawaited(connectHelper().then(
    (service) => connectedCompleter.safeComplete(service),
    onError: onError,
  ));
  unawaited(finishedCompleter.future.then((_) {
    // It is an error if we finish before we are connected.
    if (!connectedCompleter.isCompleted) {
      onError(null);
    }
  }));
  return connectedCompleter.future;
}
