// 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:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model_utils.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'dartdoc_test_base.dart';
import 'src/utils.dart';

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

@reflectiveTest
class ParameterTest extends DartdocTestBase {
  @override
  String get libraryName => 'parameters';

  void test_formalParameter_private_named_referenced() async {
    var library = await bootPackageWithLibrary(
      '''
class C {
  int _x;

  /// Makes with an [_x].
  C({required this._x});
}
''',
      libraryPreamble: '// @dart=3.12',
      additionalArguments: ['--enable-experiment=private-named-parameters'],
    );
    var cNew = library.classes.named('C').constructors.named('C.new');
    var parameter = cNew.parameters.first;

    // The parameter's documented name is the public name of the field formal parameter.
    expect(parameter.documentedName, equals('x'));

    // The parameter reference uses its public name.
    expect(cNew.documentationAsHtml, '<p>Makes with an <code>x</code>.</p>');

    // The parameter in the signature uses its public name.
    expect(cNew.linkedParams, matchesCompressed(r'''
        \{
        <span class="parameter" id="param-x">
          <span>required</span>
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">x</span>
        </span>
        \}
      '''));
  }

  void test_formalParameter_referenced() async {
    var library = await bootPackageWithLibrary('''
/// Text [p].
void f(int p) {}
''');
    var f = library.functions.named('f');
    // There is no link, but also no wrong link or crash.
    expect(f.documentationAsHtml, '<p>Text <code>p</code>.</p>');
  }

  void test_formalParameter_referenced_notShadowedElement() async {
    var library = await bootPackageWithLibrary('''
/// Text [p].
void f(int p) {}
var p = 0;
''');
    var f = library.functions.named('f');
    // There is no link, but also no wrong link or crash.
    expect(f.documentationAsHtml, '<p>Text <code>p</code>.</p>');
  }

  void test_formalParameter_referenced_notShadowedPrefix() async {
    var library = await bootPackageWithLibrary('''
import 'dart:async' as p;
/// Text [p].
void f(int p) {}
''');
    var f = library.functions.named('f');
    // There is no link, but also no wrong link or crash.
    expect(f.documentationAsHtml, '<p>Text <code>p</code>.</p>');
  }

  void test_formalParameter_referenced_wildcard() async {
    var library = await bootPackageWithLibrary('''
/// Text [_].
void f(int _) {}
''');
    var f = library.functions.named('f');
    // There is no link, but also no wrong link or crash.
    expect(f.documentationAsHtml, '<p>Text <code>_</code>.</p>');
  }

