module Journaled::Changes
  extend ActiveSupport::Concern

  included do
    cattr_accessor :_journaled_change_definitions
    cattr_accessor :journaled_attribute_names
    self._journaled_change_definitions = []
    self.journaled_attribute_names = []

    after_create do
      self.class._journaled_change_definitions.each do |definition|
        Journaled::ChangeWriter.new(model: self, change_definition: definition).create
      end
    end

    after_save unless: :saved_change_to_id? do
      self.class._journaled_change_definitions.each do |definition|
        Journaled::ChangeWriter.new(model: self, change_definition: definition).update
      end
    end

    after_destroy do
      self.class._journaled_change_definitions.each do |definition|
        Journaled::ChangeWriter.new(model: self, change_definition: definition).delete
      end
    end
  end

  def delete(force: false)
    if force || self.class.journaled_attribute_names.empty?
      super()
    else
      raise(<<~ERROR)
        #delete aborted by Journaled::Changes.

        Invoke #delete(force: true) to override and skip journaling.
      ERROR
    end
  end

  def update_columns(attributes, force: false)
    unless force || self.class.journaled_attribute_names.empty?
      conflicting_journaled_attribute_names = self.class.journaled_attribute_names & attributes.keys.map(&:to_sym)
      raise(<<~ERROR) if conflicting_journaled_attribute_names.present?
        #update_columns aborted by Journaled::Changes due to journaled attributes:

          #{conflicting_journaled_attribute_names.join(', ')}

        Invoke #update_columns with additional arg `{ force: true }` to override and skip journaling.
      ERROR
    end
    super(attributes)
  end

  class_methods do
    def journal_changes_to(*attribute_names, as:) # rubocop:disable Naming/UncommunicativeMethodParamName
      if attribute_names.empty? || attribute_names.any? { |n| !n.is_a?(Symbol) }
        raise "one or more symbol attribute_name arguments is required"
      end

      raise "as: must be a symbol" unless as.is_a?(Symbol)

      _journaled_change_definitions << Journaled::ChangeDefinition.new(attribute_names: attribute_names, logical_operation: as)
      journaled_attribute_names.concat(attribute_names)
    end

    def delete(id_or_array, force: false)
      if force || journaled_attribute_names.empty?
        where(primary_key => id_or_array).delete_all(force: true)
      else
        raise(<<~ERROR)
          #delete aborted by Journaled::Changes.

          Invoke #delete(id_or_array, force: true) to override and skip journaling.
        ERROR
      end
    end
  end
end