// Copyright (c) 2020, 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/types/shared_type.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';

/// Helper for resolving [VariableDeclaration]s.
class VariableDeclarationResolver {
  final ResolverVisitor _resolver;
  final bool _strictInference;

  VariableDeclarationResolver({
    required ResolverVisitor resolver,
    required bool strictInference,
  })  : _resolver = resolver,
        _strictInference = strictInference;

  void resolve(VariableDeclarationImpl node) {
    var parent = node.parent as VariableDeclarationList;

    var initializer = node.initializer;

    if (initializer == null) {
      if (_strictInference && parent.type == null) {
        _resolver.errorReporter.atNode(
          node,
          WarningCode.INFERENCE_FAILURE_ON_UNINITIALIZED_VARIABLE,
          arguments: [node.name.lexeme],
        );
      }
      return;
    }

    var element = node.declaredFragment!.element;
    var isTopLevel =
        element is FieldElement2 || element is TopLevelVariableElement2;

    if (isTopLevel) {
      _resolver.flowAnalysis.bodyOrInitializer_enter(node, null);
    } else if (element.isLate) {
      _resolver.flowAnalysis.flow?.lateInitializer_begin(node);
    }

    var contextType = element is! PropertyInducingElementImpl2 ||
            element.shouldUseTypeForInitializerInference
        ? element.type
        : UnknownInferredType.instance;
    _resolver.analyzeExpression(initializer, SharedTypeSchemaView(contextType));
    initializer = _resolver.popRewrite()!;
    var whyNotPromoted =
        _resolver.flowAnalysis.flow?.whyNotPromoted(initializer);

    var initializerType = initializer.typeOrThrow;
    if (parent.type == null && element is LocalVariableElementImpl2) {
      element.type = _resolver
          .variableTypeFromInitializerType(SharedTypeView(initializerType))
          .unwrapTypeView();
    }

    if (isTopLevel) {
      _resolver.flowAnalysis.bodyOrInitializer_exit();
      _resolver.nullSafetyDeadCodeVerifier.flowEnd(node);
    } else if (element.isLate) {
      _resolver.flowAnalysis.flow?.lateInitializer_end();
    }

    // Initializers of top-level variables and fields are already included
    // into elements during linking.
    if (element is LocalVariableElementImpl2 && element.isConst) {
      var fragment = element.firstFragment as ConstLocalVariableElementImpl;
      fragment.constantInitializer = initializer;
    }

    _resolver.checkForAssignableExpressionAtType(
      initializer,
      initializerType,
      element.type,
      CompileTimeErrorCode.INVALID_ASSIGNMENT,
      whyNotPromoted: whyNotPromoted,
    );
  }
}
