// Copyright (c) 2019, 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:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/fine/requirements.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
import 'package:meta/meta.dart';

/// Failure because of there is no most specific signature in [candidates].
class CandidatesConflict extends Conflict {
  /// The list has at least two items, because the only item is always valid.
  final List<ExecutableElementOrMember> candidates;

  CandidatesConflict({
    required super.name,
    required this.candidates,
  });

  List<ExecutableElement2> get candidates2 =>
      candidates.map((e) => e.asElement2).toList();
}

/// Failure to find a valid signature from superinterfaces.
class Conflict {
  /// The name for which we failed to find a valid signature.
  final Name name;

  Conflict({
    required this.name,
  });
}

/// Failure because of a getter and a method from direct superinterfaces.
class GetterMethodConflict extends Conflict {
  final ExecutableElementOrMember getter;
  final ExecutableElementOrMember method;

  GetterMethodConflict({
    required super.name,
    required this.getter,
    required this.method,
  });

  ExecutableElement2 get getter2 => getter.asElement2;

  ExecutableElement2 get method2 => method.asElement2;
}

/// The extension type has both an extension and non-extension member
/// signature with the same name.
class HasNonExtensionAndExtensionMemberConflict extends Conflict {
  final List<ExecutableElementOrMember> nonExtension;
  final List<ExecutableElementOrMember> extension;

  HasNonExtensionAndExtensionMemberConflict({
    required super.name,
    required this.nonExtension,
    required this.extension,
  });

  List<ExecutableElement2> get extension2 =>
      extension.map((e) => e.asElement2).toList();

  List<ExecutableElement2> get nonExtension2 =>
      nonExtension.map((e) => e.asElement2).toList();
}

/// Manages knowledge about interface types and their members.
class InheritanceManager3 {
  static final _noSuchMethodName =
      Name(null, MethodElement2.NO_SUCH_METHOD_METHOD_NAME);

  /// Cached instance interfaces for [InterfaceElementImpl].
  final Map<InterfaceElementImpl, Interface> _interfaces = {};

  /// The set of classes that are currently being processed, used to detect
  /// self-referencing cycles.
  final Set<InterfaceElementImpl> _processingClasses = {};

  /// Combine [candidates] into a single signature in the [targetClass].
  ///
  /// If such signature does not exist, return `null`, and if [conflicts] is
  /// not `null`, add a new [Conflict] to it.
  ExecutableElementOrMember? combineSignatures({
    required InterfaceElementImpl targetClass,
    required List<ExecutableElementOrMember> candidates,
    required bool doTopMerge,
    required Name name,
    List<Conflict>? conflicts,
  }) {
    // If just one candidate, it is always valid.
    if (candidates.length == 1) {
      return candidates[0];
    }

    var targetLibrary = targetClass.library;
    var typeSystem = targetLibrary.typeSystem;

    var validOverrides = <ExecutableElementOrMember>[];
    for (var i = 0; i < candidates.length; i++) {
      ExecutableElementOrMember? validOverride = candidates[i];
      var validOverrideType = validOverride.type;
      for (var j = 0; j < candidates.length; j++) {
        var candidate = candidates[j];
        if (!typeSystem.isSubtypeOf(validOverrideType, candidate.type)) {
          validOverride = null;
          break;
        }
      }
      if (validOverride != null) {
        validOverrides.add(validOverride);
      }
    }

    if (validOverrides.isEmpty) {
      conflicts?.add(
        CandidatesConflict(
          name: name,
          candidates: candidates,
        ),
      );
      return null;
    }

    if (doTopMerge) {
      var typeSystem = targetClass.library.typeSystem;
      return _topMerge(typeSystem, targetClass, validOverrides);
    } else {
      return validOverrides.first;
    }
  }

  /// Return the result of [getInherited2] with [type] substitution.
  ExecutableElementOrMember? getInherited(InterfaceType type, Name name) {
    type as InterfaceTypeImpl;
    var rawElement = getInherited2(type.element3.asElement, name);
    if (rawElement == null) {
      return null;
    }

    return ExecutableMember.from2(
      rawElement,
      Substitution.fromInterfaceType(type),
    );
  }

  /// Return the most specific signature of the member with the given [name]
  /// that [element] inherits from the mixins, superclasses, or interfaces;
  /// or `null` if no member is inherited because the member is not declared
  /// at all, or because there is no the most specific signature.
  ///
  /// This is equivalent to `getInheritedMap2(type)[name]`.
  ExecutableElementOrMember? getInherited2(
      InterfaceElementImpl element, Name name) {
    return getInheritedMap2(element)[name];
  }

  /// Returns the result of [getInherited2] with [type] substitution.
  // This is a replacement for `getInherited`.
  @experimental
  ExecutableElement2? getInherited3(InterfaceType type, Name name) {
    var element = getInherited(type, name);
    return element?.asElement2;
  }

  /// Returns the most specific signature of the member with the given [name]
  /// that [element] inherits from the mixins, superclasses, or interfaces.
  ///
  /// Returns `null` if no member is inherited because the member is not
  /// declared at all, or because there is no the most specific signature.
  ///
  /// This is equivalent to `getInheritedMap(type)[name]`.
  // This is a replacement for `getInherited2`.
  @experimental
  ExecutableElement2OrMember? getInherited4(
      InterfaceElement2 element, Name name) {
    element as InterfaceElementImpl2; // TODO(scheglov): remove cast
    var oldElement = getInherited2(element.asElement, name);
    return oldElement?.asElement2;
  }

  /// Returns signatures of all concrete members that the given [element]
  /// inherits from the superclasses and mixins.
  @experimental
  Map<Name, ExecutableElement2> getInheritedConcreteMap(
      InterfaceElement2 element) {
    element as InterfaceElementImpl2; // TODO(scheglov): remove cast
    var map = getInheritedConcreteMap2(element.asElement);
    return map.mapValue((element) => element.asElement2);
  }

