# frozen_string_literal: true

module ConvenientService
  module RSpec
    module Matchers
      module Custom
        class DelegateTo
          module Entities
            class Matcher
              module Entities
                module Chainings
                  module SubMatchers
                    class ReturnItsValue < Chainings::SubMatchers::Base
                      ##
                      # @param block_expectation_value [Object]
                      # @return [Boolean]
                      #
                      # @internal
                      #   TODO: Proper explanatory message when `matcher.delegation_value` raises exception.
                      #
                      def matches?(block_expectation_value)
                        super

                        ##
                        # TODO: `with_warmup`.
                        #
                        # IMPORTANT: `and_return_its_value` works only when `delegate_to` checks a pure function.
                        #
                        # For example (1):
                        #   def require_dependencies_pure
                        #     RequireDependenciesPure.call
                        #   end
                        #
                        #   class RequireDependenciesPure
                        #     def self.call
                        #       dependencies.require!
                        #
                        #       true
                        #     end
                        #   end
                        #
                        #   # Works since `RequireDependenciesPure.call` always returns `true`.
                        #   specify do
                        #     expect { require_dependencies_pure }
                        #       .to delegate_to(RequireDependenciesPure, :call)
                        #       .and_return_its_value
                        #   end
                        #
                        # Example (2):
                        #   def require_dependencies_not_pure
                        #     RequireDependenciesNotPure.call
                        #   end
                        #
                        #   class RequireDependenciesNotPure
                        #     def self.call
                        #       return false if dependencies.required?
                        #
                        #       dependencies.require!
                        #
                        #       true
                        #     end
                        #   end
                        #
                        #   # Does NOT work since `RequireDependenciesNotPure.call` returns `true` for the first time and `false` for the subsequent call.
                        #   specify do
                        #     expect { require_dependencies_not_pure }
                        #       .to delegate_to(RequireDependenciesNotPure, :call)
                        #       .and_return_its_value
                        #   end
                        #
                        block_expectation_value == matcher.delegation_value
                      end

                      ##
                      # @return [String]
                      #
                      def failure_message
                        [failure_message_permanent_part, same_visual_output_note].reject(&:empty?).join("\n\n")
                      end

                      ##
                      # @return [String]
                      #
                      def failure_message_when_negated
                        "expected `#{matcher.printable_block_expectation}` NOT to delegate to `#{matcher.printable_method}` and return its value, but it did."
                      end

                      private

                      def failure_message_permanent_part
                        <<~MESSAGE.chomp
                          expected `#{matcher.printable_block_expectation}` to delegate to `#{matcher.printable_method}` and return its value, but it didn't.

                          `#{matcher.printable_block_expectation}` returns `#{block_expectation_value.inspect}`, but delegation returns `#{matcher.delegation_value.inspect}`.
                        MESSAGE
                      end

                      ##
                      # @internal
                      #   NOTE: Early return is harder to understand in this particular case, that is why a casual if is used.
                      #
                      #   def note
                      #     return "" if block_expectation_value.inspect != matcher.delegation_value.inspect
                      #     return "" if block_expectation_value == matcher.delegation_value
                      #
                      #     # ...
                      #   end
                      #
                      def same_visual_output_note
                        if block_expectation_value.inspect == matcher.delegation_value.inspect && block_expectation_value != matcher.delegation_value
                          <<~MESSAGE.chomp
                            NOTE: `#{block_expectation_value.inspect}` and `#{matcher.delegation_value.inspect}` have the same visual output, but they are different objects in terms of `#==`.

                            If it is expected behavior, ignore this note.

                            Otherwise, define a meaningful `#==` for `#{block_expectation_value.class}` or adjust its `#inspect` to generate different output.
                          MESSAGE
                        else
                          ""
                        end
                      end
                    end
                  end
                end
              end
            end
          end
        end
      end
    end
  end
end