class RubyIndexer::Collector

def add_class_entry(node)

def add_class_entry(node)
  name = node.constant_path.location.slice
  unless /^[A-Z:]/.match?(name)
    @queue << node.body
    return
  end
  comments = collect_comments(node)
  superclass = node.superclass
  parent_class = case superclass
  when Prism::ConstantReadNode, Prism::ConstantPathNode
    superclass.slice
  end
  @current_owner = Entry::Class.new(
    fully_qualify_name(name),
    @file_path,
    node.location,
    comments,
    parent_class,
  )
  @index << @current_owner
  @stack << name
  @queue.prepend(node.body, LEAVE_EVENT)
end

def add_constant(node, name, value = nil)

def add_constant(node, name, value = nil)
  value = node.value unless node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
  comments = collect_comments(node)
  @index << case value
  when Prism::ConstantReadNode, Prism::ConstantPathNode
    Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
  when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
    Prism::ConstantOperatorWriteNode
    # If the right hand side is another constant assignment, we need to visit it because that constant has to be
    # indexed too
    @queue.prepend(value)
    Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
  when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
    Prism::ConstantPathAndWriteNode
    @queue.prepend(value)
    Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
  else
    Entry::Constant.new(name, @file_path, node.location, comments)
  end
end

def add_module_entry(node)

def add_module_entry(node)
  name = node.constant_path.location.slice
  unless /^[A-Z:]/.match?(name)
    @queue << node.body
    return
  end
  comments = collect_comments(node)
  @current_owner = Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
  @index << @current_owner
  @stack << name
  @queue.prepend(node.body, LEAVE_EVENT)
end

def collect(node)

def collect(node)
  @queue = [node]
  until @queue.empty?
    node_or_event = @queue.shift
    case node_or_event
    when Prism::ProgramNode
      @queue << node_or_event.statements
    when Prism::StatementsNode
      T.unsafe(@queue).prepend(*node_or_event.body)
    when Prism::ClassNode
      add_class_entry(node_or_event)
    when Prism::ModuleNode
      add_module_entry(node_or_event)
    when Prism::MultiWriteNode
      handle_multi_write_node(node_or_event)
    when Prism::ConstantPathWriteNode
      handle_constant_path_write_node(node_or_event)
    when Prism::ConstantPathOrWriteNode
      handle_constant_path_or_write_node(node_or_event)
    when Prism::ConstantPathOperatorWriteNode
      handle_constant_path_operator_write_node(node_or_event)
    when Prism::ConstantPathAndWriteNode
      handle_constant_path_and_write_node(node_or_event)
    when Prism::ConstantWriteNode
      handle_constant_write_node(node_or_event)
    when Prism::ConstantOrWriteNode
      name = fully_qualify_name(node_or_event.name.to_s)
      add_constant(node_or_event, name)
    when Prism::ConstantAndWriteNode
      name = fully_qualify_name(node_or_event.name.to_s)
      add_constant(node_or_event, name)
    when Prism::ConstantOperatorWriteNode
      name = fully_qualify_name(node_or_event.name.to_s)
      add_constant(node_or_event, name)
    when Prism::CallNode
      handle_call_node(node_or_event)
    when Prism::DefNode
      handle_def_node(node_or_event)
    when LEAVE_EVENT
      @stack.pop
    end
  end
end

def collect_comments(node)

def collect_comments(node)
  comments = []
  start_line = node.location.start_line - 1
  start_line -= 1 unless @comments_by_line.key?(start_line)
  start_line.downto(1) do |line|
    comment = @comments_by_line[line]
    break unless comment
    comment_content = comment.location.slice.chomp
    # invalid encodings would raise an "invalid byte sequence" exception
    if !comment_content.valid_encoding? || comment_content.match?(RubyIndexer.configuration.magic_comment_regex)
      next
    end
    comment_content.delete_prefix!("#")
    comment_content.delete_prefix!(" ")
    comments.prepend(comment_content)
  end
  comments
end

def fully_qualify_name(name)

def fully_qualify_name(name)
  if @stack.empty? || name.start_with?("::")
    name
  else
    "#{@stack.join("::")}::#{name}"
  end.delete_prefix("::")
end

def handle_attribute(node, reader:, writer:)

