// Copyright (c) 2026, 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:dartdoc/src/warnings.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'documentation_comment_test_base.dart';
import 'src/utils.dart' as utils;

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

@reflectiveTest
class ExampleDirectiveTest extends DocumentationCommentTestBase {
  Future<void> _bootPackage(String comment,
      {Map<String, String> files = const {},
      String libraryPath = 'lib/a.dart'}) async {
    projectRoot = utils.writePackage(packageName, resourceProvider);
    var pathContext = resourceProvider.pathContext;

    for (var MapEntry(key: path, value: content) in files.entries) {
      _writeFile(path, content);
    }

    _writeFile(libraryPath, '$comment\nlibrary;');

    packageGraph =
        await utils.bootBasicPackage(projectRoot.path, packageMetaProvider);

    var expectedPath =
        pathContext.normalize(pathContext.join(projectRoot.path, libraryPath));
    libraryModel = packageGraph.defaultPackage.libraries.firstWhere(
        (l) => pathContext.normalize(l.sourceFileName) == expectedPath);
  }

  void _writeFile(String path, String content) {
    var pathParts = path.split('/');
    var currentFolder = projectRoot;
    for (var i = 0; i < pathParts.length - 1; i++) {
      if (pathParts[i].isEmpty) continue;
      currentFolder = currentFolder.getChildAssumingFolder(pathParts[i]);
    }
    currentFolder
        .getChildAssumingFile(pathParts.last)
        .writeAsStringSync(content);
  }

  void test_processesExampleDirective() async {
    await _bootPackage('''
/// Text.
///
/// {@example /examples/hello.dart}
///
/// End text.
''', files: {
      'examples/hello.dart': 'void main() => print("hello");',
    });

    var doc = await libraryModel.processComment();

    expectNoWarnings();
    expect(doc, equals('''
Text.


```dart
void main() => print("hello");
```


End text.'''));
  }

  void test_processesExampleDirective_noExtension_noLang() async {
    await _bootPackage('''
/// {@example /examples/hello}
''', files: {
      'examples/hello': 'hello world',
    });

    var doc = await libraryModel.processComment();

    expectNoWarnings();
    expect(doc, equals('''

```
hello world
```
'''));
  }

  void test_processesExampleDirective_withLang() async {
    await _bootPackage('''
/// {@example /examples/hello.txt lang=text}
''', files: {
      'examples/hello.txt': 'hello world',
    });

    var doc = await libraryModel.processComment();

    expectNoWarnings();
    expect(doc, equals('''

```text
hello world
```
'''));
  }

  void test_processesExampleDirective_withIndentKeep() async {
    await _bootPackage('''
/// {@example /examples/hello.dart indent=keep}
''', files: {
      'examples/hello.dart': '  void main() {\n    print("hello");\n  }',
    });

    var doc = await libraryModel.processComment();

    expectNoWarnings();
    expect(doc, equals('''

```dart
  void main() {
    print("hello");
  }
```
'''));
  }

  void test_processesExampleDirective_inline_ignored() async {
    await _bootPackage('''
/// This is an inline {@example /examples/hello.dart} directive.
''', files: {
      'examples/hello.dart': 'void main() => print("hello");',
    });

    var doc = await libraryModel.processComment();

    expectNoWarnings();
    // It should NOT be replaced because it's not on its own line.
    expect(doc, equals('''
This is an inline {@example /examples/hello.dart} directive.'''));
  }

  void test_processesExampleDirective_withIndentStrip() async {
    await _bootPackage('''
/// {@example /examples/hello.dart indent=strip}
''', files: {
      'examples/hello.dart': '  void main() {\n    print("hello");\n  }',
    });

    var doc = await libraryModel.processComment();

    expectNoWarnings();
    expect(doc, equals('''

```dart
void main() {
  print("hello");
}
```
'''));
  }

  void test_processesExampleDirective_withIndentStrip_mixedWhitespace() async {
    await _bootPackage('''
/// {@example /examples/hello.dart indent=strip}
''', files: {
      'examples/hello.dart': '  space\n\ttab\n  space',
    });

    var doc = await libraryModel.processComment();

    expect(
      libraryModel,
      hasWarning(PackageWarning.invalidParameter,
          'Example contains non-space whitespace in indentation. Indentation stripping disabled to avoid incorrect formatting.'),
    );
    expect(doc, equals('''

```dart
  space
\ttab
  space
```
'''));
  }

  void test_exampleDirective_missingFile() async {
    await _bootPackage('''
/// {@example /examples/non_existent.dart}
''');

    await libraryModel.processComment();

    expect(
      libraryModel,
      hasWarning(
          PackageWarning.missingExampleFile, '/examples/non_existent.dart'),
    );
  }

  void test_exampleDirective_missingFilePath() async {
    await _bootPackage('''
/// {@example}
''');

    await libraryModel.processComment();

    expect(
      libraryModel,
      hasWarning(PackageWarning.invalidParameter,
          'Must specify a file path for the @example directive.'),
    );
  }

  void test_exampleDirective_extraPositionalArguments() async {
    await _bootPackage('''
/// {@example /examples/hello.dart extra1 extra2}
''', files: {
      'examples/hello.dart': 'void main() {}',
    });

    await libraryModel.processComment();

    expect(
      libraryModel,
      hasWarning(
          PackageWarning.invalidParameter,
          'The {@example} directive only takes one positional argument (the file path). '
          'Ignoring extra arguments: extra1 extra2'),
    );
  }
}
