// Copyright (c) 2025, 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:json_rpc_2/json_rpc_2.dart';

import '../constants.dart';
import '../dart_tooling_daemon.dart';
import '../response_types/response_types.dart';
import '../rpc_error_codes.dart';

/// Extension methods on the [DartToolingDaemon] that call the ConnectedApps
/// service.
extension ConnectedAppsExtension on DartToolingDaemon {
  /// Registers a new VM service connection at [uri].
  ///
  /// This is a privileged RPC that requires a [secret] to be called
  /// successfully. This [secret] is generated by DTD at startup and provided to
  /// the spawner of DTD only so that it is the only DTD client that can call
  /// protected methods. If [secret] is invalid, an [RpcException] with error
  /// code [RpcErrorCodes.kPermissionDenied] will be thrown.
  ///
  /// [exposedUri] is the URI for the VM service connection that has been
  /// exposed to the user/client machine if the backend VM service is running in
  /// a different location (for example, an editor running in the user's browser
  /// with the backend on a remote server).
  ///
  /// Code that runs on the user/client machine (such as DevTools and DevTools
  /// extensions) should prefer this URI (if provided) whereas code that also
  /// runs on the backend (such as the debug adapter) should always use [uri].
  ///
  /// [exposedUri] will be null or identical to [uri] in environments where
  /// there is no exposing to do (for example, an editor running locally on the
  /// same machine that the VM service is running).
  ///
  /// [name] is the human-readable name for this VM service connection as
  /// defined by the DTD client calling this method (e.g. 'Flutter - Pixel 5').
  /// This is optional and may be null if the DTD client registering the VM
  /// service does not have any useful naming information to provide.
  ///
  /// If a VM service connection cannot be established for [uri], an
  /// [RpcException] with error code [RpcErrorCodes.kConnectionFailed] will be
  /// thrown.
  ///
  /// When this method is called, a
  /// [ConnectedAppServiceConstants.vmServiceRegistered] event will be sent over
  /// the [ConnectedAppServiceConstants.serviceName] stream.
  Future<Success> registerVmService({
    required String uri,
    required String secret,
    String? exposedUri,
    String? name,
  }) {
    return _callOnConnectedAppService<Success>(
      ConnectedAppServiceConstants.registerVmService,
      params: {
        DtdParameters.uri: uri,
        DtdParameters.secret: secret,
        if (exposedUri != null) DtdParameters.exposedUri: exposedUri,
        if (name != null) DtdParameters.name: name,
      },
      parseResponse: Success.fromDTDResponse,
    );
  }

  /// Unregisters the VM service connection at [uri], if it exists.
  ///
  /// If the VM service at [uri] was not already registered, this method does
  /// nothing.
  ///
  /// This is a privileged RPC that requires a [secret] to be called
  /// successfully. This [secret] is generated by DTD at startup and provided to
  /// the spawner of DTD only so that it is the only DTD client that can call
  /// protected methods. If [secret] is invalid, an [RpcException] with error
  /// code [RpcErrorCodes.kPermissionDenied] will be thrown.
  ///
  /// When this method is called, a
  /// [ConnectedAppServiceConstants.vmServiceUnregistered] event will be sent
  /// over the [ConnectedAppServiceConstants.serviceName] stream.
  Future<Success> unregisterVmService({
    required String uri,
    required String secret,
  }) {
    return _callOnConnectedAppService<Success>(
      ConnectedAppServiceConstants.unregisterVmService,
      params: {DtdParameters.uri: uri, DtdParameters.secret: secret},
      parseResponse: Success.fromDTDResponse,
    );
  }

  /// Returns a response containing information about the set of VM service
  /// connections this DTD instance is aware of.
  Future<VmServicesResponse> getVmServices() {
    return _callOnConnectedAppService<VmServicesResponse>(
      ConnectedAppServiceConstants.getVmServices,
      parseResponse: VmServicesResponse.fromDTDResponse,
    );
  }

  /// The stream of VM service update events.
  ///
  /// Events sent over this stream will be of kind 'VmServiceRegistered'
  /// or 'VmServiceUnregistered' and will contain the VM service URI they are
  /// associated with.
  ///
  /// A listener added to this [Stream] should be immediately followed by a
  /// call to `streamListen(kConnectedAppServiceName)`, which will allow the
  /// listener to start receiving events on this stream. Any events received
  /// before the listener is added and `streamListen` is called will be dropeed.
  Stream<DTDEvent> onVmServiceUpdate() {
    return onEvent(ConnectedAppServiceConstants.serviceName);
  }

  Future<T> _callOnConnectedAppService<T>(
    String methodName, {
    Map<String, Object> params = const {},
    required T Function(DTDResponse) parseResponse,
  }) async {
    final response = await call(
      ConnectedAppServiceConstants.serviceName,
      methodName,
      params: params,
    );
    return parseResponse(response);
  }
}
