module ArVirtualField

def virtual_field(name, scope: nil, select:, get:, default: nil)

def virtual_field(name, scope: nil, select:, get:, default: nil)
  @ar_virtual_fields ||= FieldsData.new
  name = name.to_s
  current_class = self
  if default
    default = HelperMethods.wrap_to_lambda(default)
  end
  select_lambda = HelperMethods.wrap_to_lambda(select)
  if scope
    field_name = Arel.sql(HelperMethods.table_with_column(name))
    @ar_virtual_fields[name.to_sym] =
      if default
        -> { Arel::Nodes::NamedFunction.new('COALESCE', [field_name, default.()]) }
      else
        -> { field_name }
      end
    scope_name = :"_scope_#{name}"
    scope(scope_name, scope)
    scope(:"with_#{name}", -> do
      scope_query = current_class
        .send(scope_name)
        .select(select_lambda.().as(name), "#{table_name}.id")
      HelperMethods.select_append(joins(<<~SQL.squish), "#{HelperMethods.table_with_column(name)} AS #{name}")
        LEFT JOIN (#{scope_query.to_sql}) #{HelperMethods.table_name(name)}
          ON #{
            Array(primary_key).map do |pk|
              "#{HelperMethods.table_name(name)}.#{pk} = #{table_name}.#{pk}"
            end.join(' AND')
          }
      SQL
    end)
  else
    if default
      select_lambda = -> { Arel::Nodes::NamedFunction.new('COALESCE', [select_lambda.(), default.()]) }
    end
    @ar_virtual_fields[name.to_sym] = -> { select_lambda.() }
    scope(:"with_#{name}", -> do
      HelperMethods.select_append(self, select_lambda.().as(name))
    end)
  end
  method_name = :"ar_virtual_field_#{name}"
  define_method(method_name, &get)
  define_method(name) do
    if ActiveRecord::Base.connection.query_cache_enabled
      attributes.key?(name) ? self[name] : send(method_name)
    else
      send(method_name)
    end
  end
end

def virtual_fields

def virtual_fields
  @ar_virtual_fields || FieldsData.new
end