// Copyright (c) 2021, 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:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';

/// A resolver for [ConstructorReference] nodes.
class ConstructorReferenceResolver {
  /// The resolver driving this participant.
  final ResolverVisitor _resolver;

  ConstructorReferenceResolver(this._resolver);

  void resolve(ConstructorReferenceImpl node, {required DartType contextType}) {
    if (!_resolver.isConstructorTearoffsEnabled &&
        node.constructorName.type.typeArguments == null) {
      // Only report this if [node] has no explicit type arguments; otherwise
      // the parser has already reported an error.
      _resolver.errorReporter.atNode(
        node,
        WarningCode.SDK_VERSION_CONSTRUCTOR_TEAROFFS,
      );
    }
    node.constructorName.accept(_resolver);
    var element = node.constructorName.element;
    if (element != null && !element.isFactory) {
      var enclosingElement = element.enclosingElement2;
      if (enclosingElement is ClassElement2 && enclosingElement.isAbstract) {
        _resolver.errorReporter.atNode(
          node,
          CompileTimeErrorCode
              .TEAROFF_OF_GENERATIVE_CONSTRUCTOR_OF_ABSTRACT_CLASS,
        );
      }
    }
    var name = node.constructorName.name;
    if (element == null &&
        name != null &&
        _resolver.isConstructorTearoffsEnabled) {
      // The illegal construction, which looks like a type-instantiated
      // constructor tearoff, may be an attempt to reference a member on
      // [enclosingElement]. Try to provide a helpful error, and fall back to
      // "unknown constructor."
      //
      // Only report errors when the constructor tearoff feature is enabled,
      // to avoid reporting redundant errors.
      var enclosingElement = node.constructorName.type.element2;
      if (enclosingElement is TypeAliasElement2) {
        var aliasedType = enclosingElement.aliasedType;
        enclosingElement =
            aliasedType is InterfaceType ? aliasedType.element3 : null;
      }
      // TODO(srawlins): Handle `enclosingElement` being a function typedef:
      // typedef F<T> = void Function(); var a = F<int>.extensionOnType;`.
      // This is illegal.
      if (enclosingElement is InterfaceElement2) {
        var method = enclosingElement.getMethod2(name.name) ??
            enclosingElement.getGetter2(name.name) ??
            enclosingElement.getSetter2(name.name);
        if (method != null) {
          var error = method.isStatic
              ? CompileTimeErrorCode.CLASS_INSTANTIATION_ACCESS_TO_STATIC_MEMBER
              : CompileTimeErrorCode
                  .CLASS_INSTANTIATION_ACCESS_TO_INSTANCE_MEMBER;
          _resolver.errorReporter.atNode(
            node,
            error,
            arguments: [name.name],
          );
        } else if (!name.isSynthetic) {
          _resolver.errorReporter.atNode(
            node,
            CompileTimeErrorCode.CLASS_INSTANTIATION_ACCESS_TO_UNKNOWN_MEMBER,
            arguments: [enclosingElement.name3!, name.name],
          );
        }
      }
    }
    _inferArgumentTypes(node, contextType: contextType);
  }

  void _inferArgumentTypes(ConstructorReferenceImpl node,
      {required DartType contextType}) {
    var constructorName = node.constructorName;
    var elementToInfer = _resolver.inferenceHelper.constructorElementToInfer(
      constructorName: constructorName,
      definingLibrary: _resolver.definingLibrary,
    );

    // If the constructor is generic, we'll have a ConstructorMember that
    // substitutes in type arguments (possibly `dynamic`) from earlier in
    // resolution.
    //
    // Otherwise we'll have a ConstructorElement, and we can skip inference
    // because there's nothing to infer in a non-generic type.
    if (elementToInfer != null &&
        elementToInfer.typeParameters2.isNotEmpty &&
        constructorName.type.typeArguments == null) {
      // TODO(leafp): Currently, we may re-infer types here, since we
      // sometimes resolve multiple times.  We should really check that we
      // have not already inferred something.  However, the obvious ways to
      // check this don't work, since we may have been instantiated
      // to bounds in an earlier phase, and we *do* want to do inference
      // in that case.

      // Get back to the uninstantiated generic constructor.
      // TODO(jmesserly): should we store this earlier in resolution?
      // Or look it up, instead of jumping backwards through the Member?
      var rawElement = elementToInfer.element2;
      var constructorType = elementToInfer.asType;

      var inferred = _resolver.inferenceHelper.inferTearOff(
          node, constructorName.name!, constructorType,
          contextType: contextType) as FunctionType?;

      if (inferred != null) {
        var inferredReturnType = inferred.returnType as InterfaceType;

        // Update the static element as well. This is used in some cases, such
        // as computing constant values. It is stored in two places.
        var constructorElement =
            ConstructorMember.from2(rawElement, inferredReturnType);

        constructorName.element = constructorElement.baseElement;
        constructorName.name?.element = constructorElement.baseElement;
        node.recordStaticType(inferred, resolver: _resolver);
        // The NamedType child of `constructorName` doesn't have a static type.
        constructorName.type.type = null;
      }
    } else {
      var constructorElement = constructorName.element;
      node.recordStaticType(
          constructorElement == null
              ? InvalidTypeImpl.instance
              : constructorElement.type,
          resolver: _resolver);
      // The NamedType child of `constructorName` doesn't have a static type.
      constructorName.type.type = null;
    }
  }
}
