// 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:async';

import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;

import 'client.dart';
import 'dds_impl.dart';
import 'rpc_error_codes.dart';

/// A helper class which handles `evaluate` and `evaluateInFrame` calls by
/// potentially forwarding compilation requests to an external compilation
/// service like Flutter Tools.
class ExpressionEvaluator {
  ExpressionEvaluator(this.dds);

  Future<Map<String, dynamic>> execute(json_rpc.Parameters parameters) async {
    DartDevelopmentServiceClient? externalClient =
        dds.clientManager.findFirstClientThatHandlesService(
      'compileExpression',
    );
    // If no compilation service is registered, just forward to the VM service.
    if (externalClient == null) {
      return (await dds.vmServiceClient.sendRequest(
        parameters.method,
        parameters.value,
      )) as Map<String, dynamic>;
    }

    final isolateId = parameters['isolateId'].asString;
    final expression = parameters['expression'].asString;
    Map<String, dynamic> buildScopeResponse;

    try {
      buildScopeResponse = await _buildScope(parameters);
    } on json_rpc.RpcException catch (e) {
      throw RpcErrorCodes.buildRpcException(
        RpcErrorCodes.kExpressionCompilationError,
        data: e.data,
      );
    }
    String kernelBase64;
    try {
      kernelBase64 =
          await _compileExpression(isolateId, expression, buildScopeResponse);
    } on json_rpc.RpcException catch (e) {
      throw RpcErrorCodes.buildRpcException(
        RpcErrorCodes.kExpressionCompilationError,
        data: e.data,
      );
    }
    return await _evaluateCompiledExpression(
        parameters, isolateId, kernelBase64);
  }

  Future<Map<String, dynamic>> _buildScope(
      json_rpc.Parameters parameters) async {
    final params = _setupParams(parameters);
    params['isolateId'] = parameters['isolateId'].asString;
    if (parameters['scope'].asMapOr({}).isNotEmpty) {
      params['scope'] = parameters['scope'].asMap;
    }
    return (await dds.vmServiceClient.sendRequest(
      '_buildExpressionEvaluationScope',
      params,
    )) as Map<String, dynamic>;
  }

  Future<String> _compileExpression(String isolateId, String expression,
      Map<String, dynamic> buildScopeResponseResult) async {
    DartDevelopmentServiceClient? externalClient =
        dds.clientManager.findFirstClientThatHandlesService(
      'compileExpression',
    );
    if (externalClient == null) {
      throw RpcErrorCodes.buildRpcException(
          RpcErrorCodes.kExpressionCompilationError,
          data: 'compileExpression service disappeared.');
    }

    final compileParams = <String, dynamic>{
      'isolateId': isolateId,
      'expression': expression,
      'definitions': buildScopeResponseResult['param_names'],
      'definitionTypes': buildScopeResponseResult['param_types'],
      'typeDefinitions': buildScopeResponseResult['type_params_names'],
      'typeBounds': buildScopeResponseResult['type_params_bounds'],
      'typeDefaults': buildScopeResponseResult['type_params_defaults'],
      'libraryUri': buildScopeResponseResult['libraryUri'],
      'klass': buildScopeResponseResult['klass'],
      'method': buildScopeResponseResult['method'],
      'tokenPos': buildScopeResponseResult['tokenPos'],
      'isStatic': buildScopeResponseResult['isStatic'],
    };

    final klass = buildScopeResponseResult['klass'];
    if (klass != null) {
      compileParams['klass'] = klass;
    }
    final scriptUri = buildScopeResponseResult['scriptUri'];
    if (scriptUri != null) {
      compileParams['scriptUri'] = scriptUri;
    }
    try {
      return (await externalClient.sendRequest(
        'compileExpression',
        compileParams,
      ))['kernelBytes'];
    } on json_rpc.RpcException catch (e) {
      throw RpcErrorCodes.buildRpcException(
        RpcErrorCodes.kExpressionCompilationError,
        data: e.data,
      );
    }
  }

  Future<Map<String, dynamic>> _evaluateCompiledExpression(
    json_rpc.Parameters parameters,
    String isolateId,
    String kernelBase64,
  ) async {
    final params = _setupParams(parameters);
    params['isolateId'] = isolateId;
    params['kernelBytes'] = kernelBase64;
    params['disableBreakpoints'] =
        parameters['disableBreakpoints'].asBoolOr(false);
    if (parameters['scope'].asMapOr({}).isNotEmpty) {
      params['scope'] = parameters['scope'].asMap;
    }
    if (parameters['idZoneId'].asStringOr('').isNotEmpty) {
      params['idZoneId'] = parameters['idZoneId'].asString;
    }
    return (await dds.vmServiceClient.sendRequest(
      '_evaluateCompiledExpression',
      params,
    )) as Map<String, dynamic>;
  }

  Map<String, dynamic> _setupParams(json_rpc.Parameters parameters) {
    if (parameters.method == 'evaluateInFrame') {
      return <String, dynamic>{
        'frameIndex': parameters['frameIndex'].asInt,
      };
    } else {
      assert(parameters.method == 'evaluate');
      return <String, dynamic>{
        'targetId': parameters['targetId'].asString,
      };
    }
  }

  final DartDevelopmentServiceImpl dds;
}
