module RuboCop::RSpec::ExpectOffense

def expect_correction(correction, loop: true, source: nil)

rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def expect_correction(correction, loop: true, source: nil)
  if source
    expected_annotations = parse_annotations(source, raise_error: false)
    @processed_source = parse_processed_source(expected_annotations.plain_source)
    _investigate(cop, @processed_source)
  end
  raise '`expect_correction` must follow `expect_offense`' unless @processed_source
  source = @processed_source.raw_source
  raise 'Use `expect_no_corrections` if the code will not change' if correction == source
  iteration = 0
  new_source = loop do
    iteration += 1
    corrected_source = @last_corrector.rewrite
    break corrected_source unless loop
    break corrected_source if @last_corrector.empty?
    break corrected_source if corrected_source == @processed_source.buffer.source
    if iteration > RuboCop::Runner::MAX_ITERATIONS
      raise RuboCop::Runner::InfiniteCorrectionLoop.new(@processed_source.path, [@offenses])
    end
    # Prepare for next loop
    @processed_source = parse_source(corrected_source, @processed_source.path)
    _investigate(cop, @processed_source)
  end
  raise 'Expected correction but no corrections were made' if new_source == source
  expect(new_source).to eq(correction)
end

def expect_no_corrections

def expect_no_corrections
  raise '`expect_no_corrections` must follow `expect_offense`' unless @processed_source
  return if @last_corrector.empty?
  # In order to print a nice diff, e.g. what source got corrected to,
  # we need to run the actual corrections
  new_source = @last_corrector.rewrite
  expect(new_source).to eq(@processed_source.buffer.source)
end

def expect_no_offenses(source, file = nil)

def expect_no_offenses(source, file = nil)
  offenses = inspect_source(source, file)
  expected_annotations = AnnotatedSource.parse(source)
  actual_annotations = expected_annotations.with_offense_annotations(offenses)
  expect(actual_annotations.to_s).to eq(source)
end

def expect_offense(source, file = nil, severity: nil, chomp: false, **replacements)

def expect_offense(source, file = nil, severity: nil, chomp: false, **replacements)
  expected_annotations = parse_annotations(source, **replacements)
  source = expected_annotations.plain_source
  source = source.chomp if chomp
  @processed_source = parse_processed_source(source, file)
  @offenses = _investigate(cop, @processed_source)
  actual_annotations = expected_annotations.with_offense_annotations(@offenses)
  expect(actual_annotations).to eq(expected_annotations), ''
  expect(@offenses.map(&:severity).uniq).to eq([severity]) if severity
  @offenses
end

def format_offense(source, **replacements)

def format_offense(source, **replacements)
  replacements.each do |keyword, value|
    value = value.to_s
    source = source.gsub("%{#{keyword}}", value)
                   .gsub("^{#{keyword}}", '^' * value.size)
                   .gsub("_{#{keyword}}", ' ' * value.size)
  end
  source
end

def parse_annotations(source, raise_error: true, **replacements)

def parse_annotations(source, raise_error: true, **replacements)
  set_formatter_options
  source = format_offense(source, **replacements)
  annotations = AnnotatedSource.parse(source)
  return annotations unless raise_error && annotations.plain_source == source
  raise 'Use `expect_no_offenses` to assert that no offenses are found'
end

def parse_processed_source(source, file = nil)

def parse_processed_source(source, file = nil)
  processed_source = parse_source(source, file)
  return processed_source if processed_source.valid_syntax?
  raise 'Error parsing example code: ' \
        "#{processed_source.diagnostics.map(&:render).join("\n")}"
end

def set_formatter_options

def set_formatter_options
  RuboCop::Formatter::DisabledConfigFormatter.config_to_allow_offenses = {}
  RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
  cop.instance_variable_get(:@options)[:autocorrect] = true
end