require 'dry/types/builder'
require 'dry/types/result'
require 'dry/types/options'

module Dry
  module Types
    class Definition
      include Type
      include Options
      include Builder
      include Dry::Equalizer(:primitive, :options, :meta)

      # @return [Class]
      attr_reader :primitive

      # @param [Class] primitive
      # @return [Type]
      def self.[](primitive)
        if primitive == ::Array
          Types::Array
        elsif primitive == ::Hash
          Types::Hash
        else
          self
        end
      end

      # @param [Type,Class] primitive
      # @param [Hash] options
      def initialize(primitive, options = {})
        super
        @primitive = primitive
        freeze
      end

      # @return [String]
      def name
        primitive.name
      end

      # @return [false]
      def default?
        false
      end

      # @return [false]
      def constrained?
        false
      end

      # @return [false]
      def optional?
        false
      end

      # @param [BasicObject] input
      # @return [BasicObject]
      def call(input)
        input
      end
      alias_method :[], :call

      # @param [Object] input
      # @param [#call,nil] block
      # @yieldparam [Failure] failure
      # @yieldreturn [Result]
      # @return [Result,Logic::Result] when a block is not provided
      # @return [nil] otherwise
      def try(input, &block)
        if valid?(input)
          success(input)
        else
          failure = failure(input, "#{input.inspect} must be an instance of #{primitive}")
          block ? yield(failure) : failure
        end
      end

      # @param (see Dry::Types::Success#initialize)
      # @return [Result::Success]
      def success(input)
        Result::Success.new(input)
      end

      # @param (see Failure#initialize)
      # @return [Result::Failure]
      def failure(input, error)
        Result::Failure.new(input, error)
      end

      # Checks whether value is of a #primitive class
      # @param [Object] value
      # @return [Boolean]
      def primitive?(value)
        value.is_a?(primitive)
      end
      alias_method :valid?, :primitive?
      alias_method :===, :primitive?

      # Return AST representation of a type definition
      #
      # @api public
      #
      # @return [Array]
      def to_ast(meta: true)
        [:definition, [primitive, meta ? self.meta : EMPTY_HASH]]
      end
    end
  end
end

require 'dry/types/array'
require 'dry/types/hash'
require 'dry/types/map'