  /// Return signatures of all concrete members that the given [element] inherits
  /// from the superclasses and mixins.
  Map<Name, ExecutableElementOrMember> getInheritedConcreteMap2(
      InterfaceElementImpl element) {
    if (element is ExtensionTypeElementImpl) {
      return const {};
    }

    var interface = getInterface(element);
    return interface.superImplemented.last;
  }

  /// Returns the mapping from names to most specific signatures of members
  /// inherited from the super-interfaces (superclasses, mixins, and
  /// interfaces).
  ///
  /// If there is no most specific signature for a name, the corresponding name
  /// will not be included.
  @experimental
  Map<Name, ExecutableElement2> getInheritedMap(InterfaceElement2 element) {
    element as InterfaceElementImpl2; // TODO(scheglov): remove cast
    var map = getInheritedMap2(element.asElement);
    return map.mapValue((element) => element.asElement2);
  }

  /// Return the mapping from names to most specific signatures of members
  /// inherited from the super-interfaces (superclasses, mixins, and
  /// interfaces).  If there is no most specific signature for a name, the
  /// corresponding name will not be included.
  Map<Name, ExecutableElementOrMember> getInheritedMap2(
    InterfaceElementImpl element,
  ) {
    var interface = getInterface(element);
    var inheritedMap = interface.inheritedMap;
    if (inheritedMap == null) {
      inheritedMap = interface.inheritedMap = {};
      _findMostSpecificFromNamedCandidates(
        element,
        inheritedMap,
        element is ExtensionTypeElementImpl
            ? interface.redeclared
            : interface.overridden,
        doTopMerge: false,
      );
    }
    return inheritedMap;
  }

  /// Return the interface of the given [element].  It might include
  /// private members, not necessary accessible in all libraries.
  Interface getInterface(InterfaceElementImpl element) {
    var result = _interfaces[element];
    if (result != null) {
      return result;
    }
    _interfaces[element] = Interface._empty;

    if (!_processingClasses.add(element)) {
      return Interface._empty;
    }

    try {
      if (element is ExtensionTypeElementImpl) {
        result = _getInterfaceExtensionType(element);
      } else if (element is MixinElementImpl) {
        result = _getInterfaceMixin(element);
      } else {
        result = _getInterfaceClass(element);
      }
    } finally {
      _processingClasses.remove(element);
    }

    _interfaces[element] = result;
    return result;
  }

  /// Returns the interface of the given [element].
  ///
  /// The interface might include private members, not necessary accessible in
  /// all libraries.
  @experimental
  Interface getInterface2(InterfaceElement2 element) {
    element as InterfaceElementImpl2; // TODO(scheglov): remove cast
    return getInterface(element.asElement);
  }

  /// Return the result of [getMember2] with [type] substitution.
  ExecutableElementOrMember? getMember(
    InterfaceType type,
    Name name, {
    bool concrete = false,
    int forMixinIndex = -1,
    bool forSuper = false,
  }) {
    type as InterfaceTypeImpl; // TODO(scheglov): remove cast
    var rawElement = getMember2(
      type.element3.asElement,
      name,
      concrete: concrete,
      forMixinIndex: forMixinIndex,
      forSuper: forSuper,
    );
    if (rawElement == null) {
      return null;
    }

    var substitution = Substitution.fromInterfaceType(type);
    return ExecutableMember.from2(rawElement, substitution);
  }

  /// Return the member with the given [name].
  ///
  /// If [concrete] is `true`, the concrete implementation is returned,
  /// from the given [element], or its superclass.
  ///
  /// If [forSuper] is `true`, then [concrete] is implied, and only concrete
  /// members from the superclass are considered.
  ///
  /// If [forMixinIndex] is specified, only the nominal superclass, and the
  /// given number of mixins after it are considered.  For example for `1` in
  /// `class C extends S with M1, M2, M3`, only `S` and `M1` are considered.
  ExecutableElementOrMember? getMember2(
    InterfaceElementImpl element,
    Name name, {
    bool concrete = false,
    int forMixinIndex = -1,
    bool forSuper = false,
  }) {
    globalResultRequirements?.notifyInterfaceRequest(
        element: element.asElement2, nameObj: name);
    var interface = getInterface(element);
    if (forSuper) {
      if (element is ExtensionTypeElementImpl) {
        return null;
      }
      var superImplemented = interface.superImplemented;
      if (forMixinIndex >= 0) {
        return superImplemented[forMixinIndex][name];
      }
      if (superImplemented.isNotEmpty) {
        return superImplemented.last[name];
      } else {
        assert(element.name == 'Object');
        return null;
      }
    }
    if (concrete) {
      return interface.implemented[name];
    }
    return interface.map[name];
  }

  /// Returns the result of [getMember4] with [type] substitution.
  // This is a replacement for `getMember`.
  @experimental
  ExecutableElement2? getMember3(
    InterfaceType type,
    Name name, {
    bool concrete = false,
    int forMixinIndex = -1,
    bool forSuper = false,
  }) {
    var element = getMember(
      type,
      name,
      concrete: concrete,
      forMixinIndex: forMixinIndex,
      forSuper: forSuper,
    );
    return element?.asElement2;
  }

  /// Returns the member with the given [name].
  ///
  /// If [concrete] is `true`, the concrete implementation is returned, whether
  /// from the given [element] or its superclass.
  ///
  /// If [forSuper] is `true`, then [concrete] is implied, and only concrete
  /// members from the superclass are considered.
  ///
  /// If [forMixinIndex] is specified, only the nominal superclass, and the
  /// given number of mixins after it are considered. For example for `1` in
  /// `class C extends S with M1, M2, M3`, only `S` and `M1` are considered.
  // This is a replacement for `getMember2`.
  @experimental
  ExecutableElement2OrMember? getMember4(
    InterfaceElement2 element,
    Name name, {
    bool concrete = false,
    int forMixinIndex = -1,
    bool forSuper = false,
  }) {
    element as InterfaceElementImpl2; // TODO(scheglov): remove cast
    var oldElement = getMember2(
      element.asElement,
      name,
      concrete: concrete,
      forMixinIndex: forMixinIndex,
      forSuper: forSuper,
    );
    return oldElement?.asElement2;
  }

