require 'dry/equalizer'

module Dry
  module Types
    class Schema < Hash
      # Proxy type for schema keys. Contains only key name and
      # whether it's required or not. All other calls deletaged
      # to the wrapped type.
      #
      # @see Dry::Types::Schema
      class Key
        include Type
        include Dry::Equalizer(:name, :type, :options, inspect: false)
        include Decorator
        include Builder
        include Printable

        # @return [Symbol]
        attr_reader :name

        # @api private
        def initialize(type, name, required: Undefined, **options)
          required = Undefined.default(required) do
            type.meta.fetch(:required) { !type.meta.fetch(:omittable, false) }
          end

          super(type, name, required: required, **options)
          @name = name
        end

        # @see Dry::Types::Nominal#call
        def call(input, &block)
          type.(input, &block)
        end

        # @see Dry::Types::Nominal#try
        def try(input, &block)
          type.try(input, &block)
        end

        # Whether the key is required in schema input
        #
        # @return [Boolean]
        def required?
          options.fetch(:required)
        end

        # Control whether the key is required
        #
        # @overload required
        #   @return [Boolean]
        #
        # @overload required(required)
        #   Change key's "requireness"
        #
        #   @param [Boolean] required New value
        #   @return [Dry::Types::Schema::Key]
        def required(required = Undefined)
          if Undefined.equal?(required)
            options.fetch(:required)
          else
            with(required: required)
          end
        end

        # Make key not required
        #
        # @return [Dry::Types::Schema::Key]
        def omittable
          required(false)
        end

        # Construct a default type. Default values are
        # evaluated/applied when key is absent in schema
        # input.
        #
        # @see Dry::Types::Default
        # @return [Dry::Types::Schema::Key]
        def default(input = Undefined, &block)
          new(type.default(input, &block))
        end

        # Replace the underlying type
        # @param [Dry::Types::Type] type
        # @return [Dry::Types::Schema::Key]
        def new(type)
          self.class.new(type, name, options)
        end

        # @see Dry::Types::Safe
        # @return [Dry::Types::Schema::Key]
        def safe
          new(type.safe)
        end

        # Dump to internal AST representation
        #
        # @return [Array]
        def to_ast(meta: true)
          [
            :key,
            [
              name,
              required,
              type.to_ast(meta: meta)
            ]
          ]
        end

        # Get/set type metadata. The Key type doesn't have
        # its out meta, it delegates these calls to the underlying
        # type.
        #
        # @overload meta
        #   @return [Hash] metadata associated with type
        #
        # @overload meta(data)
        #   @param [Hash] new metadata to merge into existing metadata
        #   @return [Type] new type with added metadata
        def meta(data = nil)
          if data.nil?
            type.meta
          else
            new(type.meta(data))
          end
        end
      end
    end
  end
end