# frozen_string_literal: true
# rubocop:todo all

module Mongoid
  module Association
    module Referenced

      # Mixin module included into Mongoid::Document which adds
      # the ability to automatically save opposite-side documents
      # in referenced associations when saving the subject document.
      module AutoSave
        extend ActiveSupport::Concern

        # Used to prevent infinite loops in associated autosaves.
        #
        # @example Is the document autosaved?
        #   document.autosaved?
        #
        # @return [ true | false ] Has the document already been autosaved?
        def autosaved?
          Threaded.autosaved?(self)
        end

        # Begin the associated autosave.
        #
        # @example Begin autosave.
        #   document.__autosaving__
        def __autosaving__
          Threaded.begin_autosave(self)
          yield
        ensure
          Threaded.exit_autosave(self)
        end

        # Check if there is changes for auto-saving
        #
        # @example Return true if there is changes on self or in
        #           autosaved associations.
        #   document.changed_for_autosave?
        def changed_for_autosave?(doc)
          doc.new_record? || doc.changed? || doc.marked_for_destruction?
        end

        # Define the autosave method on an association's owning class for
        # an associated object.
        #
        # @example Define the autosave method:
        #   Association::Referenced::Autosave.define_autosave!(association)
        #
        # @param [ Mongoid::Association::Relatable ] association The association for which autosaving is enabled.
        #
        # @return [ Class ] The association's owner class.
        def self.define_autosave!(association)
          association.inverse_class.tap do |klass|
            save_method = :"autosave_documents_for_#{association.name}"
            klass.send(:define_method, save_method) do
              if before_callback_halted?
                self.before_callback_halted = false
              else
                __autosaving__ do
                  if assoc_value = ivar(association.name)
                    Array(assoc_value).each do |doc|
                      pc = doc.persistence_context? ? doc.persistence_context : persistence_context.for_child(doc)
                      doc.with(pc) do |d|
                        d.save
                      end
                    end
                  end
                end
              end
            end
            klass.after_persist_parent save_method, unless: :autosaved?
          end
        end
      end
    end
  end
end