  void test_formalParameter_generic_method() async {
    var library = await bootPackageWithLibrary('''
class C {
  int one(int Function<T>(T)? f) {
    return 1;
  }
}
''');
    var one = library.method('C', 'one');
    expect(one.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="one-param-f">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">f</span>
          &lt;<wbr>
          <span class="type-parameter">T</span>
          &gt;\(
          <span class="parameter" id="param-">
            <span class="type-annotation">T</span>
          </span>
          \)\?
        </span>
      '''));
  }

  void test_formalParameter_generic_topLevelFunction() async {
    var library = await bootPackageWithLibrary('''
int one(int Function<T>(T)? f) {
  return 1;
}
''');
    var one = library.functions.wherePublic.firstWhere((c) => c.name == 'one');
    expect(one.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="one-param-f">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">f</span>
          &lt;<wbr>
          <span class="type-parameter">T</span>
          &gt;\(
          <span class="parameter" id="param-">
            <span class="type-annotation">T</span>
          </span>
          \)\?
        </span>
      '''));
  }

  void test_formalParameter_named() async {
    var library = await bootPackageWithLibrary('''
class A {
  int? f;
  A.named({this.f});
}
''');
    var named = library.constructor('A.named');

    expect(named.linkedParams, matchesCompressed(r'''
        \{
        <span class="parameter" id="named-param-f">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>\?
          </span>
          <span class="parameter-name">f</span>
        </span>
        \}
      '''));
  }

  void test_formalParameter_named_defaultValue() async {
    var library = await bootPackageWithLibrary('''
class A {
  int? f;
  A.namedWithDefault({this.f = 0});
}
''');
    var namedWithDefault = library.constructor('A.namedWithDefault');

    expect(namedWithDefault.linkedParams, matchesCompressed(r'''
        \{
        <span class="parameter" id="namedWithDefault-param-f">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>\?
          </span>
          <span class="parameter-name">f</span>
          =
          <span class="default-value">0</span>
        </span>
        \}
      '''));
  }

  void test_formalParameter_named_required() async {
    var library = await bootPackageWithLibrary('''
class A {
  int? f;
  A.requiredNamed({required this.f});
}
''');
    var requiredNamed = library.constructor('A.requiredNamed');

    expect(requiredNamed.linkedParams, matchesCompressed(r'''
        \{
        <span class="parameter" id="requiredNamed-param-f">
          <span>required</span>
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>\?
          </span>
          <span class="parameter-name">f</span>
        </span>
        \}
      '''));
  }

  void test_formalParameter_positional_optional() async {
    var library = await bootPackageWithLibrary('''
class A {
  int? f;
  A.optionalPositional([this.f]);
}
''');
    var optionalPositional = library.constructor('A.optionalPositional');

    expect(optionalPositional.linkedParams, matchesCompressed(r'''
        \[
        <span class="parameter" id="optionalPositional-param-f">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>\?
          </span>
          <span class="parameter-name">f</span>
        </span>
        \]
      '''));
  }

  void test_formalParameter_positional_optional_defaultValue() async {
    var library = await bootPackageWithLibrary('''
class A {
  int? f;
  A.defaultValue([this.f = 0]);
}
''');
    var defaultValue = library.constructor('A.defaultValue');

    expect(defaultValue.linkedParams, matchesCompressed(r'''
        \[
        <span class="parameter" id="defaultValue-param-f">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>\?
          </span>
          <span class="parameter-name">f</span>
          =
          <span class="default-value">0</span>
        </span>
        \]
      '''));
  }

  void test_formalParameter_positional_required() async {
    var library = await bootPackageWithLibrary('''
class A {
  int? f;
  A.requiredPositional(this.f);
}
''');
    var requiredPositional = library.constructor('A.requiredPositional');

    expect(requiredPositional.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="requiredPositional-param-f">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>\?
          </span>
          <span class="parameter-name">f</span>
        </span>
      '''));
  }

  void test_fieldFormalParameter_referenced() async {
    var library = await bootPackageWithLibrary('''
class C {
  int p;
  /// Text [p].
  C(this.p);
}
''');
    var cConstructor = library.classes.named('C').constructors.named('C.new');
    // There is no link, but also no wrong link or crash.
    expect(cConstructor.documentationAsHtml, '<p>Text <code>p</code>.</p>');
  }

  void test_fieldFormalParameter_referenced_wildcard() async {
    var library = await bootPackageWithLibrary('''
class C {
  int _;
  /// Text [_].
  C(this._);
}
''');
    var cConstructor = library.classes.named('C').constructors.named('C.new');
    // There is no link, but also no wrong link or crash.
    expect(cConstructor.documentationAsHtml, '<p>Text <code>_</code>.</p>');
  }

  void test_superParameter_referenced_wildcard() async {
    var library = await bootPackageWithLibrary('''
class C {
  C(int _);
}
class D extends C {
  /// Text [_].
  D(super._) {}
}
''');
    var dConstructor = library.classes.named('D').constructors.named('D.new');
    // There is no link, but also no wrong link or crash.
    expect(dConstructor.documentationAsHtml, '<p>Text <code>_</code>.</p>');
  }

  void test_superParameter_fieldFormal() async {
    var library = await bootPackageWithLibrary('''
class C {
  int f;
  C.fieldFormal(this.f);
}
class D extends C {
  D.fieldFormal(super.f) : super.fieldFormal();
}
''');
    var fieldFormal = library.constructor('D.fieldFormal');

    expect(fieldFormal.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="fieldFormal-param-f">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">f</span>
        </span>
      '''));
  }

  void test_superParameter_isSubtype() async {
    var library = await bootPackageWithLibrary('''
class C {
  C.positionalNum(num g);
}
class D extends C {
  D.positionalNum(int super.g) : super.positionalNum();
}
''');
    var positionalNum = library.constructor('D.positionalNum');

    expect(positionalNum.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="positionalNum-param-g">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">g</span>
        </span>
      '''));
  }

  void test_superParameter_superParameter() async {
    var library = await bootPackageWithLibrary('''
class C {
  C.requiredPositional(int a);
}
class D extends C {
  D.requiredPositional(super.a) : super.requiredPositional();
}
class E extends D {
  E.superIsSuper(super.a) : super.requiredPositional();
}
''');
    var superIsSuper = library.constructor('E.superIsSuper');

    expect(superIsSuper.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="superIsSuper-param-a">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">a</span>
        </span>
      '''));
  }

  void test_superParameter_named() async {
    var library = await bootPackageWithLibrary('''
class C {
  C.named({int? a});
}
class D extends C {
  D.named({super.a}) : super.named();
}
''');
    var named = library.constructor('D.named');

    expect(named.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="named-param-a">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>\?
          </span>
          <span class="parameter-name">a</span>
        </span>
        \}
      '''));
  }

  void test_superParameter_named_default() async {
    var library = await bootPackageWithLibrary('''
class C {
  C.namedWithDefault({int a = 0});
}
class D extends C {
  D.namedWithDefault({int a = 0}) : super.namedWithDefault();
}
''');
    var namedWithDefault = library.constructor('D.namedWithDefault');

    expect(namedWithDefault.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="namedWithDefault-param-a">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">a</span>
          =
          <span class="default-value">0</span>
        </span>
        \}
      '''));
  }

  void test_superParameter_named_required() async {
    var library = await bootPackageWithLibrary('''
class C {
  C.requiredNamed({required int a});
}
class D extends C {
  D.requiredNamed({required super.a}) : super.requiredNamed();
}
''');
    var requiredNamed = library.constructor('D.requiredNamed');

    expect(requiredNamed.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="requiredNamed-param-a">
          <span>required</span>
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">a</span>
        </span>
        \}
      '''));
  }

  void test_superParameter_positional_optional() async {
    var library = await bootPackageWithLibrary('''
class C {
  C.optionalPositional([int? a]);
}
class D extends C {
  D.optionalPositional([super.a]) : super.optionalPositional();
}
''');
    var optionalPositional = library.constructor('D.optionalPositional');

    expect(optionalPositional.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="optionalPositional-param-a">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>\?
          </span>
          <span class="parameter-name">a</span>
        </span>
        \]
      '''));
  }

  void test_superParameter_positional_optional_default() async {
    var library = await bootPackageWithLibrary('''
class C {
  C.defaultValue([int a = 0]);
}
class D extends C {
  D.defaultValue([super.a = 0]) : super.defaultValue();
}
''');
    var defaultValue = library.constructor('D.defaultValue');

    expect(defaultValue.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="defaultValue-param-a">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">a</span>
          =
          <span class="default-value">0</span>
        </span>
        \]
      '''));
  }

  void test_superParameter_positional_required() async {
    var library = await bootPackageWithLibrary('''
class C {
  C.requiredPositional(int a);
}
class D extends C {
  D.requiredPositional(super.a) : super.requiredPositional();
}
''');
    var requiredPositional = library.constructor('D.requiredPositional');

    expect(requiredPositional.linkedParams, matchesCompressed(r'''
        <span class="parameter" id="requiredPositional-param-a">
          <span class="type-annotation">
            <a href=".*/dart-core/int-class\.html">int</a>
          </span>
          <span class="parameter-name">a</span>
        </span>
      '''));
  }
}

extension on Library {
  Constructor constructor(String name) {
    var className = name.split('.').first;
    return classes
        .firstWhere((c) => c.name == className)
        .constructors
        .firstWhere((c) => c.name == name);
  }

  Method method(String className, String methodName) => classes
      .firstWhere((clas) => clas.name == className)
      .declaredMethods
      .firstWhere((method) => method.name == methodName);
}
