// 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, });
        }
      '''),
    );
  });
}
