# frozen_string_literal: true

require "dry/matcher"
require "dry/matcher/match"

module Dry
  class Matcher
    # Built-in {Matcher} ready to use with `Result` or `Try` monads from
    # [dry-monads](/gems/dry-monads) or any other compatible gems.
    #
    # Provides {Case}s for two matchers:
    # * `:success` matches `Dry::Monads::Result::Success`
    #   and `Dry::Monads::Try::Value` (or any other monad that responds to
    #   `#to_result` returning result monad that is `#success?`)
    # * `:failure` matches `Dry::Monads::Result::Failure` and
    #   `Dry::Monads::Try::Error` (or any other monad that responds to
    #   `#to_result` returning result monad that is `#failure?`)
    #
    # @return [Dry::Matcher]
    #
    # @example Usage with `dry-monads`
    #   require 'dry/monads'
    #   require 'dry/matcher/result_matcher'
    #
    #   value = Dry::Monads::Result::Success.new('success!')
    #
    #   Dry::Matcher::ResultMatcher.(value) do |m|
    #     m.success do |v|
    #       "Yay: #{v}"
    #     end
    #
    #     m.failure do |v|
    #       "Boo: #{v}"
    #     end
    #   end #=> "Yay: success!"
    #
    # @example Usage with custom monad
    #   require 'dry/matcher/result_matcher'
    #
    #   class CustomBooleanMonad
    #     def initialize(value); @value = value; end
    #     attr_reader :value
    #     alias_method :success?, :value
    #     def failure?; !success?; end
    #     def to_result; self; end
    #   end
    #
    #   value = CustomBooleanMonad.new(nil)
    #
    #   Dry::Matcher::ResultMatcher.(value) do |m|
    #     m.success { |v| "#{v.inspect} is truthy" }
    #     m.failure { |v| "#{v.inspect} is falsey" }
    #   end # => "nil is falsey"
    #
    # @example Usage with error codes
    #   value = Dry::Monads::Result::Failure.new([:invalid, :reasons])
    #
    #   Dry::Matcher::ResultMatcher.(value) do |m|
    #     m.success do |v|
    #       "Yay: #{v}"
    #     end
    #
    #     m.failure(:not_found) do
    #       "No such thing"
    #     end
    #
    #     m.failure(:invalid) do |_code, errors|
    #       "Cannot be done: #{errors.inspect}"
    #     end
    #   end #=> "Cannot be done: :reasons"
    #
    ResultMatcher = Dry::Matcher.new(
      success: Case.new { |result, patterns|
        result = result.to_result

        if result.success?
          Dry::Matcher::PatternMatch.(result.value!, patterns)
        else
          Undefined
        end
      },
      failure: Case.new { |result, patterns|
        result = result.to_result

        if result.failure?
          Dry::Matcher::PatternMatch.(result.failure, patterns)
        else
          Undefined
        end
      }
    )
  end
end