  /// Returns all members of mixins, superclasses, and interfaces that a member
  /// with the given [name], defined in the [element], would override.
  ///
  /// Returns `null` if no members would be overridden.
  @experimental
  List<ExecutableElement2>? getOverridden(
      InterfaceElement2 element, Name name) {
    element as InterfaceElementImpl2; // TODO(scheglov): remove cast
    var elements = getOverridden2(
      element.asElement,
      name,
    );
    if (elements == null) {
      return null;
    }
    return elements.map((fragment) => fragment.asElement2).toList();
  }

  /// Return all members of mixins, superclasses, and interfaces that a member
  /// with the given [name], defined in the [element], would override; or `null`
  /// if no members would be overridden.
  List<ExecutableElementOrMember>? getOverridden2(
      InterfaceElementImpl element, Name name) {
    var interface = getInterface(element);
    return interface.overridden[name];
  }

  /// Return all members of mixins, superclasses, and interfaces that a member
  /// with the given [name], defined in the [element], would override; or `null`
  /// if no members would be overridden.
  List<ExecutableElement2>? getOverridden4(
      InterfaceElement2 element, Name name) {
    var interface = getInterface2(element);
    var fragments = interface.overridden[name];
    return fragments?.map((fragment) => fragment.asElement2).toList();
  }

  /// Remove interfaces for classes defined in specified libraries.
  void removeOfLibraries(Set<Uri> uriSet) {
    _interfaces.removeWhere((element, _) {
      return uriSet.contains(element.librarySource.uri);
    });
  }

  void _addCandidates({
    required Map<Name, List<ExecutableElementOrMember>> namedCandidates,
    required MapSubstitution substitution,
    required Interface interface,
  }) {
    var map = interface.map;
    for (var entry in map.entries) {
      var name = entry.key;
      var candidate = entry.value;

      candidate = ExecutableMember.from2(candidate, substitution);

      var candidates = namedCandidates[name];
      if (candidates == null) {
        candidates = <ExecutableElementOrMember>[];
        namedCandidates[name] = candidates;
      }

      candidates.add(candidate);
    }
  }

  void _addImplemented(
    Map<Name, ExecutableElementOrMember> implemented,
    InterfaceElementImpl fragment,
    InterfaceElementImpl2 element,
  ) {
    var libraryUri = fragment.librarySource.uri;

    void addMember(ExecutableElementOrMember member) {
      if (!member.isAbstract && !member.isStatic) {
        var name = Name(libraryUri, member.name);
        implemented[name] = member;
      }
    }

    element.methods2.map((e) => e.asElement).forEach(addMember);
    element.getters2.map((e) => e.asElement).forEach(addMember);
    element.setters2.map((e) => e.asElement).forEach(addMember);
  }

  void _addMixinMembers({
    required Map<Name, ExecutableElementOrMember> implemented,
    required MapSubstitution substitution,
    required Interface mixin,
  }) {
    for (var entry in mixin.implemented.entries) {
      var executable = entry.value;
      if (executable.isAbstract) {
        continue;
      }

      var class_ = executable.asElement2.enclosingElement2;
      if (class_ is ClassElement2 && class_.isDartCoreObject) {
        continue;
      }

      executable = ExecutableMember.from2(executable, substitution);

      implemented[entry.key] = executable;
    }
  }

  /// Check that all [candidates] for the given [name] have the same kind, all
  /// getters, all methods, or all setter.  If a conflict found, return the
  /// new [Conflict] instance that describes it.
  Conflict? _checkForGetterMethodConflict(
      Name name, List<ExecutableElementOrMember> candidates) {
    assert(candidates.length > 1);

    ExecutableElementOrMember? getter;
    ExecutableElementOrMember? method;
    for (var candidate in candidates) {
      var kind = candidate.kind;
      if (kind == ElementKind.GETTER) {
        getter ??= candidate;
      }
      if (kind == ElementKind.METHOD) {
        method ??= candidate;
      }
    }

    if (getter == null || method == null) {
      return null;
    } else {
      return GetterMethodConflict(name: name, getter: getter, method: method);
    }
  }

  /// The given [namedCandidates] maps names to candidates from direct
  /// superinterfaces.  Find the most specific signature, and put it into the
  /// [map], if there is no one yet (from the class itself).  If there is no
  /// such single most specific signature (i.e. no valid override), then add a
  /// new conflict description.
  List<Conflict> _findMostSpecificFromNamedCandidates(
    InterfaceElementImpl targetClass,
    Map<Name, ExecutableElementOrMember> map,
    Map<Name, List<ExecutableElementOrMember>> namedCandidates, {
    required bool doTopMerge,
  }) {
    var conflicts = <Conflict>[];

    for (var entry in namedCandidates.entries) {
      var name = entry.key;
      var candidates = entry.value;

      // There is no way to resolve the getter / method conflict.
      if (candidates.length > 1) {
        var conflict = _checkForGetterMethodConflict(name, candidates);
        if (conflict != null) {
          conflicts.add(conflict);
          continue;
        }
      }

      if (map.containsKey(name)) {
        continue;
      }

      var combinedSignature = combineSignatures(
        targetClass: targetClass,
        candidates: candidates,
        doTopMerge: doTopMerge,
        name: name,
        conflicts: conflicts,
      );

      if (combinedSignature != null) {
        map[name] = combinedSignature;
        continue;
      }
    }

    return conflicts;
  }

