# frozen_string_literal: true
# rubocop:todo all

module Mongoid
  module Clients

    # Mixin module included into Mongoid::Document which gives
    # the ability to manage the database context for persistence
    # and query operations. For example, this includes saving
    # documents to different collections, and reading documents
    # from secondary instances.
    module Options
      extend ActiveSupport::Concern

      # Change the persistence context for this object during the block.
      #
      # @example Save the current document to a different collection.
      #   model.with(collection: "bands") do |m|
      #     m.save
      #   end
      #
      # @param [ Hash | Mongoid::PersistenceContext ] options_or_context
      #   The storage options or a persistence context.
      #
      # @option options [ String | Symbol ] :collection The collection name.
      # @option options [ String | Symbol ] :database The database name.
      # @option options [ String | Symbol ] :client The client name.
      def with(options_or_context, &block)
        original_context = PersistenceContext.get(self)
        original_cluster = persistence_context.cluster
        set_persistence_context(options_or_context)
        yield self
      ensure
        clear_persistence_context(original_cluster, original_context)
      end

      # Get the collection for the document's current persistence context.
      #
      # @example Get the collection for the current persistence context.
      #   document.collection
      #
      # @param [ Object ] parent The parent object whose collection name is used
      #   instead of the current persistence context's collection name.
      #
      # @return [ Mongo::Collection ] The collection for the current persistence
      #   context.
      def collection(parent = nil)
        persistence_context.collection(parent)
      end

      # Get the collection name for the document's current persistence context.
      #
      # @example Get the collection name for the current persistence context.
      #   document.collection_name
      #
      # @return [ String ] The collection name for the current persistence
      #   context.
      def collection_name
        persistence_context.collection_name
      end

      # Get the database client for the document's current persistence context.
      #
      # @example Get the client for the current persistence context.
      #   document.mongo_client
      #
      # @return [ Mongo::Client ] The client for the current persistence
      #   context.
      def mongo_client
        persistence_context.client
      end

      # Get the document's current persistence context.
      #
      # @note For embedded documents, the persistence context of the
      #   root parent document is returned.
      #
      # @example Get the current persistence context.
      #   document.persistence_context
      #
      # @return [ Mongoid::PersistenceContext ] The current persistence
      #   context.
      def persistence_context
        if embedded? && !_root?
          _root.persistence_context
        else
          PersistenceContext.get(self) ||
            PersistenceContext.get(self.class) ||
            PersistenceContext.new(self.class, default_storage_options)
        end
      end

      # Returns whether a persistence context is set for the document
      # or the document's class.
      #
      # @note For embedded documents, the persistence context of the
      #   root parent document is used.
      #
      # @example Get the current persistence context.
      #   document.persistence_context?
      #
      # @return [ true | false ] Whether a persistence context is set.
      def persistence_context?
        if embedded? && !_root?
          _root.persistence_context?
        else
          remembered_storage_options&.any? ||
            PersistenceContext.get(self).present? ||
            PersistenceContext.get(self.class).present?
        end
      end

      private

      def default_storage_options
        # Nothing is overridden, we use either the default storage_options
        # or storage_options defined for the document class.
        return storage_options if Threaded.client_override.nil? && Threaded.database_override.nil?

        storage_options.tap do |opts|
          # Globally overridden client replaces client defined for the document class.
          opts[:client] = Threaded.client_override unless Threaded.client_override.nil?
          # Globally overridden database replaces database defined for the document class.
          opts[:database] = Threaded.database_override unless Threaded.database_override.nil?
        end
      end

      def set_persistence_context(options_or_context)
        PersistenceContext.set(self, options_or_context)
      end

      def clear_persistence_context(original_cluster = nil, context = nil)
        PersistenceContext.clear(self, original_cluster, context)
      end

      module ClassMethods

        # Get the database client name for the current persistence context
        # of the document class.
        #
        # @example Get the client name for the current persistence context.
        #   Model.client_name
        #
        # @return [ String ] The database client name for the current
        #   persistence context.
        def client_name
          persistence_context.client_name
        end

        # Get the collection name for the current persistence context of the
        # document class.
        #
        # @example Get the collection name for the current persistence context.
        #   Model.collection_name
        #
        # @return [ String ] The collection name for the current persistence
        #   context.
        def collection_name
          persistence_context.collection_name
        end

        # Get the database name for the current persistence context of the
        # document class.
        #
        # @example Get the database name for the current persistence context.
        #   Model.database_name
        #
        # @return [ String ] The database name for the current persistence
        #   context.
        def database_name
          persistence_context.database_name
        end

        # Get the collection for the current persistence context of the
        # document class.
        #
        # @example Get the collection for the current persistence context.
        #   Model.collection
        #
        # @return [ Mongo::Collection ] The collection for the current
        #   persistence context.
        def collection
          persistence_context.collection
        end

        # Get the client for the current persistence context of the
        # document class.
        #
        # @example Get the client for the current persistence context.
        #   Model.mongo_client
        #
        # @return [ Mongo::Client ] The client for the current persistence
        #   context.
        def mongo_client
          persistence_context.client
        end

        # Change the persistence context for this class during the block.
        #
        # @example Save the current document to a different collection.
        #   Model.with(collection: "bands") do |m|
        #     m.create
        #   end
        #
        # @param [ Hash ] options The storage options.
        #
        # @option options [ String | Symbol ] :collection The collection name.
        # @option options [ String | Symbol ] :database The database name.
        # @option options [ String | Symbol ] :client The client name.
        def with(options, &block)
          original_context = PersistenceContext.get(self)
          original_cluster = persistence_context.cluster
          PersistenceContext.set(self, options)
          yield self
        ensure
          PersistenceContext.clear(self, original_cluster, original_context)
        end

        # Get the current persistence context of the document class.
        # If a persistence context is not set, a new one will be
        # initialized and returned.
        #
        # @example Get the current persistence context.
        #   Model.persistence_context
        #
        # @return [ Mongoid::PersistenceContent ] The current persistence
        #   context.
        def persistence_context
          PersistenceContext.get(self) || PersistenceContext.new(self)
        end
      end
    end
  end
end
