// Copyright (c) 2013, 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.

@TestOn('vm')
library;

import 'dart:collection';

import 'package:collection/collection.dart';
import 'package:protoc_plugin/indenting_writer.dart';
import 'package:protoc_plugin/protoc.dart';
import 'package:protoc_plugin/src/gen/google/protobuf/compiler/plugin.pb.dart';
import 'package:protoc_plugin/src/gen/google/protobuf/descriptor.pb.dart';
import 'package:protoc_plugin/src/linker.dart';
import 'package:protoc_plugin/src/options.dart';
import 'package:test/test.dart';

import 'src/golden_file.dart';
import 'src/test_features.dart';

void main() {
  late FileDescriptorProto fd;
  EnumDescriptorProto ed;
  late DescriptorProto md;

  setUp(() async {
    fd = FileDescriptorProto();
    ed =
        EnumDescriptorProto()
          ..name = 'PhoneType'
          ..value.addAll([
            EnumValueDescriptorProto()
              ..name = 'MOBILE'
              ..number = 0,
            EnumValueDescriptorProto()
              ..name = 'HOME'
              ..number = 1,
            EnumValueDescriptorProto()
              ..name = 'WORK'
              ..number = 2,
            EnumValueDescriptorProto()
              ..name = 'BUSINESS'
              ..number = 2,
          ]);
    md =
        DescriptorProto()
          ..name = 'PhoneNumber'
          ..field.addAll([
            // optional PhoneType type = 2 [default = HOME];
            FieldDescriptorProto()
              ..name = 'type'
              ..jsonName = 'type'
              ..number = 2
              ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
              ..type = FieldDescriptorProto_Type.TYPE_ENUM
              ..typeName = '.PhoneNumber.PhoneType',
            // required string number = 1;
            FieldDescriptorProto()
              ..name = 'number'
              ..jsonName = 'number'
              ..number = 1
              ..label = FieldDescriptorProto_Label.LABEL_REQUIRED
              ..type = FieldDescriptorProto_Type.TYPE_STRING,
            FieldDescriptorProto()
              ..name = 'name'
              ..jsonName = 'name'
              ..number = 3
              ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
              ..type = FieldDescriptorProto_Type.TYPE_STRING
              ..defaultValue = r'$',
            FieldDescriptorProto()
              ..name = 'deprecated_field'
              ..jsonName = 'deprecatedField'
              ..number = 4
              ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
              ..type = FieldDescriptorProto_Type.TYPE_STRING
              ..options = (FieldOptions()..deprecated = true),
          ])
          ..enumType.add(ed);
  });

  test('testMessageGenerator', () {
    final options =
        parseGenerationOptions(
          CodeGeneratorRequest()..parameter = 'disable_constructor_args',
          CodeGeneratorResponse(),
        )!;

    final fg = FileGenerator(testEditionDefaults, fd, options);
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    final ctx = GenerationContext(options);
    mg.register(ctx);
    mg.resolve(ctx);

    var writer = IndentingWriter(generateMetadata: true, fileName: '');
    mg.generate(writer);
    expectGolden(writer.emitSource(format: false), 'messageGenerator.pb.dart');
    expectGolden(
      writer.sourceLocationInfo.toString(),
      'messageGenerator.pb.dart.meta',
    );

    writer = IndentingWriter(generateMetadata: true, fileName: '');
    mg.generateEnums(writer);
    expectGolden(
      writer.emitSource(format: false),
      'messageGeneratorEnums.pb.dart',
    );
    expectGolden(
      writer.sourceLocationInfo.toString(),
      'messageGeneratorEnums.pb.dart.meta',
    );
  });

  test('testMetadataIndices', () {
    final options =
        parseGenerationOptions(
          CodeGeneratorRequest()..parameter = 'disable_constructor_args',
          CodeGeneratorResponse(),
        )!;
    final fg = FileGenerator(testEditionDefaults, fd, options);
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    final ctx = GenerationContext(options);
    mg.register(ctx);
    mg.resolve(ctx);

    final writer = IndentingWriter(generateMetadata: true, fileName: '');
    mg.generate(writer);

    final eq = ListEquality();
    final fieldStringsMap = HashMap(
      equals: eq.equals,
      hashCode: eq.hash,
      isValidKey: eq.isValidKey,
    );
    fieldStringsMap[[4, 0]] = ['PhoneNumber'];
    fieldStringsMap[[4, 0, 2, 0]] = ['type', 'hasType', 'clearType'];
    fieldStringsMap[[4, 0, 2, 1]] = ['number', 'hasNumber', 'clearNumber'];
    fieldStringsMap[[4, 0, 2, 2]] = ['name', 'hasName', 'clearName'];
    fieldStringsMap[[4, 0, 2, 3]] = [
      'deprecatedField',
      'hasDeprecatedField',
      'clearDeprecatedField',
    ];

    final generatedContents = writer.emitSource(format: false);
    final metadata = writer.sourceLocationInfo;
    for (final annotation in metadata.annotation) {
      final annotatedName = generatedContents.substring(
        annotation.begin,
        annotation.end,
      );
      final expectedStrings = fieldStringsMap[annotation.path];
      if (expectedStrings == null) {
        fail(
          'The field path ${annotation.path} '
          'did not match any expected field path.',
        );
      }
      expect(annotatedName, isIn(expectedStrings));
    }
  });

  test('MessageGenerator inherits from a parent file', () {
    setTestFeature(fd, 1);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    expect(getTestFeature(mg.features), 1);
  });

  test('MessageGenerator can override parent file features', () {
    setTestFeature(fd, 1);
    setTestFeature(md, 2);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    expect(getTestFeature(mg.features), 2);
  });

  test('MessageGenerator inherits from a parent message', () {
    final mdParent = setTestFeature(md.deepCopy(), 1);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mgParent = MessageGenerator.topLevel(
      mdParent,
      fg,
      {},
      null,
      <String>{},
      0,
    );
    final mg = MessageGenerator.nested(md, mgParent, {}, null, <String>{}, 0);

    expect(getTestFeature(mg.features), 1);
  });

  test('MessageGenerator can override parent message features', () {
    final mdParent = setTestFeature(md.deepCopy(), 1);
    setTestFeature(md, 2);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mgParent = MessageGenerator.topLevel(
      mdParent,
      fg,
      {},
      null,
      <String>{},
      0,
    );
    final mg = MessageGenerator.nested(md, mgParent, {}, null, <String>{}, 0);

    expect(getTestFeature(mg.features), 2);
  });

  test('MessageGenerator fields inherit from a parent message', () {
    fd.edition = Edition.EDITION_2023;
    (md.field..clear()).add(
      FieldDescriptorProto()
        ..name = 'number'
        ..jsonName = 'number'
        ..number = 1
        ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
        ..type = FieldDescriptorProto_Type.TYPE_STRING,
    );
    setTestFeature(md, 1);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    final ctx = GenerationContext(GenerationOptions());
    mg.register(ctx);
    mg.resolve(ctx);

    expect(getTestFeature(mg.fieldList[0].features), 1);
  });

  test('MessageGenerator fields can override parent message features', () {
    fd.edition = Edition.EDITION_2023;
    (md.field..clear()).add(
      FieldDescriptorProto()
        ..name = 'number'
        ..jsonName = 'number'
        ..number = 1
        ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
        ..type = FieldDescriptorProto_Type.TYPE_STRING,
    );
    setTestFeature(md, 1);
    setTestFeature(md.field[0], 2);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    final ctx = GenerationContext(GenerationOptions());
    mg.register(ctx);
    mg.resolve(ctx);

    expect(getTestFeature(mg.fieldList[0].features), 2);
  });

  test('MessageGenerator fields inherit from a parent oneof', () {
    fd.edition = Edition.EDITION_2023;
    md.oneofDecl.add(OneofDescriptorProto()..name = 'oneof');
    (md.field..clear()).add(
      FieldDescriptorProto()
        ..name = 'number'
        ..jsonName = 'number'
        ..number = 1
        ..oneofIndex = 0
        ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
        ..type = FieldDescriptorProto_Type.TYPE_STRING,
    );
    setTestFeature(md.oneofDecl[0], 1);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    final ctx = GenerationContext(GenerationOptions());
    mg.register(ctx);
    mg.resolve(ctx);

    expect(getTestFeature(mg.fieldList[0].features), 1);
  });

  test('MessageGenerator fields can override parent oneof', () {
    fd.edition = Edition.EDITION_2023;
    md.oneofDecl.add(OneofDescriptorProto()..name = 'oneof');
    (md.field..clear()).add(
      FieldDescriptorProto()
        ..name = 'number'
        ..jsonName = 'number'
        ..number = 1
        ..oneofIndex = 0
        ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
        ..type = FieldDescriptorProto_Type.TYPE_STRING,
    );
    setTestFeature(md.oneofDecl[0], 1);
    setTestFeature(md.field[0], 2);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    final ctx = GenerationContext(GenerationOptions());
    mg.register(ctx);
    mg.resolve(ctx);

    expect(getTestFeature(mg.fieldList[0].features), 2);
  });

  test('MessageGenerator oneof inherits from a parent message', () {
    fd.edition = Edition.EDITION_2023;
    md.oneofDecl.add(OneofDescriptorProto()..name = 'oneof');
    (md.field..clear()).add(
      FieldDescriptorProto()
        ..name = 'number'
        ..jsonName = 'number'
        ..number = 1
        ..oneofIndex = 0
        ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
        ..type = FieldDescriptorProto_Type.TYPE_STRING,
    );
    setTestFeature(md, 1);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    final ctx = GenerationContext(GenerationOptions());
    mg.register(ctx);
    mg.resolve(ctx);

    expect(getTestFeature(mg.fieldList[0].features), 1);
  });

  test('MessageGenerator oneof can override parent message', () {
    fd.edition = Edition.EDITION_2023;
    md.oneofDecl.add(OneofDescriptorProto()..name = 'oneof');
    (md.field..clear()).add(
      FieldDescriptorProto()
        ..name = 'number'
        ..jsonName = 'number'
        ..number = 1
        ..oneofIndex = 0
        ..label = FieldDescriptorProto_Label.LABEL_OPTIONAL
        ..type = FieldDescriptorProto_Type.TYPE_STRING,
    );
    setTestFeature(md, 1);
    setTestFeature(md.oneofDecl[0], 2);
    final fg = FileGenerator(testEditionDefaults, fd, GenerationOptions());
    final mg = MessageGenerator.topLevel(md, fg, {}, null, <String>{}, 0);

    final ctx = GenerationContext(GenerationOptions());
    mg.register(ctx);
    mg.resolve(ctx);

    expect(getTestFeature(mg.fieldList[0].features), 2);
  });
}
