// Copyright (c) 2017, 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('should create a class', () {
    expect(
      Class((b) => b..name = 'Foo'),
      equalsDart(r'''
        class Foo {}
      '''),
    );
  });

  test('should create an abstract class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..abstract = true),
      equalsDart(r'''
        abstract class Foo {}
      '''),
    );
  });

  test('should create an abstract base class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..abstract = true
        ..modifier = ClassModifier.base),
      equalsDart(r'''
        abstract base class Foo {}
      '''),
    );
  });

  test('should create a final class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..modifier = ClassModifier.final$),
      equalsDart(r'''
        final class Foo {}
      '''),
    );
  });

  test('should create a sealed class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..sealed = true),
      equalsDart(r'''
        sealed class Foo {}
      '''),
    );
  });

  test('should create an abstract interface class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..abstract = true
        ..modifier = ClassModifier.interface),
      equalsDart(r'''
        abstract interface class Foo {}
      '''),
    );
  });

  test('should create a mixin class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..mixin = true),
      equalsDart(r'''
        mixin class Foo {}
      '''),
    );
  });

  test('should create an abstract mixin class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..abstract = true
        ..mixin = true),
      equalsDart(r'''
        abstract mixin class Foo {}
      '''),
    );
  });

  test('should create a base mixin class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..mixin = true
        ..modifier = ClassModifier.base),
      equalsDart(r'''
        base mixin class Foo {}
      '''),
    );
  });

  test('should create an abstract base mixin class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..abstract = true
        ..mixin = true
        ..modifier = ClassModifier.base),
      equalsDart(r'''
        abstract base mixin class Foo {}
      '''),
    );
  });

  test('should create a class with documentations', () {
    expect(
      Class(
        (b) => b
          ..name = 'Foo'
          ..docs.addAll(
            const [
              '/// My favorite class.',
            ],
          ),
      ),
      equalsDart(r'''
        /// My favorite class.
        class Foo {}
      '''),
    );
  });

  test('should create a class with annotations', () {
    expect(
      Class(
        (b) => b
          ..name = 'Foo'
          ..annotations.addAll([
            refer('deprecated'),
            refer('Deprecated').call([literalString('This is an old class')])
          ]),
      ),
      equalsDart(r'''
        @deprecated
        @Deprecated('This is an old class')
        class Foo {}
      '''),
    );
  });

  test('should create a class with a generic type', () {
    expect(
      Class((b) => b
        ..name = 'List'
        ..types.add(refer('T'))),
      equalsDart(r'''
        class List<T> {}
      '''),
    );
  });

  test('should create a class with multiple generic types', () {
    expect(
      Class(
        (b) => b
          ..name = 'Map'
          ..types.addAll([
            refer('K'),
            refer('V'),
          ]),
      ),
      equalsDart(r'''
        class Map<K, V> {}
      '''),
    );
  });

  test('should create a class with a bound generic type', () {
    expect(
      Class((b) => b
        ..name = 'Comparable'
        ..types.add(TypeReference((b) => b
          ..symbol = 'T'
          ..bound = TypeReference((b) => b
            ..symbol = 'Comparable'
            ..types.add(refer('T').type))))),
      equalsDart(r'''
        class Comparable<T extends Comparable<T>> {}
      '''),
    );
  });

  test('should create a class extending another class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..extend = TypeReference((b) => b.symbol = 'Bar')),
      equalsDart(r'''
        class Foo extends Bar {}
      '''),
    );
  });

  test('should create a class mixing in another class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..extend = TypeReference((b) => b.symbol = 'Bar')
        ..mixins.add(TypeReference((b) => b.symbol = 'Foo'))),
      equalsDart(r'''
        class Foo extends Bar with Foo {}
      '''),
    );
  });

  test('should create a class implementing another class', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..extend = TypeReference((b) => b.symbol = 'Bar')
        ..implements.add(TypeReference((b) => b.symbol = 'Foo'))),
      equalsDart(r'''
        class Foo extends Bar implements Foo {}
      '''),
    );
  });

  test('should create a class with a constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor())),
      equalsDart(r'''
        class Foo {
          Foo();
        }
      '''),
    );
  });

  test('should create a class with a constructor with initializers', () {
    expect(
      Class(
        (b) => b
          ..name = 'Foo'
          ..constructors.add(
            Constructor(
              (b) => b
                ..initializers.addAll([
                  const Code('a = 5'),
                  const Code('super()'),
                ]),
            ),
          ),
      ),
      equalsDart(r'''
        class Foo {
          Foo() : a = 5, super();
        }
      '''),
    );
  });

  test('should create a class with a annotated constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors
            .add(Constructor((b) => b..annotations.add(refer('deprecated'))))),
      equalsDart(r'''
        class Foo {
          @deprecated
          Foo();
        }
      '''),
    );
  });

  test('should create a class with a named constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b..name = 'named'))),
      equalsDart(r'''
        class Foo {
          Foo.named();
        }
      '''),
    );
  });

  test('should create a class with a const constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b..constant = true))),
      equalsDart(r'''
        class Foo {
          const Foo();
        }
      '''),
    );
  });

  test('should create a class with an external constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b..external = true))),
      equalsDart(r'''
        class Foo {
          external Foo();
        }
      '''),
    );
  });

  test('should create a class with a factory constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b
          ..factory = true
          ..redirect = refer('_Foo')))),
      equalsDart(r'''
        class Foo {
          factory Foo() = _Foo;
        }
      '''),
    );
  });

  test('should create a class with a const factory constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b
          ..factory = true
          ..constant = true
          ..redirect = refer('_Foo')))),
      equalsDart(r'''
        class Foo {
          const factory Foo() = _Foo;
        }
      '''),
    );
  });

  test('should create a class with a factory lambda constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b
          ..factory = true
          ..lambda = true
          ..body = const Code('_Foo()')))),
      equalsDart(r'''
        class Foo {
          factory Foo() => _Foo();
        }
      '''),
    );
  });

  test('should create a class with an implicit factory lambda constructor', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b
          ..factory = true
          ..body = refer('_Foo').newInstance([]).code))),
      equalsDart(r'''
        class Foo {
          factory Foo() => _Foo();
        }
      '''),
    );
  });

  test('should create a class with a constructor with a body', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b
          ..factory = true
          ..body = const Code('return _Foo();')))),
      equalsDart(r'''
        class Foo {
          factory Foo() {
            return _Foo();
          }
        }
      '''),
    );
  });

  test('should create a class with a constructor with parameters', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b
          ..requiredParameters.addAll([
            Parameter((b) => b..name = 'a'),
            Parameter((b) => b..name = 'b'),
          ])
          ..optionalParameters.addAll([
            Parameter((b) => b
              ..name = 'c'
              ..named = true),
          ])))),
      equalsDart(r'''
        class Foo {
          Foo(a, b, {c, });
        }
      '''),
    );
  });

  test('should create a class with a constructor+field-formal parameters', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b
          ..requiredParameters.addAll([
            Parameter((b) => b
              ..name = 'a'
              ..toThis = true),
            Parameter((b) => b
              ..name = 'b'
              ..toThis = true),
          ])
          ..optionalParameters.addAll([
            Parameter((b) => b
              ..name = 'c'
              ..named = true
              ..toThis = true),
          ])))),
      equalsDart(r'''
        class Foo {
          Foo(this.a, this.b, {this.c, });
        }
      '''),
    );
  });

  test('should create a class with a constructor+super-formal parameters', () {
    expect(
      Class((b) => b
        ..name = 'Foo'
        ..constructors.add(Constructor((b) => b
          ..requiredParameters.addAll([
            Parameter((b) => b
              ..name = 'a'
              ..toSuper = true),
            Parameter((b) => b
              ..name = 'b'
              ..toSuper = true),
          ])
          ..optionalParameters.addAll([
            Parameter((b) => b
              ..name = 'c'
              ..named = true
              ..toSuper = true),
          ])))),
      equalsDart(r'''
        class Foo {
          Foo(super.a, super.b, {super.c, });
        }
      '''),
    );
  });
}