  Interface _getInterfaceClass(InterfaceElementImpl fragment) {
    var element = fragment.element;

    var namedCandidates = <Name, List<ExecutableElementOrMember>>{};
    var superImplemented = <Map<Name, ExecutableElementOrMember>>[];
    var implemented = <Name, ExecutableElementOrMember>{};

    InterfaceType? superType = fragment.supertype;

    Interface? superTypeInterface;
    if (superType != null) {
      superType as InterfaceTypeImpl;
      var substitution = Substitution.fromInterfaceType(superType);
      superTypeInterface = getInterface2(superType.element3);
      _addCandidates(
        namedCandidates: namedCandidates,
        substitution: substitution,
        interface: superTypeInterface,
      );

      for (var entry in superTypeInterface.implemented.entries) {
        var executable = entry.value;
        executable = ExecutableMember.from2(executable, substitution);
        implemented[entry.key] = executable;
      }

      superImplemented.add(implemented);
    }

    // TODO(scheglov): Handling of members for super and mixins is not
    // optimal. We always have just one member for each name in super,
    // multiple candidates happen only when we merge super and multiple
    // interfaces. Consider using `Map<Name, ExecutableElement>` here.
    var mixinsConflicts = <List<Conflict>>[];
    for (var mixin in element.mixins) {
      var mixinElement = mixin.element3;
      var substitution = Substitution.fromInterfaceType(mixin);
      var mixinInterface = getInterface2(mixinElement);
      // `class X extends S with M1, M2 {}` is semantically a sequence of:
      //     class S&M1 extends S implements M1 {
      //       // declared M1 members
      //     }
      //     class S&M2 extends S&M1 implements M2 {
      //       // declared M2 members
      //     }
      //     class X extends S&M2 {
      //       // declared X members
      //     }
      // So, each mixin always replaces members in the interface.
      // And there are individual override conflicts for each mixin.
      var candidatesFromSuperAndMixin =
          <Name, List<ExecutableElementOrMember>>{};
      var mixinConflicts = <Conflict>[];
      for (var entry in mixinInterface.map.entries) {
        var name = entry.key;
        var candidate = ExecutableMember.from2(
          entry.value,
          substitution,
        );
        var candidate2 = candidate.asElement2;

        var currentList = namedCandidates[name];
        if (currentList == null) {
          namedCandidates[name] = [candidate];
          continue;
        }

        var current = currentList.single;
        if (candidate2.enclosingElement2 == mixinElement) {
          namedCandidates[name] = [candidate];
          if (current.kind != candidate.kind) {
            var currentIsGetter = current.kind == ElementKind.GETTER;
            mixinConflicts.add(
              GetterMethodConflict(
                name: name,
                getter: currentIsGetter ? current : candidate,
                method: currentIsGetter ? candidate : current,
              ),
            );
          }
        } else {
          candidatesFromSuperAndMixin[name] = [current, candidate];
        }
      }

      // Merge members from the superclass and the mixin interface.
      {
        var map = <Name, ExecutableElementOrMember>{};
        _findMostSpecificFromNamedCandidates(
          fragment,
          map,
          candidatesFromSuperAndMixin,
          doTopMerge: true,
        );
        for (var entry in map.entries) {
          namedCandidates[entry.key] = [entry.value];
        }
      }

      mixinsConflicts.add(mixinConflicts);

      implemented = Map.of(implemented);
      _addMixinMembers(
        implemented: implemented,
        substitution: substitution,
        mixin: mixinInterface,
      );

      superImplemented.add(implemented);
    }

    for (var interface in element.interfaces) {
      _addCandidates(
        namedCandidates: namedCandidates,
        substitution: Substitution.fromInterfaceType(interface),
        interface: getInterface2(interface.element3),
      );
    }

    implemented = Map.of(implemented);
    _addImplemented(implemented, fragment, element);

    // If a class declaration has a member declaration, the signature of that
    // member declaration becomes the signature in the interface.
    var declared = _getTypeMembers(fragment, element);

    // If a class declaration does not have a member declaration with a
    // particular name, but some super-interfaces do have a member with that
    // name, it's a compile-time error if there is no signature among the
    // super-interfaces that is a valid override of all the other
    // super-interface signatures with the same name. That "most specific"
    // signature becomes the signature of the class's interface.
    var interface = Map.of(declared);
    List<Conflict> conflicts = _findMostSpecificFromNamedCandidates(
      fragment,
      interface,
      namedCandidates,
      doTopMerge: true,
    );

    var noSuchMethodForwarders = <Name>{};
    if (fragment is ClassElementImpl && fragment.isAbstract) {
      if (superTypeInterface != null) {
        noSuchMethodForwarders = superTypeInterface.noSuchMethodForwarders;
      }
    } else {
      var noSuchMethod = implemented[_noSuchMethodName];
      if (noSuchMethod != null && !_isDeclaredInObject(noSuchMethod)) {
        var superForwarders = superTypeInterface?.noSuchMethodForwarders;
        for (var entry in interface.entries) {
          var name = entry.key;
          if (!implemented.containsKey(name) ||
              superForwarders != null && superForwarders.contains(name)) {
            implemented[name] = entry.value;
            noSuchMethodForwarders.add(name);
          }
        }
      }
    }

    // TODO(scheglov): Instead of merging conflicts we could report them on
    // the corresponding mixins applied in the class.
    for (var mixinConflicts in mixinsConflicts) {
      if (mixinConflicts.isNotEmpty) {
        conflicts.addAll(mixinConflicts);
      }
    }

    implemented =
        implemented.map<Name, ExecutableElementOrMember>((key, value) {
      var result = _inheritCovariance(fragment, namedCandidates, key, value);
      return MapEntry(key, result);
    });

    var implemented2 = implemented.mapValue((value) => value.asElement2);

    var namedCandidates2 = namedCandidates.map<Name, List<ExecutableElement2>>(
        (key, value) => MapEntry(key, value.map((e) => e.asElement2).toList()));

    var superImplemented2 = superImplemented
        .map((e) => e.map<Name, ExecutableElement2>(
            (key, value) => MapEntry(key, value.asElement2)))
        .toList();

    return Interface._(
      map: interface,
      declared: declared,
      implemented: implemented,
      implemented2: implemented2,
      noSuchMethodForwarders: noSuchMethodForwarders,
      overridden: namedCandidates,
      overridden2: namedCandidates2,
      redeclared: const {},
      redeclared2: const {},
      superImplemented: superImplemented,
      superImplemented2: superImplemented2,
      conflicts: conflicts.toFixedList(),
    );
  }

