module Lutaml::Model::Schema::XmlCompiler

def as_models(schema, options: {})

def as_models(schema, options: {})
  raise Error, XML_ADAPTER_NOT_SET_MESSAGE unless Config.xml_adapter.name.end_with?("NokogiriAdapter")
  parsed_schema = Xsd.parse(schema, location: options[:location])
  @elements = MappingHash.new
  @attributes = MappingHash.new
  @group_types = MappingHash.new
  @simple_types = MappingHash.new
  @complex_types = MappingHash.new
  @attribute_groups = MappingHash.new
  schema_to_models(Array(parsed_schema))
end

def create_file(name, content, dir)

def create_file(name, content, dir)
  File.write("#{dir}/#{Utils.snake_case(name)}.rb", content)
end

def create_mapping_hash(value, hash_key: :class_name)

def create_mapping_hash(value, hash_key: :class_name)
  MappingHash.new.tap do |hash|
    hash[hash_key] = value
  end
end

def element_arguments(element, element_hash)

def element_arguments(element, element_hash)
  MappingHash.new.tap do |hash|
    hash[:min_occurs] = element.min_occurs if element.min_occurs
    hash[:max_occurs] = element.max_occurs if element.max_occurs
    element_hash[:arguments] = hash if hash&.any?
  end
end

def require_classes(classes_hash)

def require_classes(classes_hash)
  Dir.mktmpdir do |dir|
    classes_hash.each do |name, klass|
      create_file(name, klass, dir)
      require "#{dir}/#{Utils.snake_case(name)}"
    end
  end
end

def required_files_attribute(attributes)

def required_files_attribute(attributes)
  attributes.each do |attribute|
    next if attribute[:ref_class]&.start_with?("xml") || attribute[:base_class]&.start_with?("xml")
    attribute = @attributes[attribute.ref_class.split(":").last] if attribute.key_exist?(:ref_class)
    attr_class = attribute.base_class.split(":")&.last
    next if DEFAULT_CLASSES.include?(attr_class)
    @required_files << Utils.snake_case(attr_class)
  end
end

def required_files_attribute_groups(attr_groups)

def required_files_attribute_groups(attr_groups)
  attr_groups.each do |key, value|
    case key
    when :ref_class
      required_files_attribute_groups(@attribute_groups[value.split(":").last])
    when :attribute, :attributes
      required_files_attribute(value)
    end
  end
end

def required_files_choice(choice)

def required_files_choice(choice)
  choice.each do |key, value|
    case key
    when String
      value = @elements[value.ref_class.split(":").last] if value.key?(:ref_class)
      @required_files << Utils.snake_case(value.type_name.split(":").last)
    when :element
      required_files_elements(value)
    when :group
      required_files_group(value)
    when :choice
      required_files_choice(value)
    when :sequence
      required_files_sequence(value)
    end
  end
end

def required_files_complex_content(complex_content)

def required_files_complex_content(complex_content)
  complex_content.each do |key, value|
    case key
    when :extension
      required_files_extension(value)
    when :restriction
      required_files_restriction(value)
    end
  end
end

def required_files_elements(elements)

def required_files_elements(elements)
  elements.each do |element|
    element = @elements[element.ref_class.split(":").last] if element.key_exist?(:ref_class)
    element_class = element.type_name.split(":").last
    next if DEFAULT_CLASSES.include?(element_class)
    @required_files << Utils.snake_case(element_class)
  end
end

def required_files_extension(extension)

def required_files_extension(extension)
  extension.each do |key, value|
    case key
    when :attribute_group
      required_files_attribute_groups(value)
    when :attribute, :attributes
      required_files_attribute(value)
    when :extension_base
      # Do nothing.
    when :sequence
      required_files_sequence(value)
    when :choice
      required_files_choice(value)
    end
  end
end

def required_files_group(group)

def required_files_group(group)
  group.each do |key, value|
    case key
    when :ref_class
      required_files_group(@group_types[value.split(":").last])
    when :choice
      required_files_choice(value)
    when :sequence
      required_files_sequence(value)
    end
  end
