# frozen_string_literal: true

module JWT
  module Claims
    # @private
    module Verifier
      VERIFIERS = {
        exp: ->(options) { Claims::Expiration.new(leeway: options.dig(:exp, :leeway)) },
        nbf: ->(options) { Claims::NotBefore.new(leeway: options.dig(:nbf, :leeway)) },
        iss: ->(options) { Claims::Issuer.new(issuers: options[:iss]) },
        iat: ->(*)       { Claims::IssuedAt.new },
        jti: ->(options) { Claims::JwtId.new(validator: options[:jti]) },
        aud: ->(options) { Claims::Audience.new(expected_audience: options[:aud]) },
        sub: ->(options) { Claims::Subject.new(expected_subject: options[:sub]) },

        required: ->(options) { Claims::Required.new(required_claims: options[:required]) },
        numeric: ->(*)        { Claims::Numeric.new }
      }.freeze

      private_constant(:VERIFIERS)

      class << self
        # @private
        def verify!(context, *options)
          iterate_verifiers(*options) do |verifier, verifier_options|
            verify_one!(context, verifier, verifier_options)
          end
          nil
        end

        # @private
        def errors(context, *options)
          errors = []
          iterate_verifiers(*options) do |verifier, verifier_options|
            verify_one!(context, verifier, verifier_options)
          rescue ::JWT::DecodeError => e
            errors << Error.new(message: e.message)
          end
          errors
        end

        # @private
        def iterate_verifiers(*options)
          options.each do |element|
            if element.is_a?(Hash)
              element.each_key { |key| yield(key, element) }
            else
              yield(element, {})
            end
          end
        end

        private

        def verify_one!(context, verifier, options)
          verifier_builder = VERIFIERS.fetch(verifier) { raise ArgumentError, "#{verifier} not a valid claim verifier" }
          verifier_builder.call(options || {}).verify!(context: context)
        end
      end
    end
  end
end