  /// See https://github.com/dart-lang/language
  ///   blob/main/accepted/future-releases/extension-types/feature-specification.md
  ///   #static-analysis-of-an-extension-type-member-invocation
  ///
  /// We handle "has an extension type member" and "has a non-extension type
  /// member" portions, considering redeclaration and conflicts.
  Interface _getInterfaceExtensionType(ExtensionTypeElementImpl fragment) {
    var element = fragment.element;

    // Add instance members implemented by the element itself.
    var declared = <Name, ExecutableElementOrMember>{};
    _addImplemented(declared, fragment, element);

    // Prepare precluded names.
    var precludedNames = <Name>{};
    var precludedMethods = <Name>{};
    var precludedSetters = <Name>{};
    for (var entry in declared.entries) {
      var name = entry.key;
      precludedNames.add(name);
      switch (entry.value) {
        case MethodElementOrMember():
          precludedSetters.add(name.forSetter);
        case PropertyAccessorElementOrMember(isSetter: true):
          precludedMethods.add(name.forGetter);
      }
    }

    // These declared members take precedence over "inherited" ones.
    var implemented = Map.of(declared);

    // Prepare candidates for inheritance.
    var extensionCandidates = <Name, _ExtensionTypeCandidates>{};
    var notExtensionCandidates = <Name, _ExtensionTypeCandidates>{};
    for (var interface in element.interfaces) {
      var substitution = Substitution.fromInterfaceType(interface);
      for (var entry in getInterface2(interface.element3).map.entries) {
        var name = entry.key;
        var executable = ExecutableMember.from2(entry.value, substitution);
        if (executable.isExtensionTypeMember) {
          (extensionCandidates[name] ??= _ExtensionTypeCandidates(name))
              .add(executable);
        } else {
          (notExtensionCandidates[name] ??= _ExtensionTypeCandidates(name))
              .add(executable);
        }
      }
    }

    var redeclared = <Name, List<ExecutableElementOrMember>>{};
    var conflicts = <Conflict>[];

    // Add extension type members.
    for (var entry in extensionCandidates.entries) {
      var name = entry.key;
      var candidates = entry.value;
      (redeclared[name] ??= []).addAll(candidates.all);

      var notPrecluded = candidates.notPrecluded(
        precludedNames: precludedNames,
        precludedMethods: precludedMethods,
        precludedSetters: precludedSetters,
      );

      // Stop if all precluded.
      if (notPrecluded.isEmpty) {
        continue;
      }

      // If not precluded, can have either non-extension, or extension.
      var nonExtensionSignatures = notExtensionCandidates[name];
      if (nonExtensionSignatures != null) {
        var notExtensionNotPrecluded = nonExtensionSignatures.notPrecluded(
          precludedNames: precludedNames,
          precludedMethods: precludedMethods,
          precludedSetters: precludedSetters,
        );
        if (notExtensionNotPrecluded.isNotEmpty) {
          conflicts.add(
            HasNonExtensionAndExtensionMemberConflict(
              name: name,
              nonExtension: notExtensionNotPrecluded,
              extension: notPrecluded,
            ),
          );
        }
        continue;
      }

      // The inherited member must be unique.
      ExecutableElementOrMember? uniqueElement;
      for (var candidate in notPrecluded) {
        if (uniqueElement == null) {
          uniqueElement = candidate;
        } else if (uniqueElement.asElement2.baseElement !=
            candidate.asElement2.baseElement) {
          uniqueElement = null;
          break;
        }
      }

      if (uniqueElement == null) {
        conflicts.add(
          NotUniqueExtensionMemberConflict(
            name: name,
            candidates: notPrecluded,
          ),
        );
        continue;
      }

      implemented[name] = uniqueElement;
    }

    // Add non-extension type members.
    for (var entry in notExtensionCandidates.entries) {
      var name = entry.key;
      var candidates = entry.value;
      (redeclared[name] ??= []).addAll(candidates.all);

      var notPrecluded = candidates.notPrecluded(
        precludedNames: precludedNames,
        precludedMethods: precludedMethods,
        precludedSetters: precludedSetters,
      );

      // Stop if all precluded.
      if (notPrecluded.isEmpty) {
        continue;
      }

      // Skip, if also has extension candidates.
      // The conflict is already reported.
      if (extensionCandidates.containsKey(name)) {
        continue;
      }

      var combinedSignature = combineSignatures(
        targetClass: fragment,
        candidates: notPrecluded,
        doTopMerge: true,
        name: name,
      );

      if (combinedSignature == null) {
        conflicts.add(
          CandidatesConflict(
            name: name,
            candidates: notPrecluded,
          ),
        );
        continue;
      }

      implemented[name] = combinedSignature;
    }

    // Ensure unique overridden elements.
    var uniqueRedeclared = <Name, List<ExecutableElementOrMember>>{};
    for (var entry in redeclared.entries) {
      var name = entry.key;
      var elements = entry.value;
      if (elements.length == 1) {
        uniqueRedeclared[name] = elements;
      } else {
        uniqueRedeclared[name] = elements.toSet().toFixedList();
      }
    }

    var uniqueRedeclared2 = <Name, List<ExecutableElement2>>{};
    for (var entry in redeclared.entries) {
      var name = entry.key;
      var fragments = entry.value.map((fragment) => fragment.asElement2);
      if (fragments.length == 1) {
        uniqueRedeclared2[name] = fragments.toFixedList();
      } else {
        var uniqueElements = <ExecutableElement2>{};
        for (var fragment in fragments) {
          uniqueElements.add(fragment);
        }
        uniqueRedeclared2[name] = uniqueElements.toFixedList();
      }
    }

    var implemented2 = implemented.mapValue((value) => value.asElement2);

    return Interface._(
      map: implemented,
      declared: declared,
      implemented: implemented,
      implemented2: implemented2,
      noSuchMethodForwarders: const {},
      overridden: const {},
      overridden2: const {},
      redeclared: uniqueRedeclared,
      redeclared2: uniqueRedeclared2,
      superImplemented: const [],
      superImplemented2: const [],
      conflicts: conflicts.toFixedList(),
    );
  }