end

def required_files_restriction(restriction)

def required_files_restriction(restriction)
  restriction.each do |key, value|
    case key
    when :base
      @required_files << Utils.snake_case(value.split(":").last)
    end
  end
end

def required_files_sequence(sequence)

def required_files_sequence(sequence)
  sequence.each do |key, value|
    case key
    when :elements
      required_files_elements(value)
    when :sequence
      required_files_sequence(value)
    when :groups
      value.each { |group| required_files_group(group) }
    when :choice
      value.each { |choice| required_files_choice(choice) }
    end
  end
end

def required_files_simple_content(simple_content)

START: REQUIRED FILES LIST COMPILER METHODS
def required_files_simple_content(simple_content)
  simple_content.each do |key, value|
    case key
    when :extension_base
      # Do nothing.
    when :attributes
      required_files_attribute(value)
    when :extension
      required_files_extension(value)
    when :restriction
      required_files_restriction(value)
    end
  end
end

def resolve_attribute_class(attribute)

def resolve_attribute_class(attribute)
  attr_class = attribute.base_class.split(":")&.last
  case attr_class
  when *DEFAULT_CLASSES
    ":#{attr_class}"
  else
    Utils.camel_case(attr_class)
  end
end

def resolve_attribute_default(attribute)

def resolve_attribute_default(attribute)
  klass = attribute.base_class.split(":").last
  default = attribute[:default]
  ", default: #{resolve_attribute_default_value(klass, default)}"
end

def resolve_attribute_default_value(klass, default)

def resolve_attribute_default_value(klass, default)
  return default.inspect unless DEFAULT_CLASSES.include?(klass)
  klass = "integer" if klass == "int"
  type_klass = Lutaml::Model::Type.const_get(klass.capitalize)
  type_klass.cast(default)
end

def resolve_choice(choice, hash = MappingHash.new)

def resolve_choice(choice, hash = MappingHash.new)
  choice.each do |key, value|
    case key
    when :element
      [resolve_elements(value, hash)]
    when :group
      resolve_group(value, hash)
    when String
      hash[key] = value
    when :sequence
      resolve_sequence(value, hash)
    end
  end
  hash
end

def resolve_complex_content(complex_content, hash = MappingHash.new)

def resolve_complex_content(complex_content, hash = MappingHash.new)
  complex_content.each do |key, value|
    case key
    when :extension
      resolve_extension(value, hash)
    when :restriction
      # TODO: No implementation yet!
      hash
    end
  end
  hash
end

def resolve_content(content, hash = MappingHash.new)

def resolve_content(content, hash = MappingHash.new)
  content.each do |key, value|
    case key
    when :sequence
      resolve_sequence(value, hash)
    when :choice
      resolve_choice(value, hash)
    when :group
      resolve_group(value, hash)
    end
  end
  hash
end

def resolve_element_class(element)

def resolve_element_class(element)
  element_class = element.type_name.split(":").last
  case element_class
  when *DEFAULT_CLASSES
    ":#{element_class}"
  else
    Utils.camel_case(element_class)
  end
end

def resolve_elements(elements, hash = MappingHash.new)

def resolve_elements(elements, hash = MappingHash.new)
  elements.each do |element|
    if element.key?(:ref_class)
      new_element = @elements[element.ref_class.split(":").last]
      hash[new_element.element_name] = new_element
    else
      hash[element.element_name] = element
    end
  end
  hash
end

def resolve_extension(extension, hash = MappingHash.new)

def resolve_extension(extension, hash = MappingHash.new)
  hash[:attributes] = extension.attributes if extension.key?(:attributes)
  resolve_sequence(extension.sequence, hash) if extension.key?(:sequence)
  resolve_choice(extension.choice, hash) if extension.key?(:choice)
  hash
end

def resolve_group(group, hash = MappingHash.new)

def resolve_group(group, hash = MappingHash.new)
  group.each do |key, value|
    case key
    when :ref_class
      resolve_group(@group_types[value.split(":").last], hash)
    when :choice
      resolve_choice(value, hash)
    when :group
      resolve_group(value, hash)
    when :sequence
      resolve_sequence(value, hash)
    end
  end
  hash
