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
                    instance_variable_get(:"@#{enum_name}") - [value]
                  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 = [] if value.nil?
    value = [value] if !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_mappings(doc, format, options = {})

def apply_mappings(doc, format, options = {})
  register = options[:register] || Lutaml::Model::Config.default_register
  instance = if options.key?(:instance)
               options[:instance]
             elsif model.include?(Lutaml::Model::Serialize)
               model.new({ __register: register })
             else
               object = model.new
               register_accessor_methods_for(object, register)
               object
             end
  return instance if Utils.blank?(doc)
  mappings = mappings_for(format)
  if mappings.polymorphic_mapping
    return resolve_polymorphic(doc, format, mappings, instance, options)
  end
  transformer = Lutaml::Model::Config.transformer_for(format)
  transformer.data_to_model(self, doc, format, options)
end

def apply_value_map(value, value_map, attr)

def apply_value_map(value, value_map, attr)
  if value.nil?
    value_for_option(value_map[:nil], attr)
  elsif Utils.empty?(value)
    value_for_option(value_map[:empty], attr, value)
  elsif Utils.uninitialized?(value)
    value_for_option(value_map[:omitted], attr)
  else
    value
  end
end

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

def as(format, instance, options = {})
  if instance.is_a?(Array)
    return instance.map { |item| public_send(:"as_#{format}", item) }
  end
  unless instance.is_a?(model)
    msg = "argument is a '#{instance.class}' but should be a '#{model}'"
    raise Lutaml::Model::IncorrectModelError, msg
  end
  transformer = Lutaml::Model::Config.transformer_for(format)
  transformer.model_to_data(self, instance, format, options)
end

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

Define an attribute for the model
def attribute(name, type, options = {})
  if type.is_a?(Hash)
    options[:method_name] = type[:method]
    type = nil
  end
  attr = Attribute.new(name, type, options)
  attributes[name] = attr
  define_attribute_methods(attr)
  attr
end

def attributes(register = nil)

def attributes(register = nil)
  ensure_imports!(register) if finalized?
  @attributes
end

def cast(value)

def cast(value)
  value
end

def choice(min: 1, max: 1, &block)

def choice(min: 1, max: 1, &block)
  @choice_attributes << Choice.new(self, min, max).tap do |c|
    c.instance_eval(&block)
  end
end

def default_mappings(format)

def default_mappings(format)
  klass = ::Lutaml::Model::Config.mappings_class_for(format)
  mappings = klass.new
  mappings.tap do |mapping|
    attributes&.each_key do |name|
      mapping.map_element(
        name.to_s,
        to: name,
      )
    end
    mapping.root(Utils.base_class_name(self)) if format == :xml
  end
end

def define_attribute_methods(attr)

def define_attribute_methods(attr)
  name = attr.name
  if attr.enum?
    add_enum_methods_to_model(
      model,
      name,
      attr.options[:values],
      collection: attr.options[:collection],
    )
  elsif attr.derived? && name != attr.method_name
    define_method(name) do
      public_send(attr.method_name)
    end
  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, __register))
    end
  end
end

def empty_object(attr)

def empty_object(attr)
  return attr.build_collection if attr.collection?
  ""
end

def ensure_choice_imports!(register_id = nil)

def ensure_choice_imports!(register_id = nil)
  return if @choices_imported
  register_id ||= Lutaml::Model::Config.default_register
  register = Lutaml::Model::GlobalRegister.lookup(register_id)
  importable_choices.each do |choice, choice_imports|
    choice_imports.each do |method, models|
      models.uniq!
      choice.public_send(method, register.get_class_without_register(models.shift)) until models.empty?
    end
  end
  @choices_imported = true
end

def ensure_imports!(register = nil)

def ensure_imports!(register = nil)
  ensure_model_imports!(register)
  ensure_choice_imports!(register)
end

def ensure_model_imports!(register_id = nil)

def ensure_model_imports!(register_id = nil)
  return if @models_imported
  register_id ||= Lutaml::Model::Config.default_register
  register = Lutaml::Model::GlobalRegister.lookup(register_id)
  importable_models.each do |method, models|
    models.uniq.each do |model|
      model_class = register.get_class_without_register(model)
      import_model_with_root_error(model_class)
      @model.public_send(method, model_class)
    end
  end
  @models_imported = true
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 extract_register_id(register)

def extract_register_id(register)
  if register
    register.is_a?(Lutaml::Model::Register) ? register.id : register
  elsif class_variable_defined?(:@@__register)
    class_variable_get(:@@__register)
  else
    Lutaml::Model::Config.default_register
  end
end

def finalized?

def finalized?
  @finalized
end

def from(format, data, options = {})

def from(format, data, options = {})
  adapter = Lutaml::Model::Config.adapter_for(format)
  doc = adapter.parse(data, options)
  send("of_#{format}", doc, options)
end

def handle_key_value_mappings(mapping, format)

def handle_key_value_mappings(mapping, format)
  @mappings[format] ||= KeyValueMapping.new
  @mappings[format].mappings_hash.merge!(mapping.mappings_hash)
end

def import_model(model)

def import_model(model)
  if model.is_a?(Symbol) || model.is_a?(String)
    importable_models[:import_model] << model.to_sym
    @models_imported = false
    @choices_imported = false
    setup_trace_point
    return
  end
  import_model_with_root_error(model)
  import_model_attributes(model)
  import_model_mappings(model)
