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

/// Provides metadata about mixins to dart-protoc-plugin.
/// (Experimental API; subject to change.)
library;

import 'indenting_writer.dart';

/// Finds [name] in the exported mixins.
PbMixin? findMixin(String name) {
  const exportedMixins = {'PbMapMixin': _pbMapMixin};
  return exportedMixins[name];
}

/// PbMixin contains metadata needed by dart-protoc-plugin to apply a mixin.
///
/// The mixin can be applied to a message using options in dart_options.proto.
/// Only one mixin can be applied to each message, but that mixin can depend
/// on another mixin, recursively, similar to single inheritance.
class PbMixin {
  /// The name of the mixin class to import into the .pb.dart file.
  final String name;

  /// The file that the .pb.dart file should import the symbol from.
  final String importFrom;

  /// Another mixin to apply ahead of this one, or null for none.
  final PbMixin? parent;

  /// Names that shouldn't be used by properties in the generated child class.
  /// May be null if the mixin doesn't reserve any new names.
  final List<String>? reservedNames;

  /// Code to inject into the class using the mixin.
  ///
  /// Typically used for static helpers since you cannot mix in static members.
  final List<String>? injectedHelpers;

  /// The well known type name.
  ///
  /// Values should match enumerations in the `WellKnownType` enum from
  /// `package:protobuf`.
  final String? wellKnownType;

  const PbMixin(
    this.name, {
    required this.importFrom,
    this.parent,
    this.reservedNames,
    this.injectedHelpers,
    this.wellKnownType,
  });

  /// Returns the mixin and its ancestors, in the order they should be applied.
  Iterable<PbMixin> findMixinsToApply() {
    final result = [this];
    for (var p = parent; p != null; p = p.parent) {
      result.add(p);
    }
    return result.reversed;
  }

  /// Returns all the reserved names, including from ancestor mixins.
  Iterable<String> findReservedNames() {
    final names = <String>{};
    for (PbMixin? m = this; m != null; m = m.parent) {
      names.add(m.name);
      if (m.reservedNames != null) {
        names.addAll(m.reservedNames!);
      }
    }
    return names;
  }

  void injectHelpers(IndentingWriter out) {
    if (injectedHelpers != null && injectedHelpers!.isNotEmpty) {
      out.println(injectedHelpers!.join('\n'));
    }
  }
}

const _pbMapMixin = PbMixin(
  'PbMapMixin',
  importFrom: 'package:protobuf/src/protobuf/mixins/map_mixin.dart',
  parent: _mapMixin,
);

const List<String> _reservedNamesForMap = [
  '[]',
  '[]=',
  'addAll',
  'addEntries',
  'cast',
  'containsKey',
  'containsValue',
  'entries',
  'forEach',
  'isEmpty',
  'isNotEmpty',
  'keys',
  'length',
  'map',
  'putIfAbsent',
  'remove',
  'removeWhere',
  'retype',
  'update',
  'updateAll',
  'values',
];

const _mapMixin = PbMixin(
  'MapMixin',
  importFrom: 'dart:collection',
  reservedNames: _reservedNamesForMap,
);
