// Copyright (c) 2022, 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/cider/document_symbols.dart';
import 'package:analyzer_testing/mock_packages/mock_packages.dart';
import 'package:test/expect.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'cider_service.dart';

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

@reflectiveTest
class CiderDocumentSymbolsComputerTest extends CiderServiceTest {
  void test_class() async {
    var unitOutline = await _compute('''
abstract class A<K, V> {
  int fa, fb;
  String fc;
  A(int i, String s);
  A.name(num p);
  A._privateName(num p);
  static String ma(int pa) => null;
  _mb(int pb);
  R mc<R, P>(P p) {}
  String get propA => null;
  set propB(int v) {}
}
class B {
  B(int p);
}
String fa(int pa) => null;
R fb<R, P>(P p) {}
''');

    expect(unitOutline, hasLength(4));
    // A
    {
      var outline_A = unitOutline[0];
      _expect(
        outline_A,
        kind: SymbolKind.Class,
        name: 'A',
        start: Position(line: 0, character: 0),
        end: Position(line: 11, character: 1),
      );
      // A children
      var outlines_A = outline_A.children!;
      expect(outlines_A, hasLength(11));

      _expect(outlines_A[0], kind: SymbolKind.Field, name: 'fa');
      _expect(outlines_A[1], kind: SymbolKind.Field, name: 'fb');
      _expect(outlines_A[2], kind: SymbolKind.Field, name: 'fc');

      _expect(
        outlines_A[3],
        kind: SymbolKind.Constructor,
        name: 'A',
        detail: '(int i, String s)',
        start: Position(line: 3, character: 2),
        end: Position(line: 3, character: 21),
      );

      _expect(
        outlines_A[4],
        kind: SymbolKind.Constructor,
        name: 'A.name',
        start: Position(line: 4, character: 2),
        end: Position(line: 4, character: 16),
        detail: '(num p)',
      );

      _expect(
        outlines_A[5],
        kind: SymbolKind.Constructor,
        name: 'A._privateName',
        start: Position(line: 5, character: 2),
        end: Position(line: 5, character: 24),
        detail: '(num p)',
      );

      _expect(
        outlines_A[6],
        kind: SymbolKind.Method,
        name: 'ma',
        start: Position(line: 6, character: 2),
        end: Position(line: 6, character: 35),
        detail: '(int pa)',
      );

      _expect(
        outlines_A[7],
        kind: SymbolKind.Method,
        name: '_mb',
        start: Position(line: 7, character: 2),
        end: Position(line: 7, character: 14),
        detail: '(int pb)',
      );

      _expect(
        outlines_A[8],
        kind: SymbolKind.Method,
        name: 'mc',
        start: Position(line: 8, character: 2),
        end: Position(line: 8, character: 20),
        detail: '(P p)',
      );

      _expect(
        outlines_A[9],
        kind: SymbolKind.Property,
        name: 'propA',
        start: Position(line: 9, character: 2),
        end: Position(line: 9, character: 27),
      );

      _expect(
        outlines_A[10],
        kind: SymbolKind.Property,
        name: 'propB',
        start: Position(line: 10, character: 2),
        end: Position(line: 10, character: 21),
        detail: '(int v)',
      );
      // // B
      var outline_B = unitOutline[1];
      _expect(
        outline_B,
        kind: SymbolKind.Class,
        name: 'B',
        start: Position(line: 12, character: 0),
        end: Position(line: 14, character: 1),
      );

      // B children
      var outlines_B = outline_B.children!;
      expect(outlines_B, hasLength(1));

      _expect(
        outlines_B[0],
        kind: SymbolKind.Constructor,
        name: 'B',
        start: Position(line: 13, character: 2),
        end: Position(line: 13, character: 11),
        detail: '(int p)',
      );

      _expect(
        unitOutline[2],
        kind: SymbolKind.Function,
        name: 'fa',
        start: Position(line: 15, character: 0),
        end: Position(line: 15, character: 26),
        detail: '(int pa)',
      );

      _expect(
        unitOutline[3],
        kind: SymbolKind.Function,
        name: 'fb',
        start: Position(line: 16, character: 0),
        end: Position(line: 16, character: 18),
        detail: '(P p)',
      );
    }
  }

