class ViewModel::Migrator

def initialize(required_versions)

def initialize(required_versions)
  @paths = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h|
    if required_version != viewmodel_class.schema_version
      path = viewmodel_class.migration_path(from: required_version, to: viewmodel_class.schema_version)
      h[viewmodel_class.view_name] = path
    end
  end
  @versions = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h|
    h[viewmodel_class.view_name] = [required_version, viewmodel_class.schema_version]
  end
end

def migrate!(serialization)

def migrate!(serialization)
  references = (serialization['references'] ||= {})
  # First visit everything except references; there's no issue with adding
  # new references during this.
  migrate_tree!(serialization.except('references'), references: references)
  # While visiting references itself, we need to take care that we can
  # concurrently modify them (e.g. by adding new referenced views).
  # Moreover, such added references must themselves be visited, as they'll
  # be synthesized at the current version and so may need to be migrated
  # down to the client's requested version.
  visited_refs = []
  loop do
    unvisited_refs = references.keys - visited_refs
    break if unvisited_refs.empty?
    unvisited_refs.each do |ref|
      migrate_tree!(references[ref], references: references)
    end
    visited_refs.concat(unvisited_refs)
  end
  GarbageCollection.garbage_collect_references!(serialization)
  if references.empty?
    serialization.delete('references')
  end
end

def migrate_tree!(node, references:)

def migrate_tree!(node, references:)
  case node
  when Hash
    if (type = node[ViewModel::TYPE_ATTRIBUTE])
      version = node[ViewModel::VERSION_ATTRIBUTE]
      # We allow subtrees to be excluded from migration. This is used
      # internally to permit stub references that are not a full
      # serialization of the referenced type: see ViewModel::Cache.
      return if node[EXCLUDE_FROM_MIGRATION]
      if migrate_viewmodel!(type, version, node, references)
        node[ViewModel::MIGRATED_ATTRIBUTE] = true
      end
    end
    node.each_value do |child|
      migrate_tree!(child, references: references)
    end
  when Array
    node.each { |child| migrate_tree!(child, references: references) }
  end
end

def migrate_viewmodel!(_view_name, _version, _view_hash, _references)

def migrate_viewmodel!(_view_name, _version, _view_hash, _references)
  raise RuntimeError.new('abstract method')
end

def migrated_deep_schema_version(viewmodel_class, required_versions, include_referenced: true)

def migrated_deep_schema_version(viewmodel_class, required_versions, include_referenced: true)
  deep_schema_version = viewmodel_class.deep_schema_version(include_referenced: include_referenced)
  if required_versions.present?
    deep_schema_version = deep_schema_version.dup
    required_versions.each do |required_vm_class, required_version|
      name = required_vm_class.view_name
      if deep_schema_version.has_key?(name)
        deep_schema_version[name] = required_version
      end
    end
  end
  deep_schema_version
end