end

def resolve_namespace(options)

def resolve_namespace(options)
  namespace_str = "namespace \"#{options[:namespace]}\"" if options.key?(:namespace)
  namespace_str += ", \"#{options[:prefix]}\"" if options.key?(:prefix) && options.key?(:namespace)
  namespace_str += "\n" if namespace_str
  namespace_str
end

def resolve_occurs(arguments)

def resolve_occurs(arguments)
  min_occurs = arguments[:min_occurs]
  max_occurs = arguments[:max_occurs]
  max_occurs = max_occurs.to_s&.match?(/[A-Za-z]+/) ? nil : max_occurs.to_i if max_occurs
  ", collection: #{max_occurs ? min_occurs.to_i..max_occurs : true}"
end

def resolve_parent_class(content)

START: TEMPLATE RESOLVER METHODS
def resolve_parent_class(content)
  return "Lutaml::Model::Serializable" unless content.dig(:complex_content, :extension)
  Utils.camel_case(content.dig(:complex_content, :extension, :extension_base))
end

def resolve_required_files(content)

def resolve_required_files(content)
  @required_files = []
  content.each do |key, value|
    case key
    when :sequence
      required_files_sequence(value)
    when :choice
      required_files_choice(value)
    when :group
      required_files_group(value)
    when :attributes
      required_files_attribute(value)
    when :attribute_groups
      value.each do |attribute_group|
        required_files_attribute_groups(attribute_group)
      end
    when :complex_content
      required_files_complex_content(value)
    when :simple_content
      required_files_simple_content(value)
    end
  end
  @required_files.uniq.sort_by(&:length)
end

def resolve_sequence(sequence, hash = MappingHash.new)

def resolve_sequence(sequence, hash = MappingHash.new)
  sequence.each do |key, value|
    case key
    when :sequence
      resolve_sequence(value, hash)
    when :elements
      resolve_elements(value, hash)
    when :groups
      value.each { |group| resolve_group(group, hash) }
    when :choice
      value.each { |choice| resolve_choice(choice, hash) }
    end
  end
  hash
end

def resolved_element_order(object)

def resolved_element_order(object)
  return [] if object.element_order.nil?
  object.element_order.each_with_object(object.element_order.dup) do |builder_instance, array|
    next array.delete(builder_instance) if builder_instance.text?
    next array.delete(builder_instance) if ELEMENT_ORDER_IGNORABLE.include?(builder_instance.name)
    index = 0
    array.each_with_index do |element, i|
      next unless element == builder_instance
      array[i] = Array(object.send(Utils.snake_case(builder_instance.name)))[index]
      index += 1
    end
  end
end

def restriction_content(hash, restriction)

def restriction_content(hash, restriction)
  return hash unless restriction.respond_to?(:max_length)
  hash[:max_length] = restriction.max_length.map(&:value).min if restriction.max_length&.any?
  hash[:min_length] = restriction.min_length.map(&:value).max if restriction.min_length&.any?
  hash[:min_inclusive] = restriction.min_inclusive.map(&:value).max if restriction.min_inclusive&.any?
  hash[:max_inclusive] = restriction.max_inclusive.map(&:value).min if restriction.max_inclusive&.any?
  hash[:length] = restriction_length(restriction.length) if restriction.length&.any?
end

def restriction_length(lengths)

def restriction_length(lengths)
  lengths.map do |length|
    MappingHash.new.tap do |hash|
      hash[:value] = length.value
      hash[:fixed] = length.fixed if length.fixed
    end
  end
end

def restriction_patterns(patterns, hash)

def restriction_patterns(patterns, hash)
  return if Utils.blank?(patterns)
  hash[:pattern] = patterns.map { |p| "(#{p.value})" }.join("|")
  hash
end

def schema_to_models(schemas)

