module Lutaml::Model::Serialize::ClassMethods

def add_custom_handling_methods_to_model(klass)

def add_custom_handling_methods_to_model(klass)
  Utils.add_boolean_accessor_if_not_defined(klass, :ordered)
  Utils.add_boolean_accessor_if_not_defined(klass, :mixed)
  Utils.add_accessor_if_not_defined(klass, :element_order)
  Utils.add_accessor_if_not_defined(klass, :encoding)
  Utils.add_method_if_not_defined(klass,
                                  :using_default_for) do |attribute_name|
    @using_default ||= {}
    @using_default[attribute_name] = true
  end
  Utils.add_method_if_not_defined(klass,
                                  :value_set_for) do |attribute_name|
    @using_default ||= {}
    @using_default[attribute_name] = false
  end
  Utils.add_method_if_not_defined(klass,
                                  :using_default?) do |attribute_name|
    @using_default ||= {}
    !!@using_default[attribute_name]
  end
end

def add_enum_getter_if_not_defined(klass, enum_name, collection)

def add_enum_getter_if_not_defined(klass, enum_name, collection)
  Utils.add_method_if_not_defined(klass, enum_name) do
    i = instance_variable_get(:"@#{enum_name}") || []
    if !collection && i.is_a?(Array)
      i.first
    else
      i.uniq
    end
  end
end

def add_enum_methods_to_model(klass, enum_name, values, collection: false)

def add_enum_methods_to_model(klass, enum_name, values, collection: false)
  add_enum_getter_if_not_defined(klass, enum_name, collection)
  add_enum_setter_if_not_defined(klass, enum_name, values, collection)
  return unless values.all?(::String)
  values.each do |value|
    Utils.add_method_if_not_defined(klass, "#{value}?") do
      curr_value = public_send(:"#{enum_name}")
      if collection
        curr_value.include?(value)
      else
        curr_value == value
      end
    end
    Utils.add_method_if_not_defined(klass, value.to_s) do
      public_send(:"#{value}?")
    end
    Utils.add_method_if_not_defined(klass, "#{value}=") do |val|
      value_set_for(enum_name)
      enum_vals = public_send(:"#{enum_name}")
      enum_vals = if !!val
                    if collection
                      enum_vals << value
                    else
                      [value]
                    end
                  elsif collection
                    enum_vals.delete(value)
                    enum_vals
                  else
                    []
                  end
      instance_variable_set(:"@#{enum_name}", enum_vals)
    end
    Utils.add_method_if_not_defined(klass, "#{value}!") do
      public_send(:"#{value}=", true)
    end
  end
end

def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)

def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
  Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
    value = [value] unless value.is_a?(Array)
    value_set_for(enum_name)
    if collection
      curr_value = public_send(:"#{enum_name}")
      instance_variable_set(:"@#{enum_name}", curr_value + value)
    else
      instance_variable_set(:"@#{enum_name}", value)
    end
  end
end

def apply_hash_mapping(doc, instance, format, options = {})

def apply_hash_mapping(doc, instance, format, options = {})
  mappings = options[:mappings] || mappings_for(format).mappings
  mappings.each do |rule|
    raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
    attr = attribute_for_rule(rule)
    names = rule.multiple_mappings? ? rule.name : [rule.name]
    value = names.collect do |rule_name|
      if rule.root_mapping?
        doc
      elsif rule.raw_mapping?
        adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
        adapter.new(doc).public_send(:"to_#{format}")
      elsif doc.key?(rule_name.to_s)
        doc[rule_name.to_s]
      elsif doc.key?(rule_name.to_sym)
        doc[rule_name.to_sym]
      else
        attr&.default
      end
    end.compact.first
    if rule.using_custom_methods?
      if Utils.present?(value)
        value = new.send(rule.custom_methods[:from], instance, value)
      end
      next
    end
    value = translate_mappings(value, rule.hash_mappings, attr, format)
    value = attr.cast(value, format) unless rule.hash_mappings
    attr.valid_collection!(value, self)
    rule.deserialize(instance, value, attributes, self)
  end
  instance
end

def apply_mappings(doc, format, options = {})

def apply_mappings(doc, format, options = {})
  instance = options[:instance] || model.new
  return instance if Utils.blank?(doc)
  options[:mappings] = mappings_for(format).mappings
  return apply_xml_mapping(doc, instance, options) if format == :xml
  apply_hash_mapping(doc, instance, format, options)
end

def apply_xml_mapping(doc, instance, options = {})

