module Quizard
  module Repository
    module Ordered
      def self.included(base)
        base.extend ClassMethods
      end

      module ClassMethods
        def ordered_scopes(*attrs)
          return @_ordered_scopes ||= Set.new unless attrs.any?

          ordered_scopes.merge attrs
        end

        def create(entity)
          entity.order = next_order(entity)

          super(entity)
        end

        def update(entity)
          reorder(entity)
          super(entity)
        end

        def ordered
          query.order(:order)
        end

        def next_order(entity)
          scopes = collect_ordered_scopes(entity)
          sql = <<-sql
            SELECT MAX(`order`) + 1 as next
            FROM #{collection}
            WHERE #{scopes}
          sql

          fetch(sql).first[:next] || 1
        end

        def reorder(entity)
          changed = entity.changed_attributes

          if changed.key?(:order)
            new_order = entity.order
            old_order = changed[:order]
            scopes = collect_ordered_scopes(entity)

            if old_order < new_order
              sql = <<-sql
                UPDATE #{collection}
                  SET `order` = `order` - 1
                WHERE
                  #{scopes} AND `order` > #{old_order} AND `order` <= #{new_order}
              sql

              execute(sql)
            else
              sql = <<-sql
                UPDATE #{collection}
                  SET `order` = `order` + 1
                WHERE
                  #{scopes} AND `order` < #{old_order} AND `order` >= #{new_order}
              sql

              execute(sql)
            end
          end
        end

        def collect_ordered_scopes(entity)
          ordered_scopes
            .to_a
            .collect { |k| "#{k} = #{entity.send(k)}" }
            .join(' AND ')
        end
      end
    end
  end
end
