class RubyLsp::Requests::Support::RuboCopDiagnostic

def autocorrect_action

: -> Interface::CodeAction
def autocorrect_action
  Interface::CodeAction.new(
    title: "Autocorrect #{@offense.cop_name}",
    kind: Constant::CodeActionKind::QUICK_FIX,
    edit: Interface::WorkspaceEdit.new(
      document_changes: [
        Interface::TextDocumentEdit.new(
          text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
            uri: @uri.to_s,
            version: nil,
          ),
          edits: correctable? ? offense_replacements : [],
        ),
      ],
    ),
    is_preferred: true,
  )
end

def code_description(config)

: (::RuboCop::Config config) -> Interface::CodeDescription?
def code_description(config)
  cop = RuboCopRunner.find_cop_by_name(@offense.cop_name)
  return unless cop
  doc_url = if ENHANCED_DOC_URL
    cop.documentation_url(config)
  else
    cop.documentation_url
  end
  Interface::CodeDescription.new(href: doc_url) if doc_url
end

def correctable?

: -> bool
a corrector is present. If it is, then that means some code transformation can be applied.
as `correctable?` to prevent annoying changes while typing. Instead check if
When `RuboCop::LSP.enable` is called, contextual autocorrect will not offer itself
def correctable?
  !@offense.corrector.nil?
end

def disable_line_action

: -> Interface::CodeAction
def disable_line_action
  Interface::CodeAction.new(
    title: "Disable #{@offense.cop_name} for this line",
    kind: Constant::CodeActionKind::QUICK_FIX,
    edit: Interface::WorkspaceEdit.new(
      document_changes: [
        Interface::TextDocumentEdit.new(
          text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
            uri: @uri.to_s,
            version: nil,
          ),
          edits: line_disable_comment,
        ),
      ],
    ),
  )
end

def initialize(document, offense, uri)

: (RubyDocument document, ::RuboCop::Cop::Offense offense, URI::Generic uri) -> void
encoding and file source
TODO: avoid passing document once we have alternative ways to get at
def initialize(document, offense, uri)
  @document = document
  @offense = offense
  @uri = uri
end

def length_of_line(line)

: (String line) -> Integer
def length_of_line(line)
  if @document.encoding == Encoding::UTF_16LE
    line_length = 0
    line.codepoints.each do |codepoint|
      line_length += 1
      if codepoint > RubyLsp::Document::Scanner::SURROGATE_PAIR_START
        line_length += 1
      end
    end
    line_length
  else
    line.length
  end
end

def line_disable_comment

: -> Array[Interface::TextEdit]
def line_disable_comment
  new_text = if @offense.source_line.include?(" # rubocop:disable ")
    ",#{@offense.cop_name}"
  else
    " # rubocop:disable #{@offense.cop_name}"
  end
  eol = Interface::Position.new(
    line: @offense.line - 1,
    character: length_of_line(@offense.source_line),
  )
  # TODO: fails for multiline strings - may be preferable to use block
  # comments to disable some offenses
  inline_comment = Interface::TextEdit.new(
    range: Interface::Range.new(start: eol, end: eol),
    new_text: new_text,
  )
  [inline_comment]
end

def message

: -> String
def message
  message  = @offense.message
  message += "\n\nThis offense is not auto-correctable.\n" unless correctable?
  message
end

def offense_replacements

: -> Array[Interface::TextEdit]
def offense_replacements
  @offense.corrector.as_replacements.map do |range, replacement|
    Interface::TextEdit.new(
      range: Interface::Range.new(
        start: Interface::Position.new(line: range.line - 1, character: range.column),
        end: Interface::Position.new(line: range.last_line - 1, character: range.last_column),
      ),
      new_text: replacement,
    )
  end
end

def severity

: -> Integer?
def severity
  RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name]
end

def to_lsp_code_actions

: -> Array[Interface::CodeAction]
def to_lsp_code_actions
  code_actions = []
  code_actions << autocorrect_action if correctable?
  code_actions << disable_line_action
  code_actions
end

def to_lsp_diagnostic(config)

: (::RuboCop::Config config) -> Interface::Diagnostic
def to_lsp_diagnostic(config)
  # highlighted_area contains the begin and end position of the first line
  # This ensures that multiline offenses don't clutter the editor
  highlighted = @offense.highlighted_area
  Interface::Diagnostic.new(
    message: message,
    source: "RuboCop",
    code: @offense.cop_name,
    code_description: code_description(config),
    severity: severity,
    range: Interface::Range.new(
      start: Interface::Position.new(
        line: @offense.line - 1,
        character: highlighted.begin_pos,
      ),
      end: Interface::Position.new(
        line: @offense.line - 1,
        character: highlighted.end_pos,
      ),
    ),
    data: {
      correctable: correctable?,
      code_actions: to_lsp_code_actions,
    },
  )
end