def schema_to_models(schemas)
  return if schemas.empty?
  schemas.each do |schema|
    schema_to_models(schema.include) if schema.include&.any?
    schema_to_models(schema.import) if schema.import&.any?
    resolved_element_order(schema).each do |order_item|
      item_name = order_item&.name
      case order_item
      when Xsd::SimpleType
        @simple_types[item_name] = setup_simple_type(order_item)
      when Xsd::Group
        @group_types[item_name] = setup_group_type(order_item)
      when Xsd::ComplexType
        @complex_types[item_name] = setup_complex_type(order_item)
      when Xsd::Element
        @elements[item_name] = setup_element(order_item)
      when Xsd::Attribute
        @attributes[item_name] = setup_attribute(order_item)
      when Xsd::AttributeGroup
        @attribute_groups[item_name] = setup_attribute_groups(order_item)
      end
    end
  end
  nil
end

def setup_attribute(attribute)

def setup_attribute(attribute)
  MappingHash.new.tap do |attr_hash|
    if attribute.ref
      attr_hash[:ref_class] = attribute.ref
    else
      attr_hash[:name] = attribute.name
      attr_hash[:base_class] = attribute.type
      attr_hash[:default] = attribute.default if attribute.default
    end
  end
end

def setup_attribute_groups(attribute_group)

def setup_attribute_groups(attribute_group)
  MappingHash.new.tap do |hash|
    if attribute_group.ref
      hash[:ref_class] = attribute_group.ref
    else
      hash[:attributes] = [] if attribute_group.attribute&.any?
      hash[:attribute_groups] = [] if attribute_group.attribute_group&.any?
      resolved_element_order(attribute_group).each do |instance|
        case instance
        when Xsd::Attribute
          hash[:attributes] << setup_attribute(instance)
        when Xsd::AttributeGroup
          hash[:attribute_groups] << setup_attribute_groups(instance)
        end
      end
    end
  end
end

def setup_choice(choice)

def setup_choice(choice)
  MappingHash.new.tap do |hash|
    resolved_element_order(choice).each do |element|
      case element
      when Xsd::Element
        element_name = element.name || @elements[element.ref.split(":").last]&.element_name
        hash[element_name] = setup_element(element)
      when Xsd::Sequence
        hash[:sequence] = setup_sequence(element)
      when Xsd::Group
        hash[:group] = setup_group_type(element)
      when Xsd::Choice
        hash[:choice] = setup_choice(element)
      end
    end
  end
end

def setup_complex_content(complex_content)

def setup_complex_content(complex_content)
  MappingHash.new.tap do |hash|
    hash[:mixed] = true if complex_content.mixed
    if complex_content.extension
      hash[:extension] = setup_extension(complex_content.extension)
    elsif restriction = complex_content.restriction
      setup_restriction(restriction, hash)
    end
  end
end

def setup_complex_type(complex_type)

def setup_complex_type(complex_type)
  MappingHash.new.tap do |hash|
    hash[:attributes] = [] if complex_type.attribute&.any?
    hash[:attribute_groups] = [] if complex_type.attribute_group&.any?
    hash[:mixed] = complex_type.mixed
    resolved_element_order(complex_type).each do |element|
      case element
      when Xsd::Attribute
        hash[:attributes] << setup_attribute(element)
      when Xsd::Sequence
        hash[:sequence] = setup_sequence(element)
      when Xsd::Choice
        hash[:choice] = setup_choice(element)
      when Xsd::ComplexContent
        hash[:complex_content] = setup_complex_content(element)
      when Xsd::AttributeGroup
        hash[:attribute_groups] << setup_attribute_groups(element)
      when Xsd::Group
        hash[:group] = setup_group_type(element)
      when Xsd::SimpleContent
        hash[:simple_content] = setup_simple_content(element)
      end
    end
  end
end

def setup_element(element)

def setup_element(element)
  MappingHash.new.tap do |hash|
    if element.ref
      hash[:ref_class] = element.ref
    else
      hash[:type_name] = element.type
      hash[:element_name] = element.name
      element_arguments(element, hash)
      return hash unless complex_type = element.complex_type
      hash[:complex_type] = setup_complex_type(complex_type)
      @complex_types[complex_type.name] = hash[:complex_type]
    end
  end
