// 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:analyzer/file_system/memory_file_system.dart';
import 'package:dartdoc/src/dartdoc.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

import '../src/test_descriptor_utils.dart' as d;
import '../src/utils.dart';

void main() async {
  const packageName = 'test_package';

  late List<String> eLines;
  late List<String> eRightSidebarLines;
  late List<String> enumWithDefaultConstructorLines;

  group('enums', () {
    setUpAll(() async {
      final packageMetaProvider = testPackageMetaProvider;
      final resourceProvider =
          packageMetaProvider.resourceProvider as MemoryResourceProvider;
      final packagePath = await d.createPackage(
        packageName,
        pubspec: '''
name: enums
version: 0.0.1
environment:
  sdk: '>=2.17.0-0 <3.0.0'
''',
        libFiles: [
          d.file('lib.dart', '''
class C<T> {
  const C(String m);
}

mixin M<T> {}

@C('message')
enum E<T> with M<T> implements C<T> {
  /// Doc comment for [one].
  one<int>.named(1),

  @deprecated
  two<double>.named(2.0),
  three<num>.named(3);

  const E.named(T i);

  /// A method.
  void m1() {}

  /// An operator.
  bool operator >(E other) => false;

  /// A field.
  final int f1 = 0;

  /// An instance getter.
  int get ig1 => 1;

  /// An instance setter.
  set is1(int value) {}

  /// A static method.
  static void s1() {}

  /// A constant.
  static const c1 = 1;

  /// A static field.
  static final int sf1 = 2;

  /// A static getter.
  static int get gs1 => 3;

  /// A static setter.
  static set gs1(int value) {}
}

enum EnumWithDefaultConstructor {
  /// Doc comment for [four].
  four,

  /// Doc comment for [five].
  five,

  /// Doc comment for [six].
  six;
}

extension Ext<T> on E<T> {}
'''),
        ],
        resourceProvider: resourceProvider,
      );
      await writeDartdocResources(resourceProvider);
      final context = generatorContextFromArgv([
        '--input',
        packagePath,
        '--output',
        path.join(packagePath, 'doc'),
        '--sdk-dir',
        packageMetaProvider.defaultSdkDir.path,
      ], packageMetaProvider);

      final packageBuilder = PubPackageBuilder(
        context,
        packageMetaProvider,
        skipUnreachableSdkLibraries: true,
      );
      await Dartdoc.fromContext(context, packageBuilder).generateDocs();
      eLines = resourceProvider
          .getFile(path.join(packagePath, 'doc', 'lib', 'E.html'))
          .readAsStringSync()
          .split('\n');
      eRightSidebarLines = resourceProvider
          .getFile(path.join(packagePath, 'doc', 'lib', 'E-enum-sidebar.html'))
          .readAsStringSync()
          .split('\n');
      enumWithDefaultConstructorLines = resourceProvider
          .getFile(path.join(
              packagePath, 'doc', 'lib', 'EnumWithDefaultConstructor.html'))
          .readAsStringSync()
          .split('\n');
    });

    test('enum page contains enum name with generics', () async {
      // TODO(srawlins): Use expectMainContentContainsAllInOrder throughout.
      expect(
          eLines,
          containsAllInOrder([
            matches('<span class="kind-enum">E&lt;<wbr>'
                '<span class="type-parameter">T</span>&gt;</span>'),
          ]));
    });

    test('enum page contains implemented types', () async {
      expect(
        eLines,
        containsAllInOrder([
          matches('<dt>Implemented types</dt>'),
          matches('<a href="../lib/C-class.html">C</a>'
              '<span class="signature">&lt;<wbr>'
              '<span class="type-parameter">T</span>&gt;</span>'),
        ]),
      );
    });

    test('enum page contains mixed-in types', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<dt>Mixed-in types</dt>'),
            matches('<a href="../lib/M-mixin.html">M</a>'
                '<span class="signature">&lt;<wbr>'
                '<span class="type-parameter">T</span>&gt;</span>'),
          ]));
    });

    test('enum page contains available extensions', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<dt>Available extensions</dt>'),
            matches('<a href="../lib/E.html">E</a></span>'),
          ]));
    });

    test('enum page contains annotations', () async {
      eLines.expectMainContentContainsAllInOrder([
        matches('<dt>Annotations</dt>'),
        matches('<ul class="annotation-list eNum-relationships">'),
        matches(
            r'<li>@<a href="../lib/C/C.html">C</a>&lt;dynamic&gt;\(&#39;message&#39;\)</li>'),
        matches('</ul>'),
      ]);
    });

    test('enum page contains values', () async {
      expect(
        eLines,
        containsAllInOrder([
          matches('<h2>Values</h2>'),
          matches('<span class="name ">one</span>'),
          matches('<p>Doc comment for <a href="../lib/E.html">one</a>.</p>'),
          matches(
              r'<span class="signature"><code>const E&lt;int&gt;.named\(1\)</code></span>'),
        ]),
      );
    });

    test('enum page contains classic values', () async {
      expect(
        enumWithDefaultConstructorLines,
        containsAllInOrder([
          matches('<h2>Values</h2>'),
          matches('<span class="name ">four</span>'),
          matches(
              '<p>Doc comment for <a href="../lib/EnumWithDefaultConstructor.html">six</a>.</p>'),
        ]),
      );
      expect(
        enumWithDefaultConstructorLines.join('\n'),
        isNot(contains(
            '<span class="signature"><code>EnumWithDefaultConstructor')),
      );
    });

    test('enum page contains values with deprecated annotation', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Values</h2>'),
            matches('<span class="name deprecated">two</span>'),
          ]));
    });

    test("enum page contains the 'values' constant", () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Constants</h2>'),
            matches('<a href="../lib/E/values-constant.html">values</a>'),
            matches(
                'A constant List of the values in this enum, in order of their declaration.'),
          ]));
    });

    test('enum page contains hashCode property', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Properties</h2>'),
            matches('<a href=".*/dart-core/Object/hashCode.html">hashCode</a>'),
          ]));
    });

    test('enum page contains index property', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Properties</h2>'),
            matches('<a href=".*/dart-core/Enum/index.html">index</a>'),
            matches('The integer index of this enum value.'),
          ]));
    });

    test('enum page contains other properties', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Properties</h2>'),
            matches('<a href="../lib/E/f1.html">f1</a>'),
            matches('A field.'),
          ]));
    });

    test('enum page contains instance getters', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Properties</h2>'),
            matches('<a href="../lib/E/ig1.html">ig1</a>'),
            matches('An instance getter.'),
            matches('<span class="feature">no setter</span>'),
          ]));
    });

    test('enum page contains instance setters', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Properties</h2>'),
            matches('<a href="../lib/E/is1.html">is1</a>'),
            matches('An instance setter.'),
            matches('<span class="feature">no getter</span>'),
          ]));
    });

    test('enum page contains instance methods', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Methods</h2>'),
            matches('<a href="../lib/E/m1.html">m1</a>'),
            matches('A method.'),
          ]));
    });

    test('enum page contains static methods', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Static Methods</h2>'),
            matches('<a href="../lib/E/s1.html">s1</a>'),
            matches('A static method.'),
          ]));
    });

    test('enum page contains static fields', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Static Properties</h2>'),
            matches('<a href="../lib/E/sf1.html">sf1</a>'),
            matches('A static field.'),
          ]));
    });

    test('enum page contains static getter/setter pairs', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Static Properties</h2>'),
            matches('<a href="../lib/E/gs1.html">gs1</a>'),
            matches('A static getter.'),
          ]));
    });

    test('enum page contains (static) constants', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Constants</h2>'),
            matches('<a href="../lib/E/c1-constant.html">c1</a>'),
            matches('A constant.'),
          ]));
    });

    test('enum page contains instance operators', () async {
      expect(
          eLines,
          containsAllInOrder([
            matches('<h2>Operators</h2>'),
            matches('<a href="../lib/E/operator_greater.html">operator ></a>'),
            matches('An operator.'),
          ]));
    });

    test('enum sidebar contains values', () async {
      expect(
        eRightSidebarLines,
        containsAllInOrder([
          matches('<a href="lib/E.html#values">Values</a>'),
          matches('<li><a href="lib/E.html#one">one</a></li>'),
        ]),
      );
    });

    test('enum sidebar contains properties', () async {
      expect(
        eRightSidebarLines,
        containsAllInOrder([
          matches('<a href="lib/E.html#instance-properties">Properties</a>'),
          matches('<a href="lib/E/f1.html">f1</a>'),
          matches('<a href=".*/dart-core/Enum/index.html">index</a>'),
        ]),
      );
    });

    test('enum sidebar contains methods', () async {
      expect(
        eRightSidebarLines,
        containsAllInOrder([
          matches('<a href="lib/E.html#instance-methods">Methods</a>'),
          matches('<a href="lib/E/m1.html">m1</a>'),
        ]),
      );
    });

    test('enum sidebar contains operators', () async {
      expect(
        eRightSidebarLines,
        containsAllInOrder([
          matches('<a href="lib/E.html#operators">Operators</a>'),
          matches('<a href="lib/E/operator_greater.html">operator ></a>'),
        ]),
      );
    });

    test('enum sidebar contains static properties', () async {
      expect(
        eRightSidebarLines,
        containsAllInOrder([
          matches(
              '<a href="lib/E.html#static-properties">Static properties</a>'),
          matches('<a href="lib/E/gs1.html">gs1</a>'),
          matches('<a href="lib/E/sf1.html">sf1</a>'),
        ]),
      );
    });

    test('enum sidebar contains static methods', () async {
      expect(
        eRightSidebarLines,
        containsAllInOrder([
          matches('<a href="lib/E.html#static-methods">Static methods</a>'),
          matches('<a href="lib/E/s1.html">s1</a>'),
        ]),
      );
    });

    test('enum sidebar contains constants', () async {
      expect(
        eRightSidebarLines,
        containsAllInOrder([
          matches('<a href="lib/E.html#constants">Constants</a>'),
          matches('<a href="lib/E/c1-constant.html">c1</a>'),
          matches('<a href="lib/E/values-constant.html">values</a>'),
        ]),
      );
    });

    // TODO(srawlins): Add rendering tests.
    // * Add tests for rendered supertype (Enum) HTML.
  });
}