end

def import_model_attributes(model)

def import_model_attributes(model)
  if model.is_a?(Symbol) || model.is_a?(String)
    importable_models[:import_model_attributes] << model.to_sym
    @models_imported = false
    @choices_imported = false
    setup_trace_point
    return
  end
  model.attributes.each_value do |attr|
    define_attribute_methods(attr)
  end
  @choice_attributes.concat(Utils.deep_dup(model.choice_attributes))
  @attributes.merge!(Utils.deep_dup(model.attributes))
end

def import_model_mappings(model)

def import_model_mappings(model)
  if model.is_a?(Symbol) || model.is_a?(String)
    importable_models[:import_model_mappings] << model.to_sym
    @models_imported = false
    setup_trace_point
    return
  end
  import_model_with_root_error(model)
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
    next unless model.mappings.key?(format)
    mapping = model.mappings_for(format)
    mapping = Utils.deep_dup(mapping)
    klass = ::Lutaml::Model::Config.mappings_class_for(format)
    @mappings[format] ||= klass.new
    if format == :xml
      @mappings[format].merge_mapping_attributes(mapping)
      @mappings[format].merge_mapping_elements(mapping)
      @mappings[format].merge_elements_sequence(mapping)
    else
      @mappings[format].mappings_hash.merge!(mapping.mappings_hash)
    end
  end
end

def import_model_with_root_error(model)

def import_model_with_root_error(model)
  return unless model.mappings.key?(:xml) && model.root?
  raise Lutaml::Model::ImportModelWithRootError.new(model)
end

def importable_choices

def importable_choices
  @importable_choices ||= MappingHash.new { |h, k| h[k] = MappingHash.new { |h1, k1| h1[k1] = [] } }
end

def importable_models

def importable_models
  @importable_models ||= MappingHash.new { |h, k| h[k] = [] }
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)) || {}
  @choice_attributes = Utils.deep_dup(source_class.instance_variable_get(:@choice_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(format)
    mappings[format].instance_eval(&block)
  end
end

def mappings_for(format)

def mappings_for(format)
  @mappings[:xml]&.ensure_mappings_imported! if @mappings&.dig(:xml)&.finalized?
  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 of(format, doc, options = {})

def of(format, doc, options = {})
  if doc.is_a?(Array) && format != :jsonl
    return doc.map { |item| send(:"of_#{format}", item) }
  end
  if format == :xml
    valid = root? || options[:from_collection]
    raise Lutaml::Model::NoRootMappingError.new(self) unless valid
    options[:encoding] = doc.encoding
  end
  options[:register] = extract_register_id(options[:register])
  transformer = Lutaml::Model::Config.transformer_for(format)
  transformer.data_to_model(self, doc, format, options)
end

def process_mapping(format, &block)

def process_mapping(format, &block)
  klass = ::Lutaml::Model::Config.mappings_class_for(format)
  mappings[format] ||= klass.new
  mappings[format].instance_eval(&block)
  if mappings[format].respond_to?(:finalize)
    mappings[format].finalize(self)
  end
end

def register(name)

def register(name)
  name&.to_sym
end

def register_accessor_methods_for(object, register)

def register_accessor_methods_for(object, register)
  Utils.add_singleton_method_if_not_defined(object, :__register) do
    @__register
  end
  Utils.add_singleton_method_if_not_defined(object, :__register=) do |value|
    @__register = value
  end
  object.__register = register
end

def resolve_polymorphic(doc, format, mappings, instance, options = {})

def resolve_polymorphic(doc, format, mappings, instance, options = {})
  polymorphic_mapping = mappings.polymorphic_mapping
  return instance if polymorphic_mapping.polymorphic_map.empty?
  klass_key = doc[polymorphic_mapping.name]
  klass_name = polymorphic_mapping.polymorphic_map[klass_key]
  klass = Object.const_get(klass_name)
  klass.apply_mappings(doc, format, options.merge(register: instance.__register))
end

def restrict(name, options = {})

def restrict(name, options = {})
  validate_attribute_options!(name, options)
  attr = attributes[name]
  attr.options.merge!(options)
  attr.process_options!
  name
end

def root?

def root?
  mappings_for(:xml)&.root?
end

def setup_trace_point

def setup_trace_point
  @trace ||= TracePoint.new(:end) do |_tp|
    if include?(Lutaml::Model::Serialize)
      @finalized = true
      @trace.disable
    end
  end
  @trace.enable unless @trace.enabled?
end

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

def to(format, instance, options = {})
  value = public_send(:"as_#{format}", instance, options)
  adapter = Lutaml::Model::Config.adapter_for(format)
  options[:mapper_class] = self if format == :xml
  adapter.new(value).public_send(:"to_#{format}", options)
end

def validate_attribute_options!(name, options)

def validate_attribute_options!(name, options)
  invalid_opts = options.keys - Attribute::ALLOWED_OPTIONS
  return if invalid_opts.empty?
  raise Lutaml::Model::InvalidAttributeOptionsError.new(name, invalid_opts)
end

def value_for_option(option, attr, empty_value = nil)

def value_for_option(option, attr, empty_value = nil)
  return nil if option == :nil
  return empty_value || empty_object(attr) if option == :empty
  Lutaml::Model::UninitializedClass.instance
end