class ViewModel::ActiveRecord::UpdateData

def self.check_duplicate_updates(updates, type:)

def self.check_duplicate_updates(updates, type:)
  # Ensure that no root is referred to more than once
  duplicates = updates.duplicates_by { |upd| upd.viewmodel_reference if upd.id }
  if duplicates.present?
    raise ViewModel::DeserializationError::DuplicateNodes.new(type, duplicates.keys)
  end
end

def self.empty_update_for(viewmodel)

def self.empty_update_for(viewmodel)
  metadata = ViewModel::Metadata.new(viewmodel.id, viewmodel.view_name, viewmodel.class.schema_version, false)
  self.new(viewmodel.class, metadata, {}, [])
end

def self.parse_associated(association_data, blame_reference, valid_reference_keys, child_hash)

def self.parse_associated(association_data, blame_reference, valid_reference_keys, child_hash)
  child_metadata = ViewModel.extract_viewmodel_metadata(child_hash)
  child_viewmodel_class =
    association_data.viewmodel_class_for_name(child_metadata.view_name)
  if child_viewmodel_class.nil?
    raise ViewModel::DeserializationError::InvalidAssociationType.new(
            association_data.association_name.to_s,
            child_metadata.view_name,
            blame_reference)
  end
  verify_schema_version!(child_viewmodel_class, child_metadata.schema_version, child_metadata.id) if child_metadata.schema_version
  UpdateData.new(child_viewmodel_class, child_metadata, child_hash, valid_reference_keys)
end

def self.parse_hashes(root_subtree_hashes, referenced_subtree_hashes = {})

def self.parse_hashes(root_subtree_hashes, referenced_subtree_hashes = {})
  valid_reference_keys = referenced_subtree_hashes.keys.to_set
  valid_reference_keys.each do |ref|
    unless ref.is_a?(String)
      raise ViewModel::DeserializationError::InvalidSyntax.new("Invalid reference string: #{ref}")
    end
  end
  # Construct root UpdateData
  root_updates = root_subtree_hashes.map do |subtree_hash|
    metadata = ViewModel.extract_viewmodel_metadata(subtree_hash)
    viewmodel_class = ViewModel::Registry.for_view_name(metadata.view_name)
    verify_schema_version!(viewmodel_class, metadata.schema_version, metadata.id) if metadata.schema_version
    UpdateData.new(viewmodel_class, metadata, subtree_hash, valid_reference_keys)
  end
  # Ensure that no root is referred to more than once
  check_duplicate_updates(root_updates, type: 'root')
  # Construct reference UpdateData
  referenced_updates = referenced_subtree_hashes.transform_values do |subtree_hash|
    metadata = ViewModel.extract_viewmodel_metadata(subtree_hash)
    viewmodel_class = ViewModel::Registry.for_view_name(metadata.view_name)
    verify_schema_version!(viewmodel_class, metadata.schema_version, metadata.id) if metadata.schema_version
    UpdateData.new(viewmodel_class, metadata, subtree_hash, valid_reference_keys)
  end
  check_duplicate_updates(referenced_updates.values, type: 'reference')
  return root_updates, referenced_updates
end

def self.reference_only_hash?(hash)

def self.reference_only_hash?(hash)
  hash.size == 2 && hash.has_key?(ViewModel::ID_ATTRIBUTE) && hash.has_key?(ViewModel::TYPE_ATTRIBUTE)
end

def self.verify_schema_version!(viewmodel_class, schema_version, id)

def self.verify_schema_version!(viewmodel_class, schema_version, id)
  unless viewmodel_class.accepts_schema_version?(schema_version)
    raise ViewModel::DeserializationError::SchemaVersionMismatch.new(
            viewmodel_class,
            schema_version,
            ViewModel::Reference.new(viewmodel_class, id))
  end
end

def [](name)

def [](name)
  case name
  when :id
    id
  when :_type
    viewmodel_class.view_name
  else
    attributes.fetch(name) { associations.fetch(name) { referenced_associations.fetch(name) } }
  end
end

def blame_reference

def blame_reference
  ViewModel::Reference.new(self.viewmodel_class, self.id)
end

def build_preload_specs(association_data, updates)

def build_preload_specs(association_data, updates)
  if association_data.polymorphic?
    updates.map do |update_data|
      target_model = update_data.viewmodel_class.model_class
      DeepPreloader::PolymorphicSpec.new(
        target_model.name => update_data.preload_dependencies)
    end
  else
    updates.map { |update_data| update_data.preload_dependencies }
  end
end

def has_key?(name)

