# typed: strict
# frozen_string_literal: true

module RubyLsp
  module Requests
    module Support
      class RuboCopDiagnostic
        extend T::Sig

        RUBOCOP_TO_LSP_SEVERITY = T.let(
          {
            convention: Constant::DiagnosticSeverity::INFORMATION,
            info: Constant::DiagnosticSeverity::INFORMATION,
            refactor: Constant::DiagnosticSeverity::INFORMATION,
            warning: Constant::DiagnosticSeverity::WARNING,
            error: Constant::DiagnosticSeverity::ERROR,
            fatal: Constant::DiagnosticSeverity::ERROR,
          }.freeze,
          T::Hash[Symbol, Integer],
        )

        # Cache cops to attach URLs to diagnostics. Only built-in cops for now.
        COP_TO_DOC_URL = T.let(
          RuboCop::Cop::Registry.global.to_h,
          T::Hash[String, [T.class_of(RuboCop::Cop::Base)]],
        )

        sig { params(offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
        def initialize(offense, uri)
          @offense = offense
          @uri = uri
        end

        sig { returns(Interface::CodeAction) }
        def to_lsp_code_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: @offense.correctable? ? offense_replacements : [],
                ),
              ],
            ),
            is_preferred: true,
          )
        end

        sig { returns(Interface::Diagnostic) }
        def to_lsp_diagnostic
          severity = RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name]
          message  = @offense.message

          message += "\n\nThis offense is not auto-correctable.\n" unless @offense.correctable?

          cop = COP_TO_DOC_URL[@offense.cop_name]&.first
          if cop&.documentation_url
            code_description = { href: cop.documentation_url }
          end

          Interface::Diagnostic.new(
            message: message,
            source: "RuboCop",
            code: @offense.cop_name,
            code_description: code_description,
            severity: severity,
            range: Interface::Range.new(
              start: Interface::Position.new(
                line: @offense.line - 1,
                character: @offense.column,
              ),
              end: Interface::Position.new(
                line: @offense.last_line - 1,
                character: @offense.last_column,
              ),
            ),
            data: {
              correctable: @offense.correctable?,
              code_action: to_lsp_code_action,
            },
          )
        end

        private

        sig { returns(T::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
      end
    end
  end
end