  Interface _getInterfaceMixin(MixinElementImpl fragment) {
    var element = fragment.element;

    var superCandidates = <Name, List<ExecutableElementOrMember>>{};
    for (var constraint in element.superclassConstraints) {
      var substitution = Substitution.fromInterfaceType(constraint);
      var interfaceObj = getInterface2(constraint.element3);
      _addCandidates(
        namedCandidates: superCandidates,
        substitution: substitution,
        interface: interfaceObj,
      );
    }

    // `mixin M on S1, S2 {}` can call using `super` any instance member
    // from its superclass constraints, whether it is abstract or concrete.
    var superInterface = <Name, ExecutableElementOrMember>{};
    var superConflicts = _findMostSpecificFromNamedCandidates(
      fragment,
      superInterface,
      superCandidates,
      doTopMerge: true,
    );

    var interfaceCandidates = Map.of(superCandidates);
    for (var interface in element.interfaces) {
      _addCandidates(
        namedCandidates: interfaceCandidates,
        substitution: Substitution.fromInterfaceType(interface),
        interface: getInterface2(interface.element3),
      );
    }

    var declared = _getTypeMembers(fragment, element);

    var interface = Map.of(declared);
    var interfaceConflicts = _findMostSpecificFromNamedCandidates(
      fragment,
      interface,
      interfaceCandidates,
      doTopMerge: true,
    );

    var implemented = <Name, ExecutableElementOrMember>{};
    _addImplemented(implemented, fragment, element);

    var implemented2 = implemented.mapValue((value) => value.asElement2);

    var interfaceCandidates2 =
        interfaceCandidates.map<Name, List<ExecutableElement2>>((key, value) =>
            MapEntry(key, value.map((e) => e.asElement2).toList()));

    var superInterface2 = superInterface.mapValue((value) => value.asElement2);

    return Interface._(
      map: interface,
      declared: declared,
      implemented: implemented,
      implemented2: implemented2,
      noSuchMethodForwarders: {},
      overridden: interfaceCandidates,
      overridden2: interfaceCandidates2,
      redeclared: const {},
      redeclared2: const {},
      superImplemented: [superInterface],
      superImplemented2: [superInterface2],
      conflicts: <Conflict>[
        ...superConflicts,
        ...interfaceConflicts,
      ].toFixedList(),
    );
  }

  /// If a candidate from [namedCandidates] has covariant parameters, return
  /// a copy of the [executable] with the corresponding parameters marked
  /// covariant. If there are no covariant parameters, or parameters to
  /// update are already covariant, return the [executable] itself.
  ExecutableElementOrMember _inheritCovariance(
    InterfaceElementImpl class_,
    Map<Name, List<ExecutableElementOrMember>> namedCandidates,
    Name name,
    ExecutableElementOrMember executable,
  ) {
    if (executable.asElement2.enclosingElement2 == class_.asElement2) {
      return executable;
    }

    var parameters = executable.parameters;
    if (parameters.isEmpty) {
      return executable;
    }

    var candidates = namedCandidates[name];
    if (candidates == null) {
      return executable;
    }

    // Find parameters that are covariant (by declaration) in any overridden.
    Set<_ParameterDesc>? covariantParameters;
    for (var candidate in candidates) {
      var parameters = candidate.parameters;
      for (var i = 0; i < parameters.length; i++) {
        var parameter = parameters[i];
        if (parameter.isCovariant) {
          covariantParameters ??= {};
          covariantParameters.add(
            _ParameterDesc(i, parameter),
          );
        }
      }
    }

    if (covariantParameters == null) {
      return executable;
    }

    // Update covariance of the parameters of the chosen executable.
    List<ParameterElementImpl>? transformedParameters;
    for (var index = 0; index < parameters.length; index++) {
      var parameter = parameters[index];
      var shouldBeCovariant = covariantParameters.contains(
        _ParameterDesc(index, parameter),
      );
      if (parameter.isCovariant != shouldBeCovariant) {
        transformedParameters ??= [
          for (var parameter in parameters) parameter.declaration
        ];
        transformedParameters[index] = parameter.declaration.copyWith(
          isCovariant: shouldBeCovariant,
        );
      }
    }

    if (transformedParameters == null) {
      return executable;
    }

    if (executable is MethodElementOrMember) {
      var result = MethodElementImpl(executable.name, -1);
      result.enclosingElement3 = class_;
      result.isSynthetic = true;
      result.parameters = transformedParameters;
      result.returnType = executable.returnType;
      result.typeParameters = executable.typeParameters;
      return result;
    }

    if (executable is SetterFragmentImpl) {
      var result = SetterFragmentImpl(executable.name, -1);
      result.enclosingElement3 = class_;
      result.isSynthetic = true;
      result.parameters = transformedParameters;
      result.returnType = executable.returnType;

      var field = executable.variable2!;
      var resultField = FieldElementImpl(field.name, -1);
      resultField.enclosingElement3 = class_;
      resultField.getter = field.getter;
      resultField.setter = executable;
      resultField.type = executable.parameters[0].type;
      result.variable2 = resultField;

      return result;
    }

    return executable;
  }

