class ViewModel::SerializeContext

def self.normalize_includes(includes)

def self.normalize_includes(includes)
  case includes
  when Array
    includes.each_with_object({}) do |v, new_includes|
      new_includes.merge!(normalize_includes(v))
    end
  when Hash
    includes.each_with_object({}) do |(k,v), new_includes|
      new_includes[k.to_s] = normalize_includes(v)
    end
  when nil
    nil
  else
    { includes.to_s => nil }
  end
end

def self.shared_context_class

def self.shared_context_class
  SharedContext
end

def add_includes(includes)

def add_includes(includes)
  return if includes.blank?
  @include ||= {}
  @include.deep_merge!(self.class.normalize_includes(includes))
end

def add_prunes(prunes)

def add_prunes(prunes)
  return if prunes.blank?
  @prune ||= {}
  @prune.deep_merge!(self.class.normalize_includes(prunes))
end

def extract_referenced_views!

Return viewmodels referenced during serialization and clear @references.
def extract_referenced_views!
  refs = references.each.to_h
  references.clear!
  refs
end

def for_child(parent_viewmodel, association_name:, **rest)

def for_child(parent_viewmodel, association_name:, **rest)
  super(parent_viewmodel,
        association_name: association_name,
        include: @include.try { |i| i[association_name] },
        prune:   @prune.try   { |p| p[association_name] },
        **rest)
end

def includes_member?(member_name, default)

def includes_member?(member_name, default)
  member_name = member_name.to_s
  # Every node in the include tree is to be included
  included = @include.try { |is| is.has_key?(member_name) }
  # whereas only the leaves of the prune tree are to be removed
  pruned   = @prune.try { |ps| ps.fetch(member_name, :sentinel).nil? }
  (default || included) && !pruned
end

def initialize(include: nil, prune: nil, **rest)

def initialize(include: nil, prune: nil, **rest)
  super(**rest)
  @include = self.class.normalize_includes(include)
  @prune   = self.class.normalize_includes(prune)
end

def initialize_as_child(include:, prune:, **rest)

def initialize_as_child(include:, prune:, **rest)
  super(**rest)
  @include = include
  @prune   = prune
end

def serialize_references(json)

def serialize_references(json)
  reference_context = self.for_references
  # References should be serialized in a stable order to improve caching via
  # naive response hash.
  serialized_refs = {}
  while references.present?
    extract_referenced_views!.each do |ref, value|
      unless serialized_refs.has_key?(ref)
        serialized_refs[ref] = Jbuilder.new do |j|
          ViewModel.serialize(value, j, serialize_context: reference_context)
        end
      end
    end
  end
  serialized_refs.sort.each do |ref, value|
    json.set!(ref, value)
  end
end

def serialize_references_to_hash

def serialize_references_to_hash
  Jbuilder.new { |json| serialize_references(json) }.attributes!
end