class EagerEye::Detectors::MissingCounterCache

def self.detector_name

def self.detector_name
  :missing_counter_cache
end

def count_on_association?(node)

def count_on_association?(node)
  return false unless node.type == :send
  return false unless COUNT_METHODS.include?(node.children[1])
  receiver = node.children[0]
  receiver && likely_association_receiver?(receiver)
end

def detect(ast, file_path)

def detect(ast, file_path)
  return [] unless ast
  issues = []
  traverse_ast(ast) do |node|
    next unless count_on_association?(node)
    next unless inside_iteration?(node)
    association_name = extract_association_name(node)
    next unless association_name
    issues << create_issue(
      file_path: file_path,
      line_number: node.loc.line,
      message: "`.#{node.children[1]}` called on `#{association_name}` inside iteration may cause N+1 queries",
      suggestion: "Consider adding `counter_cache: true` to the belongs_to association"
    )
  end
  issues
end

def extract_association_name(node)

def extract_association_name(node)
  receiver = node.children[0]
  receiver.children[1].to_s if receiver&.type == :send
end

def inside_iteration?(node)

def inside_iteration?(node)
  parent = node
  while (parent = @parent_map[parent])
    return true if iteration_block?(parent)
  end
  false
end

def iteration_block?(node)

def iteration_block?(node)
  node.type == :block && node.children[0]&.type == :send &&
    ITERATION_METHODS.include?(node.children[0].children[1])
end

def likely_association_receiver?(node)

def likely_association_receiver?(node)
  node.type == :send && PLURAL_ASSOCIATIONS.include?(node.children[1].to_s)
end

def traverse_ast(node, &block)

def traverse_ast(node, &block)
  return unless node.is_a?(Parser::AST::Node)
  @parent_map ||= {}
  yield node
  node.children.each do |child|
    next unless child.is_a?(Parser::AST::Node)
    @parent_map[child] = node
    traverse_ast(child, &block)
  end
end