// Copyright (c) 2019, 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:analysis_server/src/lsp/constants.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../shared/shared_code_actions_refactor_tests.dart';
import 'code_actions_mixin.dart';
import 'server_abstract.dart';

void main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(ExtractMethodRefactorCodeActionsTest);
    defineReflectiveTests(ExtractWidgetRefactorCodeActionsTest);
    defineReflectiveTests(ExtractVariableRefactorCodeActionsTest);
    defineReflectiveTests(InlineLocalVariableRefactorCodeActionsTest);
    defineReflectiveTests(InlineMethodRefactorCodeActionsTest);
    defineReflectiveTests(ConvertGetterToMethodCodeActionsTest);
    defineReflectiveTests(ConvertMethodToGetterCodeActionsTest);
  });
}

@reflectiveTest
class ConvertGetterToMethodCodeActionsTest extends RefactorCodeActionsTest
    with
        // Tests are defined in a shared mixin.
        SharedConvertGetterToMethodRefactorCodeActionsTests {}

@reflectiveTest
class ConvertMethodToGetterCodeActionsTest extends RefactorCodeActionsTest
    with
        // Tests are defined in a shared mixin.
        SharedConvertMethodToGetterRefactorCodeActionsTests {}

@reflectiveTest
class ExtractMethodRefactorCodeActionsTest extends RefactorCodeActionsTest
    with
        // Most tests are defined in a shared mixin.
        SharedExtractMethodRefactorCodeActionsTests {
  Future<void> test_progress_clientProvided() async {
    const content = '''
void f() {
  print('Test!');
  [!print('Test!');!]
}
''';
    const expectedContent = '''
void f() {
  print('Test!');
  newMethod();
}

void newMethod() {
  print('Test!');
}
''';

    // Expect begin/end progress updates without a create, since the
    // token was supplied by us (the client).
    expect(progressUpdates, emitsInOrder(['BEGIN', 'END']));

    await verifyCodeActionLiteralEdits(
      content,
      expectedContent,
      command: Commands.performRefactor,
      title: extractMethodTitle,
      commandWorkDoneToken: clientProvidedTestWorkDoneToken,
    );
  }

  Future<void> test_progress_notSupported() async {
    const content = '''
void f() {
  print('Test!');
  [!print('Test!');!]
}
''';
    const expectedContent = '''
void f() {
  print('Test!');
  newMethod();
}

void newMethod() {
  print('Test!');
}
''';

    var didGetProgressNotifications = false;
    progressUpdates.listen((_) => didGetProgressNotifications = true);

    await verifyCodeActionLiteralEdits(
      content,
      expectedContent,
      command: Commands.performRefactor,
      title: extractMethodTitle,
    );

    expect(didGetProgressNotifications, isFalse);
  }

  Future<void> test_progress_serverGenerated() async {
    const content = '''
void f() {
  print('Test!');
  [!print('Test!');!]
}
''';
    const expectedContent = '''
void f() {
  print('Test!');
  newMethod();
}

void newMethod() {
  print('Test!');
}
''';

    // Expect create/begin/end progress updates, because in this case the server
    // generates the token.
    expect(progressUpdates, emitsInOrder(['CREATE', 'BEGIN', 'END']));

    setWorkDoneProgressSupport();
    await verifyCodeActionLiteralEdits(
      content,
      expectedContent,
      command: Commands.performRefactor,
      title: extractMethodTitle,
    );
  }

  /// Test if the client does not call refactor.validate it still gets a
  /// sensible `showMessage` call and not a failed request.
  Future<void> test_validLocation_failsInitialValidation_noValidation() async {
    const content = '''
f() {
  var a = 0;
  doFoo([!() => print(a)!]);
  print(a);
}

void doFoo(void Function() a) => a();
''';

    var codeAction = await expectCodeActionLiteral(
      content,
      command: Commands.performRefactor,
      title: extractMethodTitle,
    );
    var command = codeAction.command!;

    // Call the refactor without any validation and expected an error message
    // without a request failure.
    var errorNotification = await expectErrorNotification(() async {
      var response = await executeCommand(
        Command(
          title: command.title,
          command: command.command,
          arguments: command.arguments,
        ),
      );
      expect(response, isNull);
    });
    expect(
      errorNotification.message,
      contains('Cannot extract the closure as a method'),
    );
  }
}

@reflectiveTest
class ExtractVariableRefactorCodeActionsTest extends RefactorCodeActionsTest
    with
        // Tests are defined in a shared mixin.
        SharedExtractVariableRefactorCodeActionsTests {}

@reflectiveTest
class ExtractWidgetRefactorCodeActionsTest extends RefactorCodeActionsTest
    with
        // Tests are defined in a shared mixin.
        SharedExtractWidgetRefactorCodeActionsTests {}

@reflectiveTest
class InlineLocalVariableRefactorCodeActionsTest extends RefactorCodeActionsTest
    with
        // Tests are defined in a shared mixin.
        SharedInlineLocalVariableRefactorCodeActionsTests {}

@reflectiveTest
class InlineMethodRefactorCodeActionsTest extends RefactorCodeActionsTest
    with
        // Tests are defined in a shared mixin.
        SharedInlineMethodRefactorCodeActionsTests {}

abstract class RefactorCodeActionsTest extends AbstractLspAnalysisServerTest
    with
        LspSharedTestMixin,
        CodeActionsTestMixin,
        SharedRefactorCodeActionsTests {
  @override
  Future<T> handleRefactorAnywayPrompt<T>(
    Future<T> Function() f, {
    required String expectedMessage,
    required List<String> expectedActions,
    String? selectAction,
  }) async {
    return await handleExpectedRequest(
      Method.window_showMessageRequest,
      ShowMessageRequestParams.fromJson,
      f,
      handler: (ShowMessageRequestParams params) async {
        // Ensure the warning prompt is as expected.
        expect(params.type, equals(MessageType.Warning));
        expect(params.message, equals(expectedMessage));
        expect(params.actions, hasLength(2));
        expect(
          params.actions![0],
          equals(MessageActionItem(title: UserPromptActions.refactorAnyway)),
        );
        expect(
          params.actions![1],
          equals(MessageActionItem(title: UserPromptActions.cancel)),
        );

        // Respond to the request with the required action.
        return selectAction != null
            ? MessageActionItem(title: selectAction)
            : null;
      },
    );
  }

  @override
  void setUp() {
    super.setUp();

    setApplyEditSupport();
    setDocumentChangesSupport();
    setSupportedCodeActionKinds([CodeActionKind.Refactor]);
  }
}
