# frozen_string_literal: true

module RuboCop
  module Cop
    module Lint
      # Checks for unreachable code.
      # The check are based on the presence of flow of control
      # statement in non-final position in `begin` (implicit) blocks.
      #
      # @example
      #
      #   # bad
      #
      #   def some_method
      #     return
      #     do_something
      #   end
      #
      #   # bad
      #
      #   def some_method
      #     if cond
      #       return
      #     else
      #       return
      #     end
      #     do_something
      #   end
      #
      # @example
      #
      #   # good
      #
      #   def some_method
      #     do_something
      #   end
      class UnreachableCode < Base
        MSG = 'Unreachable code detected.'

        def on_begin(node)
          expressions = *node

          expressions.each_cons(2) do |expression1, expression2|
            next unless flow_expression?(expression1)

            add_offense(expression2)
          end
        end

        alias on_kwbegin on_begin

        private

        # @!method flow_command?(node)
        def_node_matcher :flow_command?, <<~PATTERN
          {
            return next break retry redo
            (send
             {nil? (const {nil? cbase} :Kernel)}
             {:raise :fail :throw :exit :exit! :abort}
             ...)
          }
        PATTERN

        def flow_expression?(node)
          return true if flow_command?(node)

          case node.type
          when :begin, :kwbegin
            expressions = *node
            expressions.any? { |expr| flow_expression?(expr) }
          when :if
            check_if(node)
          when :case
            check_case(node)
          else
            false
          end
        end

        def check_if(node)
          if_branch = node.if_branch
          else_branch = node.else_branch
          if_branch && else_branch && flow_expression?(if_branch) && flow_expression?(else_branch)
        end

        def check_case(node)
          else_branch = node.else_branch
          return false unless else_branch
          return false unless flow_expression?(else_branch)

          node.when_branches.all? { |branch| branch.body && flow_expression?(branch.body) }
        end
      end
    end
  end
end