def apply_xml_mapping(doc, instance, options = {})
  options = Utils.deep_dup(options)
  instance.encoding = options[:encoding]
  return instance unless doc
  if options[:default_namespace].nil?
    options[:default_namespace] =
      mappings_for(:xml)&.namespace_uri
  end
  mappings = options[:mappings] || mappings_for(:xml).mappings
  if doc.is_a?(Array)
    raise Lutaml::Model::CollectionTrueMissingError(self, option[:caller_class])
  end
  if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash)
    instance.element_order = doc.item_order
    instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
    instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
  end
  if doc["attributes"]&.key?("__schema_location")
    instance.schema_location = Lutaml::Model::SchemaLocation.new(
      schema_location: doc["attributes"]["__schema_location"][:schema_location],
      prefix: doc["attributes"]["__schema_location"][:prefix],
      namespace: doc["attributes"]["__schema_location"][:namespace],
    )
  end
  defaults_used = []
  mappings.each do |rule|
    raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
    attr = attribute_for_rule(rule)
    value = if rule.raw_mapping?
              doc.node.inner_xml
            elsif rule.content_mapping?
              doc[rule.content_key]
            elsif val = value_for_rule(doc, rule, options)
              val
            else
              defaults_used << rule.to
              attr&.default || rule.to_value_for(instance)
            end
    value = normalize_xml_value(value, rule, attr, options)
    rule.deserialize(instance, value, attributes, self)
  end
  defaults_used.each do |attribute_name|
    instance.using_default_for(attribute_name)
  end
  instance
end

def attribute(name, type, options = {})

Define an attribute for the model
def attribute(name, type, options = {})
  attr = Attribute.new(name, type, options)
  attributes[name] = attr
  if attr.enum?
    add_enum_methods_to_model(
      model,
      name,
      options[:values],
      collection: options[:collection],
    )
  else
    define_method(name) do
      instance_variable_get(:"@#{name}")
    end
    define_method(:"#{name}=") do |value|
      value_set_for(name)
      instance_variable_set(:"@#{name}", attr.cast_value(value))
    end
  end
end

def attribute_for_child(child_name, format)

def attribute_for_child(child_name, format)
  mapping_rule = mappings_for(format).find_by_name(child_name)
  attribute_for_rule(mapping_rule) if mapping_rule
end

def attribute_for_rule(rule)

def attribute_for_rule(rule)
  return attributes[rule.to] unless rule.delegate
  attributes[rule.delegate].type.attributes[rule.to]
end

def cast(value)

def cast(value)
  value
end

def cast_value?(attr, rule)

def cast_value?(attr, rule)
  attr &&
    !rule.raw_mapping? &&
    !rule.content_mapping? &&
    !rule.custom_methods[:from]
end

def default_mappings(format)

def default_mappings(format)
  klass = format == :xml ? XmlMapping : KeyValueMapping
  klass.new.tap do |mapping|
    attributes&.each_key do |name|
      mapping.map_element(
        name.to_s,
        to: name,
      )
    end
    mapping.root(to_s.split("::").last) if format == :xml
  end
end

def ensure_utf8(value)

def ensure_utf8(value)
  case value
  when String
    value.encode("UTF-8", invalid: :replace, undef: :replace,
                          replace: "")
  when Array
    value.map { |v| ensure_utf8(v) }
  when Hash
    value.transform_keys do |k|
      ensure_utf8(k)
    end.transform_values do |v|
      ensure_utf8(v)
    end
  else
    value
  end
end

def enums

def enums
  attributes.select { |_, attr| attr.enum? }
end

def generate_hash_from_child_mappings(attr, value, format, child_mappings)

def generate_hash_from_child_mappings(attr, value, format, child_mappings)
  return value unless child_mappings
  hash = {}
  if child_mappings.values == [:key]
    klass = value.first.class
    mappings = klass.mappings_for(format)
    klass.attributes.each_key do |name|
      next if child_mappings.key?(name.to_sym) || child_mappings.key?(name.to_s)
      child_mappings[name.to_sym] = mappings.find_by_to(name)&.name.to_s || name.to_s
    end
  end
  value.each do |child_obj|
    map_key = nil
    map_value = {}
    mapping_rules = attr.type.mappings_for(format)
    child_mappings.each do |attr_name, path|
      mapping_rule = mapping_rules.find_by_to(attr_name)
      attr_value = child_obj.send(attr_name)
      attr_value = if attr_value.is_a?(Lutaml::Model::Serialize)
                     attr_value.to_yaml_hash
                   elsif attr_value.is_a?(Array) && attr_value.first.is_a?(Lutaml::Model::Serialize)
                     attr_value.map(&:to_yaml_hash)
                   else
                     attr_value
                   end
      next unless mapping_rule&.render?(attr_value)
      if path == :key
        map_key = attr_value
      elsif path == :value
        map_value = attr_value
      else
        path = [path] unless path.is_a?(Array)
        path[0...-1].inject(map_value) do |acc, k|
          acc[k.to_s] ||= {}
        end.public_send(:[]=, path.last.to_s, attr_value)
      end
    end
    map_value = nil if map_value.empty?
    hash[map_key] = map_value
  end
  hash
