// Copyright (c) 2014, 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:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:test/test.dart';

import 'analysis_server_base.dart';

class AbstractCompletionDomainTest extends PubPackageAnalysisServerTest {
  // TODO(brianwilkerson): Merge this class and `CompletionTestCase`.
  late int completionOffset; // TODO(scheglov): remove it
  int? replacementOffset;
  late int replacementLength;
  List<CompletionSuggestion> suggestions = [];

  void assertHasResult(
    CompletionSuggestionKind kind,
    String completion, {
    bool isDeprecated = false,
    bool isPotential = false,
    int? selectionOffset,
    int? replacementOffset,
    int? replacementLength,
    ElementKind? elementKind,
  }) {
    CompletionSuggestion? cs;
    for (var s in suggestions) {
      if (elementKind != null && s.element?.kind != elementKind) {
        return;
      }
      if (s.completion == completion) {
        if (cs == null) {
          cs = s;
        } else {
          fail('expected exactly one $completion but found > 1');
        }
      }
    }
    if (cs == null) {
      var completions = suggestions.map((s) => s.completion).toList();

      var expectationText = '"$completion"';
      if (elementKind != null) {
        expectationText += ' ($elementKind)';
      }

      fail('expected $expectationText, but found\n $completions');
    }
    var suggestion = cs;
    expect(suggestion.kind, equals(kind));
    expect(suggestion.selectionOffset, selectionOffset ?? completion.length);
    expect(suggestion.selectionLength, equals(0));
    expect(suggestion.replacementOffset, equals(replacementOffset));
    expect(suggestion.replacementLength, equals(replacementLength));
    expect(suggestion.isDeprecated, equals(isDeprecated));
    expect(suggestion.isPotential, equals(isPotential));
  }

  void assertNoResult(String completion, {ElementKind? elementKind}) {
    if (suggestions.any(
      (cs) =>
          cs.completion == completion &&
          (elementKind == null || cs.element?.kind == elementKind),
    )) {
      fail('did not expect completion: $completion');
    }
  }

  void assertValidId(String id) {
    expect(id, isNotNull);
    expect(id.isNotEmpty, isTrue);
  }

  Future<void> getCodeSuggestions({
    required String path,
    required String content,
    int maxResults = 1 << 10,
  }) async {
    var code = TestCode.parseNormalized(content);
    var completionOffset = code.position.offset;

    newFile(path, code.code);

    return await getSuggestions(
      path: path,
      completionOffset: completionOffset,
      maxResults: maxResults,
    );
  }

  Future<void> getSuggestions({
    required String path,
    required int completionOffset,
    required int maxResults,
  }) async {
    var request = CompletionGetSuggestions2Params(
      path,
      completionOffset,
      maxResults,
    ).toRequest('0', clientUriConverter: server.uriConverter);

    var response = await handleSuccessfulRequest(request);
    var result = CompletionGetSuggestions2Result.fromResponse(
      response,
      clientUriConverter: server.uriConverter,
    );
    replacementOffset = result.replacementOffset;
    replacementLength = result.replacementLength;
    suggestions = result.suggestions;
  }

  Future<void> getTestCodeSuggestions(String content) {
    return getCodeSuggestions(path: testFile.path, content: content);
  }

  @override
  Future<void> processNotification(Notification notification) async {
    if (notification.event == serverNotificationError) {
      fail('server error: ${notification.toJson()}');
    }
  }

  @override
  Future<void> setUp() async {
    super.setUp();
    await setRoots(included: [workspaceRootPath], excluded: []);
  }
}
