class RubyLsp::Requests::Rename

def adjust_reference_for_edit(name, reference)

: (String name, RubyIndexer::ReferenceFinder::Reference reference) -> Interface::TextEdit
def adjust_reference_for_edit(name, reference)
  # The reference may include a namespace in front. We need to check if the rename new name includes namespaces
  # and then adjust both the text and the location to produce the correct edit
  location = reference.location
  new_text = reference.name.sub(name, @new_name)
  Interface::TextEdit.new(range: range_from_location(location), new_text: new_text)
end

def collect_changes(target, ast, name, uri)

: (RubyIndexer::ReferenceFinder::Target target, Prism::Node ast, String name, URI::Generic uri) -> Array[Interface::TextEdit]
def collect_changes(target, ast, name, uri)
  dispatcher = Prism::Dispatcher.new
  finder = RubyIndexer::ReferenceFinder.new(target, @global_state.index, dispatcher, uri)
  dispatcher.visit(ast)
  finder.references.map do |reference|
    adjust_reference_for_edit(name, reference)
  end
end

def collect_file_renames(fully_qualified_name, document_changes)

: (String fully_qualified_name, Array[(Interface::RenameFile | Interface::TextDocumentEdit)] document_changes) -> void
def collect_file_renames(fully_qualified_name, document_changes)
  # Check if the declarations of the symbol being renamed match the file name. In case they do, we automatically
  # rename the files for the user.
  #
  # We also look for an associated test file and rename it too
  short_name = fully_qualified_name.split("::").last #: as !nil
  @global_state.index[fully_qualified_name]&.each do |entry|
    # Do not rename files that are not part of the workspace
    uri = entry.uri
    file_path = uri.full_path
    next unless file_path&.start_with?(@global_state.workspace_path)
    case entry
    when RubyIndexer::Entry::Class, RubyIndexer::Entry::Module, RubyIndexer::Entry::Constant,
         RubyIndexer::Entry::ConstantAlias, RubyIndexer::Entry::UnresolvedConstantAlias
      file_name = file_from_constant_name(short_name)
      if "#{file_name}.rb" == entry.file_name
        new_file_name = file_from_constant_name(
          @new_name.split("::").last, #: as !nil
        )
        new_uri = URI::Generic.from_path(path: File.join(
          File.dirname(file_path),
          "#{new_file_name}.rb",
        )).to_s
        document_changes << Interface::RenameFile.new(kind: "rename", old_uri: uri.to_s, new_uri: new_uri)
      end
    end
  end
end

def collect_text_edits(target, name)

: (RubyIndexer::ReferenceFinder::Target target, String name) -> Hash[String, Array[Interface::TextEdit]]
def collect_text_edits(target, name)
  changes = {}
  Dir.glob(File.join(@global_state.workspace_path, "**/*.rb")).each do |path|
    uri = URI::Generic.from_path(path: path)
    # If the document is being managed by the client, then we should use whatever is present in the store instead
    # of reading from disk
    next if @store.key?(uri)
    parse_result = Prism.parse_file(path)
    edits = collect_changes(target, parse_result.value, name, uri)
    changes[uri.to_s] = edits unless edits.empty?
  rescue Errno::EISDIR, Errno::ENOENT
    # If `path` is a directory, just ignore it and continue. If the file doesn't exist, then we also ignore it.
  end
  @store.each do |uri, document|
    next unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
    edits = collect_changes(target, document.ast, name, document.uri)
    changes[uri] = edits unless edits.empty?
  end
  changes
end

def file_from_constant_name(constant_name)

: (String constant_name) -> String
def file_from_constant_name(constant_name)
  constant_name
    .gsub(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/, '\1\3_\2\4')
    .downcase
end

def initialize(global_state, store, document, params)

: (GlobalState global_state, Store store, (RubyDocument | ERBDocument) document, Hash[Symbol, untyped] params) -> void
def initialize(global_state, store, document, params)
  super()
  @global_state = global_state
  @store = store
  @document = document
  @position = params[:position] #: Hash[Symbol, Integer]
  @new_name = params[:newName] #: String
end

def perform

: -> Interface::WorkspaceEdit?
@override
def perform
  char_position, _ = @document.find_index_by_position(@position)
  node_context = RubyDocument.locate(
    @document.ast,
    char_position,
    node_types: [Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode],
    code_units_cache: @document.code_units_cache,
  )
  target = node_context.node
  parent = node_context.parent
  return if !target || target.is_a?(Prism::ProgramNode)
  if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
    target = determine_target(
      target,
      parent,
      @position,
    )
  end
  target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode
  name = RubyIndexer::Index.constant_name(target)
  return unless name
  entries = @global_state.index.resolve(name, node_context.nesting)
  return unless entries
  if (conflict_entries = @global_state.index.resolve(@new_name, node_context.nesting))
    raise InvalidNameError, "The new name is already in use by #{conflict_entries.first&.name}"
  end
  fully_qualified_name = entries.first #: as !nil
    .name
  reference_target = RubyIndexer::ReferenceFinder::ConstTarget.new(fully_qualified_name)
  changes = collect_text_edits(reference_target, name)
  # If the client doesn't support resource operations, such as renaming files, then we can only return the basic
  # text changes
  unless @global_state.client_capabilities.supports_rename?
    return Interface::WorkspaceEdit.new(changes: changes)
  end
  # Text edits must be applied before any resource operations, such as renaming files. Otherwise, the file is
  # renamed and then the URI associated to the text edit no longer exists, causing it to be dropped
  document_changes = changes.map do |uri, edits|
    Interface::TextDocumentEdit.new(
      text_document: Interface::VersionedTextDocumentIdentifier.new(uri: uri, version: nil),
      edits: edits,
    )
  end
  collect_file_renames(fully_qualified_name, document_changes)
  Interface::WorkspaceEdit.new(document_changes: document_changes)
end

def provider

: -> Interface::RenameOptions
def provider
  Interface::RenameOptions.new(prepare_provider: true)
end