def has_key?(name)
  case name
  when :id, :_type
    true
  else
    attributes.has_key?(name) || associations.has_key?(name) || referenced_associations.has_key?(name)
  end
end

def initialize(viewmodel_class, metadata, hash_data, valid_reference_keys)

def initialize(viewmodel_class, metadata, hash_data, valid_reference_keys)
  self.viewmodel_class = viewmodel_class
  self.metadata        = metadata
  self.attributes      = {}
  self.associations    = {}
  self.referenced_associations = {}
  parse(hash_data, valid_reference_keys)
end

def merge_preload_specs(association_data, specs)

def merge_preload_specs(association_data, specs)
  empty = association_data.polymorphic? ? DeepPreloader::PolymorphicSpec.new : DeepPreloader::Spec.new
  specs.inject(empty) { |acc, spec| acc.merge!(spec) }
end

def parse(hash_data, valid_reference_keys)

def parse(hash_data, valid_reference_keys)
  hash_data = hash_data.dup
  # handle view pre-parsing if defined
  self.viewmodel_class.pre_parse(viewmodel_reference, metadata, hash_data) if self.viewmodel_class.respond_to?(:pre_parse)
  hash_data.keys.each do |key| # rubocop:disable Style/HashEachMethods
    if self.viewmodel_class.respond_to?(:"pre_parse_#{key}")
      self.viewmodel_class.public_send("pre_parse_#{key}", viewmodel_reference, metadata, hash_data, hash_data.delete(key))
    end
  end
  hash_data.each do |name, value|
    member_data = self.viewmodel_class._members[name]
    case member_data
    when ViewModel::Record::AttributeData
      attributes[name] = value
    when AssociationData
      association_data = member_data
      case
      when value.nil?
        if association_data.collection?
          raise ViewModel::DeserializationError::InvalidSyntax.new(
                  "Invalid collection update value 'nil' for association '#{name}'",
                  blame_reference)
        end
        if association_data.referenced?
          referenced_associations[name] = nil
        else
          associations[name] = nil
        end
      when association_data.referenced?
        if association_data.collection?
          referenced_associations[name] =
            ReferencedCollectionUpdate::Parser
              .new(association_data, blame_reference, valid_reference_keys)
              .parse(value)
        else
          # Extract and check reference
          ref = ViewModel.extract_reference_metadata(value)
          unless valid_reference_keys.include?(ref)
            raise ViewModel::DeserializationError::InvalidSharedReference.new(ref, blame_reference)
          end
          referenced_associations[name] = ref
        end
      else
        associations[name] =
          if association_data.collection?
            OwnedCollectionUpdate::Parser
              .new(association_data, blame_reference, valid_reference_keys)
              .parse(value)
          else # not a collection
            if value.nil? # rubocop:disable Style/IfInsideElse
              nil
            else
              self.class.parse_associated(association_data, blame_reference, valid_reference_keys, value)
            end
          end
      end
    else
      raise ViewModel::DeserializationError::UnknownAttribute.new(name, blame_reference)
    end
  end
end

def preload_dependencies

associations necessary to perform update.
Updates in terms of activerecord associations: used for preloading subtree
def preload_dependencies
  deps = {}
  associations.merge(referenced_associations).each do |assoc_name, reference|
    association_data = self.viewmodel_class._association_data(assoc_name)
    preload_specs = build_preload_specs(association_data,
                                        to_sequence(assoc_name, reference))
    referenced_deps = merge_preload_specs(association_data, preload_specs)
    if association_data.through?
      referenced_deps = DeepPreloader::Spec.new(association_data.indirect_reflection.name.to_s => referenced_deps)
    end
    deps[association_data.direct_reflection.name.to_s] = referenced_deps
  end
  deserialization_includes = viewmodel_class.deserialization_includes
  unless deserialization_includes.is_a?(DeepPreloader::AbstractSpec)
    deserialization_includes = DeepPreloader::Spec.parse(deserialization_includes)
  end
  DeepPreloader::Spec.new(deps).merge!(deserialization_includes)
end

def reference_only?

def reference_only?
  attributes.empty? && associations.empty? && referenced_associations.empty?
end

def to_sequence(name, value)

Produce a sequence of update datas for a given association update value, in the spirit of Array.wrap.
def to_sequence(name, value)
  association_data = self.viewmodel_class._association_data(name)
  case
  when value.nil?
    []
  when association_data.referenced?
    []
  when association_data.collection? # nested, because of referenced? check above
    value.update_datas
  else
    [value]
  end
end

def viewmodel_reference

def viewmodel_reference
  ViewModel::Reference.new(viewmodel_class, id)
end