  /// Given one or more [validOverrides], merge them into a single resulting
  /// signature. This signature always exists.
  ExecutableElementOrMember _topMerge(
    TypeSystemImpl typeSystem,
    InterfaceElementImpl targetClass,
    List<ExecutableElementOrMember> validOverrides,
  ) {
    var first = validOverrides[0];

    if (validOverrides.length == 1) {
      return first;
    }

    var firstType = first.type;
    var allTypesEqual = true;
    for (var executable in validOverrides) {
      if (executable.type != firstType) {
        allTypesEqual = false;
        break;
      }
    }

    if (allTypesEqual) {
      return first;
    }

    var resultType = validOverrides.map((e) {
      return typeSystem.normalize(e.type) as FunctionTypeImpl;
    }).reduce((previous, next) {
      return typeSystem.topMerge(previous, next) as FunctionTypeImpl;
    });

    for (var executable in validOverrides) {
      if (executable.type == resultType) {
        return executable;
      }
    }

    if (first is MethodElementOrMember) {
      var firstMethod = first;
      var fragmentName = first.asElement2.firstFragment.name2;
      var result = MethodElementImpl(firstMethod.name, -1);
      result.enclosingElement3 = targetClass;
      result.name2 = fragmentName;
      result.typeParameters = resultType.typeFormals;
      result.returnType = resultType.returnType;
      // TODO(scheglov): check if can type cast instead
      result.parameters = resultType.parameters.cast();
      return result;
    } else {
      var firstAccessor = first as PropertyAccessorElementOrMember;
      var fragmentName = first.asElement2.firstFragment.name2;
      var variableName = firstAccessor.displayName;
      var field = FieldElementImpl(variableName, -1);

      PropertyAccessorElementImpl result;
      if (firstAccessor.isGetter) {
        field.getter = result = GetterFragmentImpl(variableName, -1);
      } else {
        field.setter = result = SetterFragmentImpl(variableName, -1);
      }
      result.enclosingElement3 = targetClass;
      result.name2 = fragmentName;
      result.returnType = resultType.returnType;
      // TODO(scheglov): check if can type cast instead
      result.parameters = resultType.parameters.cast();

      field.enclosingElement3 = targetClass;
      field.name2 = fragmentName;
      if (firstAccessor.isGetter) {
        field.type = result.returnType;
      } else {
        field.type = result.parameters[0].type;
      }
      result.variable2 = field;

      return result;
    }
  }

  static Map<Name, ExecutableElementOrMember> _getTypeMembers(
    InterfaceElementImpl fragment,
    InterfaceElementImpl2 element,
  ) {
    var declared = <Name, ExecutableElementOrMember>{};
    var libraryUri = fragment.librarySource.uri;

    void addMember(ExecutableElementOrMember member) {
      if (!member.isStatic) {
        var name = Name(libraryUri, member.name);
        declared[name] = member;
      }
    }

    element.methods2.map((e) => e.asElement).forEach(addMember);
    element.getters2.map((e) => e.asElement).forEach(addMember);
    element.setters2.map((e) => e.asElement).forEach(addMember);

    return declared;
  }

  static bool _isDeclaredInObject(ExecutableElementOrMember element) {
    var enclosing = element.asElement2.enclosingElement2;
    return enclosing is ClassElement2 && enclosing.isDartCoreObject;
  }
}

/// The instance interface of an [InterfaceType].
class Interface {
  static final _empty = Interface._(
    map: const {},
    declared: const {},
    implemented: const {},
    implemented2: const {},
    noSuchMethodForwarders: <Name>{},
    overridden: const {},
    overridden2: const {},
    redeclared: const {},
    redeclared2: const {},
    superImplemented: const [{}],
    superImplemented2: const [{}],
    conflicts: const [],
  );

  /// The map of names to their signature in the interface.
  final Map<Name, ExecutableElementOrMember> map;

  /// The map of declared names to their signatures.
  final Map<Name, ExecutableElementOrMember> declared;

  /// The map of names to their concrete implementations.
  final Map<Name, ExecutableElementOrMember> implemented;

  /// The map of names to their concrete implementations.
  final Map<Name, ExecutableElement2OrMember> implemented2;

  /// The set of names that are `noSuchMethod` forwarders in [implemented].
  final Set<Name> noSuchMethodForwarders;

  /// The map of names to their signatures from the mixins, superclasses,
  /// or interfaces.
  final Map<Name, List<ExecutableElementOrMember>> overridden;

  /// The map of names to their signatures from the mixins, superclasses,
  /// or interfaces.
  final Map<Name, List<ExecutableElement2>> overridden2;

  /// The map of names to the signatures from superinterfaces that a member
  /// declaration in this extension type redeclares.
  final Map<Name, List<ExecutableElementOrMember>> redeclared;

  /// The map of names to the signatures from superinterfaces that a member
  /// declaration in this extension type redeclares.
  final Map<Name, List<ExecutableElement2>> redeclared2;

  /// Each item of this list maps names to their concrete implementations.
  /// The first item of the list is the nominal superclass, next the nominal
  /// superclass plus the first mixin, etc. So, for the class like
  /// `class C extends S with M1, M2`, we get `[S, S&M1, S&M1&M2]`.
  final List<Map<Name, ExecutableElementOrMember>> superImplemented;

  /// Each item of this list maps names to their concrete implementations.
  /// The first item of the list is the nominal superclass, next the nominal
  /// superclass plus the first mixin, etc. So, for the class like
  /// `class C extends S with M1, M2`, we get `[S, S&M1, S&M1&M2]`.
  final List<Map<Name, ExecutableElement2>> superImplemented2;

