# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      class MethodCallWithArgsParentheses
        # Style omit_parentheses
        module OmitParentheses
          TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze

          def on_send(node)
            return unless node.parenthesized?
            return if node.implicit_call?
            return if super_call_without_arguments?(node)
            return if allowed_camel_case_method_call?(node)
            return if legitimate_call_with_parentheses?(node)

            add_offense(offense_range(node)) do |corrector|
              if parentheses_at_the_end_of_multiline_call?(node)
                corrector.replace(args_begin(node), ' \\')
              else
                corrector.replace(args_begin(node), ' ')
              end
              corrector.remove(node.loc.end)
            end
          end
          alias on_csend on_send
          alias on_super on_send
          alias on_yield on_send

          private

          def offense_range(node)
            node.loc.begin.join(node.loc.end)
          end

          def message(_range = nil)
            'Omit parentheses for method calls with arguments.'
          end

          def super_call_without_arguments?(node)
            node.super_type? && node.arguments.none?
          end

          def allowed_camel_case_method_call?(node)
            node.camel_case_method? &&
              (node.arguments.none? ||
              cop_config['AllowParenthesesInCamelCaseMethod'])
          end

          def parentheses_at_the_end_of_multiline_call?(node)
            node.multiline? &&
              node.loc.begin.source_line
                  .gsub(TRAILING_WHITESPACE_REGEX, '')
                  .end_with?('(')
          end

          def legitimate_call_with_parentheses?(node)
            call_in_literals?(node) ||
              call_with_ambiguous_arguments?(node) ||
              call_in_logical_operators?(node) ||
              call_in_optional_arguments?(node) ||
              call_in_single_line_inheritance?(node) ||
              allowed_multiline_call_with_parentheses?(node) ||
              allowed_chained_call_with_parentheses?(node)
          end

          def call_in_literals?(node)
            node.parent &&
              (node.parent.pair_type? ||
              node.parent.array_type? ||
              node.parent.range_type? ||
              splat?(node.parent) ||
              ternary_if?(node.parent))
          end

          def call_in_logical_operators?(node)
            parent = node.parent&.block_type? ? node.parent.parent : node.parent
            parent &&
              (logical_operator?(parent) ||
              parent.send_type? &&
              parent.arguments.any?(&method(:logical_operator?)))
          end

          def call_in_optional_arguments?(node)
            node.parent &&
              (node.parent.optarg_type? || node.parent.kwoptarg_type?)
          end

          def call_in_single_line_inheritance?(node)
            node.parent&.class_type? && node.parent&.single_line?
          end

          def call_with_ambiguous_arguments?(node)
            call_with_braced_block?(node) ||
              call_as_argument_or_chain?(node) ||
              hash_literal_in_arguments?(node) ||
              node.descendants.any? do |n|
                ambigious_literal?(n) || logical_operator?(n) ||
                  call_with_braced_block?(n)
              end
          end

          def call_with_braced_block?(node)
            (node.send_type? || node.super_type?) &&
              node.block_node && node.block_node.braces?
          end

          def call_as_argument_or_chain?(node)
            node.parent &&
              (node.parent.send_type? && !assigned_before?(node.parent, node) ||
              node.parent.csend_type? || node.parent.super_type?)
          end

          def hash_literal_in_arguments?(node)
            node.arguments.any? do |n|
              hash_literal?(n) ||
                n.send_type? && node.descendants.any?(&method(:hash_literal?))
            end
          end

          def allowed_multiline_call_with_parentheses?(node)
            cop_config['AllowParenthesesInMultilineCall'] && node.multiline?
          end

          def allowed_chained_call_with_parentheses?(node)
            return false unless cop_config['AllowParenthesesInChaining']

            previous = node.descendants.first
            return false unless previous&.send_type?

            previous.parenthesized? ||
              allowed_chained_call_with_parentheses?(previous)
          end

          def ambigious_literal?(node)
            splat?(node) || ternary_if?(node) || regexp_slash_literal?(node) ||
              unary_literal?(node)
          end

          def splat?(node)
            node.splat_type? || node.kwsplat_type? || node.block_pass_type?
          end

          def ternary_if?(node)
            node.if_type? && node.ternary?
          end

          def logical_operator?(node)
            (node.and_type? || node.or_type?) && node.logical_operator?
          end

          def hash_literal?(node)
            node.hash_type? && node.braces?
          end

          def regexp_slash_literal?(node)
            node.regexp_type? && node.loc.begin.source == '/'
          end

          def unary_literal?(node)
            node.numeric_type? && node.sign? ||
              node.parent&.send_type? && node.parent&.unary_operation?
          end

          def assigned_before?(node, target)
            node.assignment? &&
              node.loc.operator.begin < target.loc.begin
          end
        end
      end
    end
  end
end
