# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # Enforces the use the shorthand for self-assignment.
      #
      # @example
      #
      #   # bad
      #   x = x + 1
      #
      #   # good
      #   x += 1
      class SelfAssignment < Base
        extend AutoCorrector

        MSG = 'Use self-assignment shorthand `%<method>s=`.'
        OPS = %i[+ - * ** / | &].freeze

        def self.autocorrect_incompatible_with
          [Layout::SpaceAroundOperators]
        end

        def on_lvasgn(node)
          check(node, :lvar)
        end

        def on_ivasgn(node)
          check(node, :ivar)
        end

        def on_cvasgn(node)
          check(node, :cvar)
        end

        private

        def check(node, var_type)
          var_name, rhs = *node
          return unless rhs

          if rhs.send_type?
            check_send_node(node, rhs, var_name, var_type)
          elsif %i[and or].include?(rhs.type)
            check_boolean_node(node, rhs, var_name, var_type)
          end
        end

        def check_send_node(node, rhs, var_name, var_type)
          receiver, method_name, *_args = *rhs
          return unless OPS.include?(method_name)

          target_node = s(var_type, var_name)
          return unless receiver == target_node

          add_offense(node, message: format(MSG, method: method_name)) do |corrector|
            autocorrect(corrector, node)
          end
        end

        def check_boolean_node(node, rhs, var_name, var_type)
          first_operand, _second_operand = *rhs

          target_node = s(var_type, var_name)
          return unless first_operand == target_node

          operator = rhs.loc.operator.source
          add_offense(node, message: format(MSG, method: operator)) do |corrector|
            autocorrect(corrector, node)
          end
        end

        def autocorrect(corrector, node)
          _var_name, rhs = *node

          if rhs.send_type?
            autocorrect_send_node(corrector, node, rhs)
          elsif %i[and or].include?(rhs.type)
            autocorrect_boolean_node(corrector, node, rhs)
          end
        end

        def autocorrect_send_node(corrector, node, rhs)
          _receiver, method_name, args = *rhs
          apply_autocorrect(corrector, node, rhs, method_name.to_s, args)
        end

        def autocorrect_boolean_node(corrector, node, rhs)
          _first_operand, second_operand = *rhs
          apply_autocorrect(corrector, node, rhs, rhs.loc.operator.source, second_operand)
        end

        def apply_autocorrect(corrector, node, rhs, operator, new_rhs)
          corrector.insert_before(node.loc.operator, operator)
          corrector.replace(rhs, new_rhs.source)
        end
      end
    end
  end
end
