# frozen_string_literal: true

# Make sure the custom attributes are defined
require "decidim/attributes"

module Decidim
  module AttributeObject
    # This resolves the ActiveModel::Attributes types from the Decidim's own
    # type definitions format inspired by Virtus.
    class TypeResolver
      def resolve(type, **options)
        case type
        when Symbol, ActiveModel::Type::Value
          {
            type:,
            options:
          }
        when Class
          resolve_class(type, **options)
        when Hash
          resolve_hash(type, **options)
        when Array
          resolve_array(type, **options)
        else
          resolve_default(type, **options)
        end
      end

      def exists?(type)
        ActiveModel::Type.registry.send(:registrations).has_key?(type)
      end

      private

      def resolve_class(type, **options)
        if type.include?(Decidim::AttributeObject::Model) || type <= ActiveRecord::Base
          {
            type: :model,
            options: options.merge(primitive: type)
          }
        elsif type == Hash
          resolve_hash({ Symbol => Object }, **options)
        elsif type == Array
          resolve_array(Array[Object], **options)
        else
          resolve_default(type, **options)
        end
      end

      def resolve_hash(type, **options)
        key_type = type.keys.first || Symbol
        val_type = type.values.first || Object
        options[:default] ||= {}

        {
          type: :hash,
          options: options.merge(key_type:, value_type: val_type)
        }
      end

      def resolve_array(type, **options)
        options[:default] ||= []

        {
          type: :array,
          options: options.merge(value_type: type.first)
        }
      end

      def resolve_default(type, **options)
        type_symbol = type.to_s.underscore.to_sym

        if exists?(type_symbol)
          # Off the bat, this handles the basic types from:
          # https://github.com/rails/rails/tree/main/activemodel/lib/active_model/type
          {
            type: type_symbol,
            options:
          }
        else
          {
            type: :object,
            options: options.merge(primitive: type)
          }
        end
      end
    end
  end
end