end

def handle_delegate(instance, rule, hash, format)

def handle_delegate(instance, rule, hash, format)
  name = rule.to
  value = instance.send(rule.delegate).send(name)
  return if value.nil? && !rule.render_nil
  attribute = instance.send(rule.delegate).class.attributes[name]
  rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
  hash[rule_from_name] = attribute.serialize(value, format)
end

def hash_representation(instance, format, options = {})

def hash_representation(instance, format, options = {})
  only = options[:only]
  except = options[:except]
  mappings = mappings_for(format).mappings
  mappings.each_with_object({}) do |rule, hash|
    name = rule.to
    next if except&.include?(name) || (only && !only.include?(name))
    next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))
    next handle_delegate(instance, rule, hash, format) if rule.delegate
    if rule.custom_methods[:to]
      next instance.send(rule.custom_methods[:to], instance, hash)
    end
    value = instance.send(name)
    if rule.raw_mapping?
      adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
      return adapter.parse(value, options)
    end
    attribute = attributes[name]
    next hash.merge!(generate_hash_from_child_mappings(attribute, value, format, rule.root_mappings)) if rule.root_mapping?
    value = if rule.child_mappings
              generate_hash_from_child_mappings(attribute, value, format, rule.child_mappings)
            else
              attribute.serialize(value, format, options)
            end
    next unless rule.render?(value)
    rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
    hash[rule_from_name] = value
  end
end

def included(base)

def included(base)
  base.extend(ClassMethods)
  base.initialize_attrs(self)
end

def inherited(subclass)

def inherited(subclass)
  super
  subclass.initialize_attrs(self)
end

def initialize_attrs(source_class)

def initialize_attrs(source_class)
  @mappings = Utils.deep_dup(source_class.instance_variable_get(:@mappings)) || {}
  @attributes = Utils.deep_dup(source_class.instance_variable_get(:@attributes)) || {}
  instance_variable_set(:@model, self)
end

def key_value(&block)

def key_value(&block)
  Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
    mappings[format] ||= KeyValueMapping.new
    mappings[format].instance_eval(&block)
  end
end

def mappings_for(format)

def mappings_for(format)
  mappings[format] || default_mappings(format)
end

def model(klass = nil)

def model(klass = nil)
  if klass
    @model = klass
    add_custom_handling_methods_to_model(klass)
  else
    @model
  end
end

def normalize_xml_value(value, rule, attr, options = {})

def normalize_xml_value(value, rule, attr, options = {})
  value = [value].compact if attr&.collection? && !value.is_a?(Array)
  value = if value.is_a?(Array)
            value.map do |v|
              text_hash?(attr, v) ? v.text : v
            end
          elsif attr&.raw? && value
            value.node.children.map(&:to_xml).join
          elsif text_hash?(attr, value)
            value.text
          else
            value
          end
  return value unless cast_value?(attr, rule)
  options.merge(caller_class: self, mixed_content: rule.mixed_content)
  attr.cast(
    value,
    :xml,
    options,
  )
end

def text_hash?(attr, value)

def text_hash?(attr, value)
  return false unless value.is_a?(Hash)
  return value.one? && value.text? unless attr
  !(attr.type <= Serialize) && attr.type != Lutaml::Model::Type::Hash
end

def translate_mappings(hash, child_mappings, attr, format)

def translate_mappings(hash, child_mappings, attr, format)
  return hash unless child_mappings
  hash.map do |key, value|
    child_hash = child_mappings.to_h do |attr_name, path|
      attr_value = if path == :key
                     key
                   elsif path == :value
                     value
                   else
                     path = [path] unless path.is_a?(Array)
                     value.dig(*path.map(&:to_s))
                   end
      attr_rule = attr.type.mappings_for(format).find_by_to(attr_name)
      [attr_rule.from.to_s, attr_value]
    end
    if child_mappings.values == [:key] && hash.values.all?(Hash)
      child_hash.merge!(value)
    end
    attr.type.apply_hash_mapping(
      child_hash,
      attr.type.model.new,
      format,
      { mappings: attr.type.mappings_for(format).mappings },
    )
  end
end

def valid_rule?(rule)

def valid_rule?(rule)
  attribute = attribute_for_rule(rule)
  !!attribute || rule.custom_methods[:from]
end

def value_for_rule(doc, rule, options)

def value_for_rule(doc, rule, options)
  rule_names = rule.namespaced_names(options[:default_namespace])
  hash = rule.attribute? ? doc["attributes"] : doc["elements"]
  return unless hash
  value_key = rule_names.find { |name| hash.key_exist?(name) }
  hash.fetch(value_key) if value_key
end