// 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:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';

/// Helper for checking potentially nullable dereferences.
class NullableDereferenceVerifier {
  final TypeSystemImpl _typeSystem;
  final ErrorReporter _errorReporter;

  /// The resolver driving this participant.
  final ResolverVisitor _resolver;

  NullableDereferenceVerifier({
    required TypeSystemImpl typeSystem,
    required ErrorReporter errorReporter,
    required ResolverVisitor resolver,
  })  : _typeSystem = typeSystem,
        _errorReporter = errorReporter,
        _resolver = resolver;

  bool expression(ErrorCode errorCode, Expression expression,
      {DartType? type}) {
    type ??= expression.typeOrThrow;
    return _check(errorCode, expression, type);
  }

  void report(
      ErrorCode errorCode, SyntacticEntity errorEntity, DartType receiverType,
      {List<String> arguments = const <String>[],
      List<DiagnosticMessage>? messages}) {
    if (receiverType == _typeSystem.typeProvider.nullType) {
      errorCode = CompileTimeErrorCode.INVALID_USE_OF_NULL_VALUE;
      arguments = [];
    }
    if (errorEntity is AstNode) {
      _errorReporter.atNode(
        errorEntity,
        errorCode,
        arguments: arguments,
        contextMessages: messages,
      );
    } else if (errorEntity is Token) {
      _errorReporter.atToken(
        errorEntity,
        errorCode,
        arguments: arguments,
        contextMessages: messages,
      );
    } else {
      throw StateError('Syntactic entity must be AstNode or Token to report.');
    }
  }

  /// If the [receiverType] is potentially nullable, report it.
  ///
  /// The [errorNode] is usually the receiver of the invocation, but if the
  /// receiver is the implicit `this`, the name of the invocation.
  ///
  /// Returns whether [receiverType] was reported.
  bool _check(
    ErrorCode errorCode,
    AstNode errorNode,
    DartType receiverType,
  ) {
    if (receiverType is DynamicType ||
        receiverType is InvalidType ||
        !_typeSystem.isPotentiallyNullable(receiverType)) {
      return false;
    }

    List<DiagnosticMessage>? messages;
    if (errorNode is ExpressionImpl) {
      messages = _resolver.computeWhyNotPromotedMessages(
          errorNode, _resolver.flowAnalysis.flow?.whyNotPromoted(errorNode)());
    }
    report(errorCode, errorNode, receiverType, messages: messages);
    return true;
  }
}
