// Copyright (c) 2023, 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:code_builder/code_builder.dart';
import 'package:test/test.dart';

import '../common.dart';

void main() {
  useDartfmt();

  test('minimum extension type', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              ),
      ),
      equalsDart(r'''
        extension type Foo(int bar) { }
      '''),
    );
  });

  test('const extension type', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..constant = true
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              ),
      ),
      equalsDart(r'''
        extension type const Foo(int bar) { }
      '''),
    );
  });

  test('extension type with metadata', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              )
              ..docs.add('/// My favorite extension type.')
              ..annotations.addAll([
                refer('deprecated'),
                refer(
                  'Deprecated',
                ).call([literalString('This is an old extension type')]),
              ]),
      ),
      equalsDart(r'''
        /// My favorite extension type.
        @deprecated
        @Deprecated('This is an old extension type')
        extension type Foo(int bar) { }
      '''),
    );
  });

  test('extension type with generics', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..types.addAll([
                TypeReference((b) => b..symbol = 'T'),
                TypeReference((b) => b..symbol = 'U'),
              ])
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'T',
                      )
                      ..name = 'bar',
              ),
      ),
      equalsDart(r'''
        extension type Foo<T,U>(T bar) { }
      '''),
    );
  });

  test('extension type with generics bound', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..types.add(
                TypeReference(
                  (b) =>
                      b
                        ..symbol = 'T'
                        ..bound = TypeReference((b) => b..symbol = 'num'),
                ),
              )
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'T',
                      )
                      ..name = 'bar',
              ),
      ),
      equalsDart(r'''
        extension type Foo<T extends num>(T bar) { }
      '''),
    );
  });

  test('extension type with named primary constructor', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..primaryConstructorName = 'named'
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              ),
      ),
      equalsDart(r'''
        extension type Foo.named(int bar) { }
      '''),
    );
  });

  test('extension type with metadata on field', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar'
                      ..docs.add('/// My favorite representation declaration.')
                      ..annotations.addAll([
                        refer('deprecated'),
                        refer('Deprecated').call([
                          literalString(
                            'This is an old representation declaration',
                          ),
                        ]),
                      ]),
              ),
      ),
      equalsDart(r'''
        extension type Foo(/// My favorite representation declaration.
          @deprecated
          @Deprecated('This is an old representation declaration')
          int bar) { }
      '''),
    );
  });

  test('extension type with implements', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..implements.add(TypeReference((b) => b.symbol = 'num'))
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              ),
      ),
      equalsDart(r'''
        extension type Foo(int bar) implements num { }
      '''),
    );
  });

  test('extension type with multiple implements', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..implements.addAll([
                TypeReference((b) => b.symbol = 'num'),
                TypeReference((b) => b.symbol = 'Object'),
              ])
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              ),
      ),
      equalsDart(r'''
        extension type Foo(int bar) implements num,Object { }
      '''),
    );
  });

  test('extension type with constructors', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..primaryConstructorName = '_'
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              )
              ..constructors.addAll([
                Constructor(
                  (b) => b.requiredParameters.add(
                    Parameter(
                      (b) =>
                          b
                            ..toThis = true
                            ..name = 'bar',
                    ),
                  ),
                ),
                Constructor(
                  (b) =>
                      b
                        ..name = 'named'
                        ..factory = true
                        ..requiredParameters.add(
                          Parameter(
                            (b) =>
                                b
                                  ..type = TypeReference(
                                    (b) => b.symbol = 'int',
                                  )
                                  ..name = 'baz',
                          ),
                        )
                        ..body = const Code('return Foo(baz);'),
                ),
              ]),
      ),
      equalsDart(r'''
        extension type Foo._(int bar) {
          Foo(this.bar);

          factory Foo.named(int baz) {
            return Foo(baz);
          }
        }
      '''),
    );
  });

  test('extension type with external field', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              )
              ..fields.add(
                Field(
                  (b) =>
                      b
                        ..external = true
                        ..type = TypeReference((b) => b.symbol = 'int')
                        ..name = 'property',
                ),
              ),
      ),
      equalsDart(r'''
        extension type Foo(int bar) {
          external int property;
        }
      '''),
    );
  });

  test('extension type with methods', () {
    expect(
      ExtensionType(
        (b) =>
            b
              ..name = 'Foo'
              ..representationDeclaration = RepresentationDeclaration(
                (b) =>
                    b
                      ..declaredRepresentationType = TypeReference(
                        (b) => b.symbol = 'int',
                      )
                      ..name = 'bar',
              )
              ..methods.addAll([
                Method(
                  (b) =>
                      b
                        ..type = MethodType.getter
                        ..returns = TypeReference((b) => b.symbol = 'int')
                        ..name = 'value'
                        ..body = const Code('return this.bar;'),
                ),
                Method(
                  (b) =>
                      b
                        ..returns = TypeReference((b) => b.symbol = 'int')
                        ..name = 'getValue'
                        ..lambda = true
                        ..body = const Code('this.bar'),
                ),
              ]),
      ),
      equalsDart(r'''
        extension type Foo(int bar) {
          int get value { return this.bar; }
          int getValue() => this.bar;
        }
      '''),
    );
  });
}