end

def setup_extension(extension)

def setup_extension(extension)
  MappingHash.new.tap do |hash|
    hash[:extension_base] = extension.base
    hash[:attribute_groups] = [] if extension&.attribute_group&.any?
    hash[:attributes] = [] if extension&.attribute&.any?
    resolved_element_order(extension).each do |element|
      case element
      when Xsd::Attribute
        hash[:attributes] << setup_attribute(element)
      when Xsd::Sequence
        hash[:sequence] = setup_sequence(element)
      when Xsd::Choice
        hash[:choice] = setup_choice(element)
      end
    end
  end
end

def setup_group_type(group)

def setup_group_type(group)
  MappingHash.new.tap do |hash|
    if group.ref
      hash[:ref_class] = group.ref
    else
      resolved_element_order(group).map do |instance|
        case instance
        when Xsd::Sequence
          hash[:sequence] = setup_sequence(instance)
        when Xsd::Choice
          hash[:choice] = setup_choice(instance)
        end
      end
    end
  end
end

def setup_restriction(restriction, hash)

def setup_restriction(restriction, hash)
  hash[:base_class] = restriction.base
  restriction_patterns(restriction.pattern, hash) if restriction.respond_to?(:pattern)
  restriction_content(hash, restriction)
  return hash unless restriction.respond_to?(:enumeration) && restriction.enumeration&.any?
  hash[:values] = restriction.enumeration.map(&:value)
  hash
end

def setup_sequence(sequence)

def setup_sequence(sequence)
  MappingHash.new.tap do |hash|
    hash[:sequences] = [] if sequence.sequence&.any?
    hash[:elements] = [] if sequence.element&.any?
    hash[:choice] = [] if sequence.choice&.any?
    hash[:groups] = [] if sequence.group&.any?
    resolved_element_order(sequence).each do |instance|
      case instance
      when Xsd::Sequence
        hash[:sequences] << setup_sequence(instance)
      when Xsd::Element
        hash[:elements] << if instance.name
          setup_element(instance)
        else
          create_mapping_hash(instance.ref, hash_key: :ref_class)
        end
      when Xsd::Choice
        hash[:choice] << setup_choice(instance)
      when Xsd::Group
        hash[:groups] << if instance.name
          setup_group_type(instance)
        else
          create_mapping_hash(instance.ref, hash_key: :ref_class)
        end
      when Xsd::Any
        # No implementation yet!
      end
    end
  end
end

def setup_simple_content(simple_content)

def setup_simple_content(simple_content)
  if simple_content.extension
    setup_extension(simple_content.extension)
  elsif simple_content.restriction
    setup_restriction(simple_content.restriction, {})
  end
end

def setup_simple_type(simple_type)

def setup_simple_type(simple_type)
  MappingHash.new.tap do |hash|
    setup_restriction(simple_type.restriction, hash) if simple_type&.restriction
    hash[:union] = setup_union(simple_type.union) if simple_type.union
  end
end

def setup_union(union)

def setup_union(union)
  union.member_types.split.map do |member_type|
    @simple_types[member_type]
  end.flatten
end

def to_models(schema, options = {})

def to_models(schema, options = {})
  as_models(schema, options: options)
  @data_types_classes = Templates::SimpleType.create_simple_types(@simple_types)
  if options[:create_files]
    dir = options.fetch(:output_dir, "lutaml_models_#{Time.now.to_i}")
    FileUtils.mkdir_p(dir)
    @data_types_classes.each do |name, content|
      create_file(name, content, dir)
    end
    @complex_types.each do |name, content|
      create_file(name, MODEL_TEMPLATE.result(binding), dir)
    end
    nil
  else
    simple_types = @data_types_classes.transform_keys do |key|
      Utils.camel_case(key.to_s)
    end
    complex_types = @complex_types.to_h do |name, content|
      [Utils.camel_case(name), MODEL_TEMPLATE.result(binding)]
    end
    classes_hash = simple_types.merge(complex_types)
    require_classes(classes_hash) if options[:load_classes]
    classes_hash
  end
end