class Dry::Types::Printer

@api private

def call(type)

def call(type)
  output = "".dup
  visit(type) { |str| output << str }
  "#<Dry::Types[#{output}]>"
end

def initialize

def initialize
  @composition_printers = {}
  freeze
end

def visit(type, &)

def visit(type, &)
  print_with = MAPPING.fetch(type.class) do
    if type.class < Constructor
      :visit_constructor
    elsif type.is_a?(Type)
      return yield type.inspect
    else
      raise ArgumentError, "Do not know how to print #{type.class}"
    end
  end
  send(print_with, type, &)
end

def visit_any(_) = yield "Any"

def visit_any(_) = yield "Any"

def visit_array(type)

def visit_array(type)
  visit_options(EMPTY_HASH, type.meta) do |opts|
    yield "Array#{opts}"
  end
end

def visit_array_member(array)

def visit_array_member(array)
  visit(array.member) do |type|
    visit_options(EMPTY_HASH, array.meta) do |opts|
      yield "Array<#{type}#{opts}>"
    end
  end
end

def visit_callable(callable)

def visit_callable(callable)
  fn = callable.is_a?(String) ? FnContainer[callable] : callable
  case fn
  when ::Method
    yield "#{fn.receiver}.#{fn.name}"
  when ::Proc
    path, line = fn.source_location
    if line&.zero?
      yield ".#{path}"
    elsif path
      yield "#{path.sub("#{Dir.pwd}/", EMPTY_STRING)}:#{line}"
    else
      match = fn.to_s.match(/\A#<Proc:0x\h+\(&:(?<name>\w+)\)(:? \(lambda\))?>\z/) # rubocop:disable Lint/MixedRegexpCaptureTypes
      if match
        yield ".#{match[:name]}"
      elsif fn.lambda?
        yield "(lambda)"
      else
        yield "(proc)"
      end
    end
  else
    call = fn.method(:call)
    if call.owner == fn.class
      yield "#{fn.class}#call"
    else
      yield "#{fn}.call"
    end
  end
end

def visit_composition(composition, &)

def visit_composition(composition, &)
  klass = composition.class
  @composition_printers[klass] = Composition.new(self, klass)
  @composition_printers[klass].visit(composition, &)
end

def visit_constrained(constrained)

def visit_constrained(constrained)
  visit(constrained.type) do |type|
    options = constrained.options.dup
    rule = options.delete(:rule)
    visit_options(options) do |_opts|
      yield "Constrained<#{type} rule=[#{rule}]>"
    end
  end
end

def visit_constructor(constructor)

def visit_constructor(constructor)
  visit(constructor.type) do |type|
    visit_callable(constructor.fn.fn) do |fn|
      options = constructor.options.dup
      options.delete(:fn)
      visit_options(options) do |opts|
        yield "Constructor<#{type} fn=#{fn}#{opts}>"
      end
    end
  end
end

def visit_default(default)

def visit_default(default)
  visit(default.type) do |type|
    visit_options(default.options) do |opts|
      if default.is_a?(Default::Callable)
        visit_callable(default.value) do |fn|
          yield "Default<#{type} value_fn=#{fn}#{opts}>"
        end
      else
        yield "Default<#{type} value=#{default.value.inspect}#{opts}>"
      end
    end
  end
end

def visit_enum(enum)

def visit_enum(enum)
  visit(enum.type) do |type|
    options = enum.options.dup
    mapping = options.delete(:mapping)
    visit_options(options) do |opts|
      if mapping == enum.inverted_mapping
        yield "Enum(#{enum.joined_values})<#{type}#{opts}>"
      else
        mapping_str = mapping.map { |key, value|
          "#{key.inspect}=>#{value.inspect}"
        }.join(", ")
        yield "Enum<#{type} mapping={#{mapping_str}}#{opts}>"
      end
    end
  end
end

def visit_hash(hash)

def visit_hash(hash)
  options = hash.options.dup
  type_fn_str = ""
  if (type_fn = options.delete(:type_transform_fn))
    visit_callable(type_fn) do |fn|
      type_fn_str = "type_fn=#{fn}"
    end
  end
  visit_options(options, hash.meta) do |opts|
    if opts.empty? && type_fn_str.empty?
      yield "Hash"
    else
      yield "Hash<#{type_fn_str}#{opts}>"
    end
  end
end

def visit_key(key)

def visit_key(key)
  visit(key.type) do |type|
    if key.required?
      yield "#{key.name}: #{type}"
    else
      yield "#{key.name}?: #{type}"
    end
  end
end

def visit_lax(lax)

def visit_lax(lax)
  visit(lax.type) do |type|
    yield "Lax<#{type}>"
  end
end

def visit_map(map)

def visit_map(map)
  visit(map.key_type) do |key|
    visit(map.value_type) do |value|
      options = map.options.dup
      options.delete(:key_type)
      options.delete(:value_type)
      visit_options(options) do |_opts|
        yield "Map<#{key} => #{value}>"
      end
    end
  end
end

def visit_maybe(maybe)

Other tags:
    Api: - private
def visit_maybe(maybe)
  visit(maybe.type) do |type|
    yield "Maybe<#{type}>"
  end
end

def visit_nominal(type)

def visit_nominal(type)
  visit_options(type.options, type.meta) do |opts|
    yield "Nominal<#{type.primitive}#{opts}>"
  end
end

def visit_options(options, meta = EMPTY_HASH) # rubocop:disable Metrics/PerceivedComplexity

rubocop:disable Metrics/PerceivedComplexity
def visit_options(options, meta = EMPTY_HASH) # rubocop:disable Metrics/PerceivedComplexity
  if options.empty? && meta.empty?
    yield ""
  else
    opts = options.empty? ? "" : " options=#{options.inspect}"
    if meta.empty?
      yield opts
    else
      values = meta.map do |key, value|
        case key
        when Symbol
          "#{key}: #{value.inspect}"
        else
          "#{key.inspect}=>#{value.inspect}"
        end
      end
      yield "#{opts} meta={#{values.join(", ")}}"
    end
  end
end

def visit_schema(schema)

def visit_schema(schema)
  options = schema.options.dup
  size = schema.count
  key_fn_str = ""
  type_fn_str = ""
  strict_str = ""
  strict_str = "strict " if options.delete(:strict)
  if (key_fn = options.delete(:key_transform_fn))
    visit_callable(key_fn) do |fn|
      key_fn_str = "key_fn=#{fn} "
    end
  end
  if (type_fn = options.delete(:type_transform_fn))
    visit_callable(type_fn) do |fn|
      type_fn_str = "type_fn=#{fn} "
    end
  end
  keys = options.delete(:keys)
  visit_options(options, schema.meta) do |opts|
    opts = "#{opts[1..]} " unless opts.empty?
    schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}"
    header = "Schema<#{schema_parameters}keys={"
    if size.zero?
      yield "#{header}}>"
    else
      yield header.dup << keys.map { |key|
        visit(key) { |type| type }
      }.join(" ") << "}>"
    end
  end
end