// Copyright (c) 2026, 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:analysis_server_plugin/src/utilities/selection.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';

extension SelectionExtension on Selection {
  /// The end of the selection.
  int get end => offset + length;

  /// Returns the element of the constructor at the given [selection].
  ///
  /// Returns `null` if
  /// - the selection doesn't identify a constructor, or
  /// - [mustHaveName] is `true` and the referenced constructor doesn't have a
  ///   name, or
  /// - [mustNotHaveName] is `true` and the referenced constructor has a name.
  ConstructorElement? constructor({
    bool mustHaveName = false,
    bool mustNotHaveName = false,
  }) {
    var node = coveringNode;

    bool meetsRequirements(Object? name) {
      if (name == null) {
        if (mustHaveName) return false;
      } else {
        if (mustNotHaveName) return false;
      }
      return true;
    }

    if (node is ConstructorDeclaration) {
      if (!meetsRequirements(node.name)) return null;
      var left =
          node.typeName?.offset ??
          node.factoryKeyword?.offset ??
          node.newKeyword?.offset ??
          node.firstTokenAfterCommentAndMetadata.offset;
      var right = node.separator?.offset ?? node.parameters.offset;
      if (left <= offset && end <= right) {
        return node.declaredFragment?.element;
      }
    } else if (node is SimpleIdentifier || node is FormalParameterList) {
      var parent = node.parent;
      if (parent is ConstructorDeclaration) {
        if (!meetsRequirements(parent.name)) return null;
        return parent.declaredFragment?.element;
      } else if (parent is ConstructorName) {
        if (!meetsRequirements(parent.name)) return null;
        return parent.element;
      }
    } else if (node is PrimaryConstructorDeclaration) {
      if (!meetsRequirements(node.constructorName)) return null;
      return node.declaredFragment?.element;
    } else if (node is PrimaryConstructorName) {
      if (!meetsRequirements(node.name)) return null;
      var parent = node.parent;
      if (parent is PrimaryConstructorDeclaration) {
        return parent.declaredFragment?.element;
      }
    } else if (node is NamedType) {
      var parent = node.parent;
      if (parent is ConstructorName) {
        if (!meetsRequirements(parent.name)) return null;
        return parent.element;
      }
    } else if (node is ConstructorName) {
      if (!meetsRequirements(node.name)) return null;
      return node.element;
    }
    return null;
  }

  /// Returns the [LibraryImport] at the given [selection].
  ///
  /// Returns `null` if
  /// - the selection doesn't identify an import directive, or
  /// - [mustHavePrefix] is `true` and the directive doesn't have a
  ///   prefix, or
  /// - [mustNotHavePrefix] is `true` and the directive has a prefix, or
  /// - the AST has not been resolved so the node has no `libraryImport`.
  /// - [mustNotBeDeferred] is `true` and the directive is deferred, or
  /// - the AST has not been resolved so the node has no `libraryImport`.
  LibraryImport? importDirective({
    bool mustHavePrefix = false,
    bool mustNotHavePrefix = false,
    bool mustNotBeDeferred = false,
  }) {
    var node = coveringNode.thisOrAncestorOfType<ImportDirective>();
    if (node == null) {
      return null;
    }

    var hasPrefix = node.prefix != null;
    var isDeferred = node.deferredKeyword != null;

    if (mustHavePrefix && !hasPrefix) {
      return null;
    } else if (mustNotHavePrefix && hasPrefix) {
      return null;
    } else if (mustNotBeDeferred && isDeferred) {
      return null;
    } else {
      return node.libraryImport;
    }
  }
}
