// Copyright (c) 2021, 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:analysis_server/lsp_protocol/protocol.dart';
import 'package:analyzer/src/test_utilities/platform.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:test/test.dart';

import '../utils/test_code_extensions.dart';
import 'server_abstract.dart';

mixin CompletionTestMixin on AbstractLspAnalysisServerTest {
  /// The last set of completion results fetched.
  List<CompletionItem> completionResults = [];

  int sortTextSorter(CompletionItem item1, CompletionItem item2) =>
      (item1.sortText == item2.sortText)
      ? item1.label.compareTo(item2.label)
      : (item1.sortText ?? item1.label).compareTo(
          item2.sortText ?? item2.label,
        );

  Future<String?> verifyCompletions(
    Uri fileUri,
    String content, {
    required List<String> expectCompletions,
    String? applyEditsFor,
    bool resolve = false,
    String? expectedContent,
    String? expectedContentIfInserting,
    bool verifyInsertReplaceRanges = false,
    bool openCloseFile = true,
    bool expectNoAdditionalItems = false,
  }) async {
    // If verifyInsertReplaceRanges is true, we need both expected contents.
    assert(
      !verifyInsertReplaceRanges ||
          (expectedContent != null && expectedContentIfInserting != null),
    );

    var code = TestCode.parseNormalized(content);
    if (expectedContent != null) {
      expectedContent = normalizeNewlinesForPlatform(expectedContent);
    }
    if (expectedContentIfInserting != null) {
      expectedContentIfInserting = normalizeNewlinesForPlatform(
        expectedContentIfInserting,
      );
    }

    if (!initialized) {
      setCompletionItemSnippetSupport();
      if (verifyInsertReplaceRanges) {
        setCompletionItemInsertReplaceSupport();
      }
      await initialize();
    }

    if (openCloseFile) {
      await openFile(fileUri, code.code);
    }
    completionResults = await getCompletion(fileUri, code.position.position);
    if (openCloseFile) {
      await closeFile(fileUri);
    }

    var sortedResults =
        completionResults
            // Filter to those we expect (unless `expectNoAdditionalItems` which
            // indicates the test wants to ensure no additional unmatched items)
            .where(
              (r) =>
                  expectNoAdditionalItems ||
                  expectCompletions.contains(r.label),
            )
            .toList()
          // Sort using sortText as a client would.
          ..sort(sortTextSorter);

    expect(sortedResults.map((item) => item.label), equals(expectCompletions));

    // Check the edits apply correctly.
    if (applyEditsFor != null) {
      var item = completionResults.singleWhere((c) => c.label == applyEditsFor);
      var insertFormat = item.insertTextFormat;

      if (resolve) {
        item = await resolveCompletion(item);
      }

      String updatedContent;
      if (verifyInsertReplaceRanges &&
          expectedContent != expectedContentIfInserting) {
        // Replacing.
        updatedContent = applyTextEdits(code.code, [
          textEditForReplace(item.textEdit!),
        ]);
        expect(
          withCaret(updatedContent, insertFormat),
          equals(expectedContent),
        );

        // Inserting.
        var inserted = applyTextEdits(code.code, [
          textEditForInsert(item.textEdit!),
        ]);
        expect(
          withCaret(inserted, insertFormat),
          equals(expectedContentIfInserting),
        );
      } else {
        updatedContent = applyTextEdits(code.code, [
          toTextEdit(item.textEdit!),
        ]);
        if (expectedContent != null) {
          expect(
            withCaret(updatedContent, insertFormat),
            equals(expectedContent),
          );
        }
      }
      return updatedContent;
    }

    return null;
  }

  /// Replaces the LSP snippet placeholder '$0' with '^' for easier verifying
  /// of the cursor position in completions.
  String withCaret(String contents, InsertTextFormat? format) =>
      format == InsertTextFormat.Snippet
      ? contents.replaceFirst(r'$0', '^')
      : contents;
}
