# frozen_string_literal: true

# The preference_cache_key is used to determine if the preference
# can be set. The default behavior is to return nil if there is no
# id value. On ActiveRecords, new objects will have their preferences
# saved to a pending hash until it is persisted.
#
# class_attributes are inheritied unless you reassign them in
# the subclass, so when you inherit a Preferable class, the
# inherited hook will assign a new hash for the subclass definitions
# and copy all the definitions allowing the subclass to add
# additional defintions without affecting the base

module Spree
  module Preferences
    module Preferable
      def self.included(base)
        base.class_eval do
          extend Spree::Preferences::PreferableClassMethods

          # Disabling rubocop rule because the fix to this rubocop warning breaks specs
          # rubocop:disable Style/SymbolProc
          if respond_to?(:after_create)
            after_create do |obj|
              obj.save_pending_preferences
            end
          end

          if respond_to?(:after_destroy)
            after_destroy do |obj|
              obj.clear_preferences
            end
          end
          # rubocop:enable Style/SymbolProc
        end
      end

      def get_preference(name)
        has_preference! name
        __send__ self.class.preference_getter_method(name)
      end
      alias :preferred :get_preference
      alias :prefers? :get_preference

      def set_preference(name, value)
        has_preference! name
        __send__ self.class.preference_setter_method(name), value
      end

      def preference_type(name)
        has_preference! name
        __send__ self.class.preference_type_getter_method(name)
      end

      def preference_default(name)
        has_preference! name
        __send__ self.class.preference_default_getter_method(name)
      end

      def preference_description(name)
        has_preference! name
        __send__ self.class.preference_description_getter_method(name)
      end

      def has_preference!(name)
        raise NoMethodError, "#{name} preference not defined" unless has_preference? name
      end

      def has_preference?(name)
        respond_to? self.class.preference_getter_method(name)
      end

      def preferences
        prefs = {}
        methods.grep(/^prefers_.*\?$/).each do |pref_method|
          prefs[pref_method.to_s.gsub(/prefers_|\?/, '').to_sym] = __send__(pref_method)
        end
        prefs
      end

      def preference_cache_key(name)
        return unless id

        [ENV.fetch("RAILS_CACHE_ID", nil), self.class.name, name, id].join('::').underscore
      end

      def save_pending_preferences
        return unless @pending_preferences

        @pending_preferences.each do |name, value|
          set_preference(name, value)
        end
      end

      def clear_preferences
        preferences.each_key { |pref| preference_store.delete preference_cache_key(pref) }
      end

      private

      def add_pending_preference(name, value)
        @pending_preferences ||= {}
        @pending_preferences[name] = value
      end

      def get_pending_preference(name)
        return unless @pending_preferences

        @pending_preferences[name]
      end

      def convert_preference_value(value, type)
        case type
        when :string, :text, :password
          value.to_s
        when :decimal
          value = 0 if value.blank?
          BigDecimal(value.to_s, exception: false)&.round(2, BigDecimal::ROUND_HALF_UP) || value
        when :integer
          value.to_i
        when :boolean
          if value.is_a?(FalseClass) ||
             value.nil? ||
             value == 0 ||
             (value.is_a?(String) && value =~ /^(f|false|0)$/i) ||
             (value.respond_to?(:empty?) && value.empty?)
            false
          else
            true
          end
        else
          value
        end
      end

      def preference_store
        Spree::Preferences::Store.instance
      end
    end
  end
end