  void test_isTest_isTestGroup() async {
    BlazeMockPackages.instance.addMeta(resourceProvider);

    var outline = await _compute('''
import 'package:meta/meta.dart';

@isTestGroup
void myGroup(name, body()) {}

@isTest
void myTest(name) {}

void f() {
  myGroup('group1', () {
    myGroup('group1_1', () {
      myTest('test1_1_1');
      myTest('test1_1_2');
    });
    myGroup('group1_2', () {
      myTest('test1_2_1');
    });
  });
  myGroup('group2', () {
    myTest('test2_1');
    myTest('test2_2');
  });
}
''');
    // outline
    expect(outline, hasLength(3));
    // f
    var f_outline = outline[2];
    _expect(
      f_outline,
      kind: SymbolKind.Function,
      name: 'f',
      start: Position(line: 8, character: 0),
      end: Position(line: 22, character: 1),
      detail: '()',
    );
    var f_children = f_outline.children!;
    expect(f_children, hasLength(2));
    // group1
    var group1_outline = f_children[0];
    _expect(
      group1_outline,
      kind: SymbolKind.Method,
      name: 'myGroup("group1")',
      start: Position(line: 9, character: 2),
      end: Position(line: 17, character: 4),
    );
    var group1_children = group1_outline.children!;
    expect(group1_children, hasLength(2));
    // group1_1
    var group1_1_outline = group1_children[0];
    _expect(
      group1_1_outline,
      kind: SymbolKind.Method,
      name: 'myGroup("group1_1")',
      start: Position(line: 10, character: 4),
      end: Position(line: 13, character: 6),
    );
    var group1_1_children = group1_1_outline.children!;
    expect(group1_1_children, hasLength(2));
    // test1_1_1
    var test1_1_1_outline = group1_1_children[0];
    _expect(
      test1_1_1_outline,
      kind: SymbolKind.Method,
      name: 'myTest("test1_1_1")',
      start: Position(line: 11, character: 6),
      end: Position(line: 11, character: 25),
    );
    // test1_1_1
    var test1_1_2_outline = group1_1_children[1];
    _expect(
      test1_1_2_outline,
      kind: SymbolKind.Method,
      name: 'myTest("test1_1_2")',
      start: Position(line: 12, character: 6),
      end: Position(line: 12, character: 25),
    );
    // group1_2
    var group1_2_outline = group1_children[1];
    _expect(
      group1_2_outline,
      kind: SymbolKind.Method,
      name: 'myGroup("group1_2")',
      start: Position(line: 14, character: 4),
      end: Position(line: 16, character: 6),
    );
    var group1_2_children = group1_2_outline.children!;
    expect(group1_2_children, hasLength(1));
    // test2_1
    var test1_2_1_outline = group1_2_children[0];
    _expect(
      test1_2_1_outline,
      kind: SymbolKind.Method,
      name: 'myTest("test1_2_1")',
      start: Position(line: 15, character: 6),
      end: Position(line: 15, character: 25),
    );
    // group2
    var group2_outline = f_children[1];
    _expect(
      group2_outline,
      kind: SymbolKind.Method,
      name: 'myGroup("group2")',
      start: Position(line: 18, character: 2),
      end: Position(line: 21, character: 4),
    );
    var group2_children = group2_outline.children!;
    expect(group2_children, hasLength(2));
    // test2_1
    var test2_1_outline = group2_children[0];
    _expect(
      test2_1_outline,
      kind: SymbolKind.Method,
      name: 'myTest("test2_1")',
      start: Position(line: 19, character: 4),
      end: Position(line: 19, character: 21),
    );
    // test2_2
    var test2_2_outline = group2_children[1];
    _expect(
      test2_2_outline,
      kind: SymbolKind.Method,
      name: 'myTest("test2_2")',
      start: Position(line: 20, character: 4),
      end: Position(line: 20, character: 21),
    );
  }

  Future<List<DocumentSymbol>> _compute(String content) {
    newFile(testPath, content);
    return CiderDocumentSymbolsComputer(
      fileResolver,
    ).compute2(convertPath(testPath));
  }

  void _expect(
    DocumentSymbol outline, {
    SymbolKind? kind,
    String? name,
    Position? start,
    Position? end,
    String? detail,
  }) {
    if (kind != null) {
      expect(outline.kind, kind);
    }
    if (name != null) {
      expect(outline.name, name);
    }
    if (start != null) {
      var range = outline.range;
      expect(range.start, start);
      expect(range.end, end);
    }
    if (detail != null) {
      expect(outline.detail, detail);
    }
  }
}
