// 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 '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/file_system/file_system.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../analysis_server_base.dart';

void main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(AnalysisNotificationAnalyzedFilesTest);
  });
}

@reflectiveTest
class AnalysisNotificationAnalyzedFilesTest
    extends PubPackageAnalysisServerTest {
  late List<String> analyzedFiles;
  bool analyzedFilesReceived = false;

  void assertHasFile(File file) {
    expect(analyzedFilesReceived, isTrue);
    expect(analyzedFiles, contains(file.path));
  }

  void assertHasNoFile(String filePath) {
    expect(analyzedFilesReceived, isTrue);
    expect(analyzedFiles, isNot(contains(filePath)));
  }

  Future<void> prepareAnalyzedFiles() async {
    await addGeneralAnalysisSubscription(GeneralAnalysisService.ANALYZED_FILES);
    await pumpEventQueue(times: 5000);
  }

  @override
  void processNotification(Notification notification) {
    if (notification.event == analysisNotificationAnalyzedFiles) {
      var params = AnalysisAnalyzedFilesParams.fromNotification(
        notification,
        clientUriConverter: server.uriConverter,
      );
      analyzedFilesReceived = true;
      analyzedFiles = params.directories;
    }
  }

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

  Future<void> test_afterAnalysis() async {
    addTestFile('''
class A {}
''');
    await waitForTasksFinished();
    await prepareAnalyzedFiles();
    assertHasFile(testFile);
  }

  Future<void> test_beforeAnalysis() async {
    addTestFile('''
class A {}
''');
    await prepareAnalyzedFiles();
    assertHasFile(testFile);
  }

  Future<void> test_beforeAnalysis_excludeYamlFiles() async {
    var yamlFile = newFile('$testPackageRootPath/sample.yaml', '');
    addTestFile('''
class A {}
''');
    await prepareAnalyzedFiles();
    assertHasFile(testFile);
    assertHasNoFile(yamlFile.path);
  }

  Future<void> test_insignificant_change() async {
    // Making a change that doesn't affect the set of reachable files should
    // not trigger the notification to be re-sent.
    addTestFile('class A {}');
    await prepareAnalyzedFiles();
    expect(analyzedFilesReceived, isTrue);

    analyzedFilesReceived = false;
    modifyTestFile('class B {}');
    await prepareAnalyzedFiles();
    expect(analyzedFilesReceived, isFalse);
  }

  Future<void> test_resubscribe_no_changes() async {
    // Unsubscribing and resubscribing should cause the notification to be
    // re-sent, even if nothing has changed.
    addTestFile('class A {}');
    await prepareAnalyzedFiles();
    expect(analyzedFilesReceived, isTrue);

    unsubscribeAnalyzedFiles();
    analyzedFilesReceived = false;

    await prepareAnalyzedFiles();
    expect(analyzedFilesReceived, isTrue);
    assertHasFile(testFile);
  }

  Future<void> test_significant_change() async {
    // Making a change that *does* affect the set of reachable files should
    // trigger the notification to be re-sent.
    addTestFile('class A {}');
    var foo = newFile('/foo.dart', 'library foo;');
    await prepareAnalyzedFiles();
    expect(analyzedFilesReceived, isTrue);

    analyzedFilesReceived = false;
    modifyTestFile('import "${toUriStr('/foo.dart')}";');
    await prepareAnalyzedFiles();
    assertHasFile(foo);
  }

  void unsubscribeAnalyzedFiles() {
    removeGeneralAnalysisSubscription(GeneralAnalysisService.ANALYZED_FILES);
  }
}
