module Braintree
  class ValidationErrorCollection
    include Enumerable

    def initialize(data)
      return if !data.is_a? Hash
      @errors = (data[:errors] || {}).map { |hash| Braintree::ValidationError.new(hash) }
      @nested = {}
      data.keys.each do |key|
        next if key == :errors
        @nested[key] = ValidationErrorCollection.new(data[key])
      end
    end

    # Accesses the error at the given index.
    def [](index)
      @errors[index]
    end

    # Returns an array of ValidationError objects at this level and all nested levels in the error
    # hierarchy
    def deep_errors
      ([@errors] + @nested.values.map { |error_collection| error_collection.deep_errors }).flatten
    end

    def deep_size
      size + @nested.values.inject(0) { |count, error_collection| count + error_collection.deep_size }
    end

    # Iterates over errors at the current level. Nested errors will not be yielded.
    def each(&block)
      @errors.each(&block)
    end

    # Returns a ValidationErrorCollection of errors nested under the given nested_key.
    # Returns nil if there are not any errors nested under the given key.
    def for(nested_key)
      @nested[nested_key]
    end

    def for_index(index)
      self.for("index_#{index}".to_sym)
    end

    def inspect
      "#<#{self.class} errors#{_inner_inspect}>"
    end

    # Returns an array of ValidationError objects on the given attribute.
    def on(attribute)
      @errors.select { |error| error.attribute == attribute.to_s }
    end

    # Returns an array of ValidationError objects at the given level in the error hierarchy
    def shallow_errors
      @errors.dup
    end

    # The number of errors at this level. This does not include nested errors.
    def size
      @errors.size
    end

    def _inner_inspect(scope = [])
      all = []
      scope_string = scope.join("/")
      if @errors.any?
        all << "#{scope_string}:[" + @errors.map { |e| "(#{e.code}) #{e.message}" }.join(", ") + "]"
      end
      @nested.each do |key, values|
        all << values._inner_inspect(scope + [key])
      end
      all.join(", ")
    end
  end
end