import { getEslintDisabledRules } from './get-eslint-disabled-rules.js'
import { isPartitionComment } from './is-partition-comment.js'
import { getCommentsBefore } from './get-comments-before.js'
import { ASTUtils } from '@typescript-eslint/utils'
/**
 * Determines the complete range of a node including its associated comments.
 *
 * Calculates the full range that should be considered when moving or analyzing
 * a node. This includes:
 *
 * - The node itself
 * - Parentheses surrounding the node (if any)
 * - Preceding comments that "belong" to the node.
 *
 * The function intelligently determines which comments should be included by:
 *
 * - Including comments directly above the node (no empty lines between)
 * - Stopping at partition comments (used to separate sections)
 * - Stopping at ESLint disable/enable comments
 * - Optionally excluding the highest block comment (e.g., file headers).
 *
 * @example
 *
 * ```ts
 * // Source code:
 * // This comment belongs to the function
 * // So does this one
 * function foo() {}
 *
 * const range = getNodeRange({ node: functionNode, sourceCode })
 * // Returns range including both comments
 * ```
 *
 * @example
 *
 * ```ts
 * // Source code:
 * /* File header comment *\/
 * // Function comment
 * function bar() { }
 *
 * const range = getNodeRange({
 * node: functionNode,
 * sourceCode,
 * ignoreHighestBlockComment: true
 * });
 * // Returns range including line comment but not block comment
 * ```
 *
 * @param params - Parameters for range calculation.
 * @returns Tuple of [start, end] positions including relevant comments.
 */
function getNodeRange({
  ignoreHighestBlockComment,
  sourceCode,
  options,
  node,
}) {
  let start = node.range.at(0)
  let end = node.range.at(1)
  if (ASTUtils.isParenthesized(node, sourceCode)) {
    let bodyOpeningParen = sourceCode.getTokenBefore(
      node,
      ASTUtils.isOpeningParenToken,
    )
    let bodyClosingParen = sourceCode.getTokenAfter(
      node,
      ASTUtils.isClosingParenToken,
    )
    start = bodyOpeningParen.range.at(0)
    end = bodyClosingParen.range.at(1)
  }
  let comments = getCommentsBefore({
    sourceCode,
    node,
  })
  let highestBlockComment = comments.find(comment => comment.type === 'Block')
  /**
   * Iterate on all comments starting from the bottom until we reach the last of
   * the comments, a newline between comments, a partition comment, or a
   * eslint-disable comment.
   */
  let relevantTopComment
  for (let i = comments.length - 1; i >= 0; i--) {
    let comment = comments[i]
    let eslintDisabledRules = getEslintDisabledRules(comment.value)
    if (
      isPartitionComment({
        partitionByComment: options?.partitionByComment ?? false,
        comment,
      }) ||
      eslintDisabledRules?.eslintDisableDirective === 'eslint-disable' ||
      eslintDisabledRules?.eslintDisableDirective === 'eslint-enable'
    ) {
      break
    }
    /**
     * Check for newlines between comments or between the first comment and the
     * node.
     */
    let previousCommentOrNodeStartLine =
      i === comments.length - 1 ?
        node.loc.start.line
      : comments[i + 1].loc.start.line
    if (comment.loc.end.line !== previousCommentOrNodeStartLine - 1) {
      break
    }
    if (ignoreHighestBlockComment && comment === highestBlockComment) {
      break
    }
    relevantTopComment = comment
  }
  if (relevantTopComment) {
    start = relevantTopComment.range.at(0)
  }
  return [start, end]
}
export { getNodeRange }
