# frozen-string-literal: true

module Sequel
  module Plugins
    # The insert_conflict plugin allows handling conflicts due to unique
    # constraints when saving new model instance, using the INSERT ON CONFLICT
    # support in PostgreSQL 9.5+ and SQLite 3.24.0+. Example:
    #
    #   class Album < Sequel::Model
    #     plugin :insert_conflict
    #   end
    #
    #   Album.new(name: 'Foo', copies_sold: 1000).
    #     insert_conflict(
    #       target: :name,
    #       update: {copies_sold: Sequel[:excluded][:b]}
    #     ).
    #     save
    #
    # This example will try to insert the album, but if there is an existing
    # album with the name 'Foo', this will update the copies_sold attribute
    # for that album.  See the PostgreSQL and SQLite adapter documention for
    # the options you can pass to the insert_conflict method.
    #
    # You should not attempt to use this plugin to ignore conflicts when
    # inserting, you should only use it to turn insert conflicts into updates.
    # Any usage to ignore conflicts is not recommended or supported.
    #
    # Usage:
    #
    #   # Make all model subclasses support insert_conflict
    #   Sequel::Model.plugin :insert_conflict
    #
    #   # Make the Album class support insert_conflict
    #   Album.plugin :insert_conflict
    module InsertConflict
      def self.configure(model)
        model.instance_exec do
          if @dataset && !@dataset.respond_to?(:insert_conflict)
            raise Error, "#{self}'s dataset does not support insert_conflict"
          end
        end
      end

      module InstanceMethods
        # Set the insert_conflict options to pass to the dataset when inserting. 
        def insert_conflict(opts=OPTS)
          raise Error, "Model#insert_conflict is only supported on new model instances" unless new?
          @insert_conflict_opts = opts
          self
        end

        private

        # Set the dataset used for inserting to use INSERT ON CONFLICT
        # Model#insert_conflict has been called on the instance previously.
        def _insert_dataset
          ds = super

          if @insert_conflict_opts
            ds = ds.insert_conflict(@insert_conflict_opts)
          end

          ds
        end

        # Disable the use of prepared insert statements, as they are not compatible
        # with this plugin.
        def use_prepared_statements_for?(type)
          return false if type == :insert || type == :insert_select
          super if defined?(super)
        end
      end
    end
  end
end