def handle_attribute(node, reader:, writer:)
  arguments = node.arguments&.arguments
  return unless arguments
  receiver = node.receiver
  return unless receiver.nil? || receiver.is_a?(Prism::SelfNode)
  comments = collect_comments(node)
  arguments.each do |argument|
    name, loc = case argument
    when Prism::SymbolNode
      [argument.value, argument.value_loc]
    when Prism::StringNode
      [argument.content, argument.content_loc]
    end
    next unless name && loc
    @index << Entry::Accessor.new(name, @file_path, loc, comments, @current_owner) if reader
    @index << Entry::Accessor.new("#{name}=", @file_path, loc, comments, @current_owner) if writer
  end
end

def handle_call_node(node)

def handle_call_node(node)
  message = node.name
  case message
  when :private_constant
    handle_private_constant(node)
  when :attr_reader
    handle_attribute(node, reader: true, writer: false)
  when :attr_writer
    handle_attribute(node, reader: false, writer: true)
  when :attr_accessor
    handle_attribute(node, reader: true, writer: true)
  end
end

def handle_constant_path_and_write_node(node)

def handle_constant_path_and_write_node(node)
  # ignore variable constants like `var::FOO` or `self.class::FOO`
  target = node.target
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
  name = fully_qualify_name(target.location.slice)
  add_constant(node, name)
end

def handle_constant_path_operator_write_node(node)

def handle_constant_path_operator_write_node(node)
  # ignore variable constants like `var::FOO` or `self.class::FOO`
  target = node.target
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
  name = fully_qualify_name(target.location.slice)
  add_constant(node, name)
end

def handle_constant_path_or_write_node(node)

def handle_constant_path_or_write_node(node)
  # ignore variable constants like `var::FOO` or `self.class::FOO`
  target = node.target
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
  name = fully_qualify_name(target.location.slice)
  add_constant(node, name)
end

def handle_constant_path_write_node(node)

def handle_constant_path_write_node(node)
  # ignore variable constants like `var::FOO` or `self.class::FOO`
  target = node.target
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
  name = fully_qualify_name(target.location.slice)
  add_constant(node, name)
end

def handle_constant_write_node(node)

def handle_constant_write_node(node)
  name = fully_qualify_name(node.name.to_s)
  add_constant(node, name)
end

def handle_def_node(node)

def handle_def_node(node)
  method_name = node.name.to_s
  comments = collect_comments(node)
  case node.receiver
  when nil
    @index << Entry::InstanceMethod.new(
      method_name,
      @file_path,
      node.location,
      comments,
      node.parameters,
      @current_owner,
    )
  when Prism::SelfNode
    @index << Entry::SingletonMethod.new(
      method_name,
      @file_path,
      node.location,
      comments,
      node.parameters,
      @current_owner,
    )
  end
end

def handle_multi_write_node(node)

def handle_multi_write_node(node)
  value = node.value
  values = value.is_a?(Prism::ArrayNode) && value.opening_loc ? value.elements : []
  [*node.lefts, *node.rest, *node.rights].each_with_index do |target, i|
    current_value = values[i]
    # The moment we find a splat on the right hand side of the assignment, we can no longer figure out which value
    # gets assigned to what
    values.clear if current_value.is_a?(Prism::SplatNode)
    case target
    when Prism::ConstantTargetNode
      add_constant(target, fully_qualify_name(target.name.to_s), current_value)
    when Prism::ConstantPathTargetNode
      add_constant(target, fully_qualify_name(target.slice), current_value)
    end
  end
end

def handle_private_constant(node)

def handle_private_constant(node)
  arguments = node.arguments&.arguments
  return unless arguments
  first_argument = arguments.first
  name = case first_argument
  when Prism::StringNode
    first_argument.content
  when Prism::SymbolNode
    first_argument.value
  end
  return unless name
  receiver = node.receiver
  name = "#{receiver.slice}::#{name}" if receiver
  # The private_constant method does not resolve the constant name. It always points to a constant that needs to
  # exist in the current namespace
  entries = @index[fully_qualify_name(name)]
  entries&.each { |entry| entry.visibility = :private }
end

def initialize(index, parse_result, file_path)

def initialize(index, parse_result, file_path)
  @index = index
  @file_path = file_path
  @stack = T.let([], T::Array[String])
  @comments_by_line = T.let(
    parse_result.comments.to_h do |c|
      [c.location.start_line, c]
    end,
    T::Hash[Integer, Prism::Comment],
  )
  @queue = T.let([], T::Array[Object])
  @current_owner = T.let(nil, T.nilable(Entry::Namespace))
  super()
end