  /// The list of conflicts between superinterfaces - the nominal superclass,
  /// mixins, and interfaces.  Does not include conflicts with the declared
  /// members of the class.
  final List<Conflict> conflicts;

  /// The map of names to the most specific signatures from the mixins,
  /// superclasses, or interfaces.
  Map<Name, ExecutableElementOrMember>? inheritedMap;

  Interface._({
    required this.map,
    required this.declared,
    required this.implemented,
    required this.implemented2,
    required this.noSuchMethodForwarders,
    required this.overridden,
    required this.overridden2,
    required this.redeclared,
    required this.redeclared2,
    required this.superImplemented,
    required this.superImplemented2,
    required this.conflicts,
  });

  /// The map of declared names to their signatures.
  @experimental
  Map<Name, ExecutableElement2> get declared2 {
    return declared.mapValue((element) => element.asElement2);
  }

  /// The map of names to the most specific signatures from the mixins,
  /// superclasses, or interfaces.
  Map<Name, ExecutableElement2>? get inheritedMap2 {
    if (inheritedMap == null) {
      return null;
    }
    var inheritedMap2 = <Name, ExecutableElement2>{};
    for (var entry in inheritedMap!.entries) {
      inheritedMap2[entry.key] = entry.value.asElement2;
    }
    return inheritedMap2;
  }

  /// The map of names to their signature in the interface.
  @experimental
  Map<Name, ExecutableElement2OrMember> get map2 {
    return map.mapValue((element) => element.asElement2);
  }

  /// Return `true` if the [name] is implemented in the supertype.
  bool isSuperImplemented(Name name) {
    return superImplemented.last.containsKey(name);
  }
}

/// A public name, or a private name qualified by a library URI.
@AnalyzerPublicApi(message: 'Exposed by InterfaceElement2 methods')
class Name {
  /// If the name is private, the URI of the defining library.
  /// Otherwise, it is `null`.
  final Uri? libraryUri;

  /// The name of this name object.
  /// If the name starts with `_`, then the name is private.
  /// Names of setters end with `=`.
  final String name;

  /// Precomputed
  final bool isPublic;

  /// The cached, pre-computed hash code.
  @override
  final int hashCode;

  factory Name(Uri? libraryUri, String name) {
    if (name.startsWith('_')) {
      var hashCode = Object.hash(libraryUri, name);
      return Name._internal(libraryUri, name, false, hashCode);
    } else {
      return Name._internal(null, name, true, name.hashCode);
    }
  }

  factory Name.forLibrary(LibraryElement2? library, String name) {
    return Name(library?.uri, name);
  }

  Name._internal(this.libraryUri, this.name, this.isPublic, this.hashCode);

  Name get forGetter {
    if (name.endsWith('=')) {
      var getterName = name.substring(0, name.length - 1);
      return Name(libraryUri, getterName);
    } else {
      return this;
    }
  }

  Name get forSetter {
    if (name.endsWith('=')) {
      return this;
    } else {
      return Name(libraryUri, '$name=');
    }
  }

  @override
  bool operator ==(Object other) {
    return other is Name &&
        name == other.name &&
        libraryUri == other.libraryUri;
  }

  bool isAccessibleFor(Uri libraryUri) {
    return isPublic || this.libraryUri == libraryUri;
  }

  @override
  String toString() => libraryUri != null ? '$libraryUri::$name' : name;

  /// Returns the name that corresponds to [element].
  ///
  /// If the element is private, the name includes the library URI.
  ///
  /// If the name is a setter, the name ends with `=`.
  static Name? forElement(Element2 element) {
    var name = element.lookupName;
    if (name == null) {
      return null;
    }

    if (name.startsWith('_')) {
      var libraryUri = element.library2!.uri;
      return Name(libraryUri, name);
    } else {
      return Name(null, name);
    }
  }
}

/// Failure because of not unique extension type member.
class NotUniqueExtensionMemberConflict extends Conflict {
  final List<ExecutableElementOrMember> candidates;

  NotUniqueExtensionMemberConflict({
    required super.name,
    required this.candidates,
  });

  List<ExecutableElement2> get candidates2 =>
      candidates.map((e) => e.asElement2).toList();
}

class _ExtensionTypeCandidates {
  final Name name;
  final List<MethodElementOrMember> methods = [];
  final List<PropertyAccessorElementOrMember> getters = [];
  final List<PropertyAccessorElementOrMember> setters = [];

  _ExtensionTypeCandidates(this.name);

  List<ExecutableElementOrMember> get all {
    return [...methods, ...getters, ...setters];
  }

  void add(ExecutableElementOrMember element) {
    switch (element) {
      case MethodElementOrMember():
        methods.add(element);
      case PropertyAccessorElementOrMember(isGetter: true):
        getters.add(element);
      case PropertyAccessorElementOrMember(isSetter: true):
        setters.add(element);
    }
  }

  List<ExecutableElementOrMember> notPrecluded({
    required Set<Name> precludedNames,
    required Set<Name> precludedMethods,
    required Set<Name> precludedSetters,
  }) {
    if (precludedNames.contains(name)) {
      return const [];
    }
    return [
      if (!precludedMethods.contains(name)) ...methods,
      ...getters,
      if (!precludedSetters.contains(name)) ...setters,
    ];
  }
}

class _ParameterDesc {
  final int? index;
  final String? name;

  factory _ParameterDesc(int index, ParameterElementMixin element) {
    return element.isNamed
        ? _ParameterDesc.name(element.name)
        : _ParameterDesc.index(index);
  }

  _ParameterDesc.index(this.index) : name = null;

  _ParameterDesc.name(this.name) : index = null;

  @override
  int get hashCode {
    return index?.hashCode ?? name?.hashCode ?? 0;
  }

  @override
  bool operator ==(other) {
    return other is _ParameterDesc &&
        other.index == index &&
        other.name == name;
  }
}
