#
# Adds Filterrific methods ActionController instances
#
module Filterrific
  module ActionControllerExtension
    include HasResetFilterrificUrlMixin

    protected

    # @param model_class [Class]
    # @param filterrific_params [ActionController::Params, Hash] typically the
    #    Rails request params under the :filterrific key (params[:filterrific]),
    #    however can be any Hash.
    # @param opts [Hash, optional]
    # @option opts [Array<String>, optional] :available_filters
    #   further restrict which of the filters specified in the model are
    #   available in this context.
    # @option opts [Hash, optional] :default_filter_params
    #   overrides the defaults specified in the model.
    # @option opts [String, Symbol, optional] :persistence_id
    #   defaults to "namespace/controller#action" string, used for session key
    #   and saved searches to isolate different filters' persisted params from
    #   each other. Set to false to turn off session persistence.
    # @option opts [Hash, optional] :select_options
    #   these are available in the view to populate select lists and other
    #   dynamic values.
    # @option opts [Boolean, optional] :sanitize_params
    #   if true, sanitizes all filterrific params to prevent reflected (or stored) XSS attacks.
    #   Defaults to true.
    # @return [Filterrific::ParamSet]
    def initialize_filterrific(model_class, filterrific_params, opts = {})
      f_params = (filterrific_params || {}).stringify_keys
      opts = opts.stringify_keys
      pers_id = if opts["persistence_id"] == false
        nil
      else
        opts["persistence_id"] || compute_default_persistence_id
      end

      if f_params.delete("reset_filterrific")
        # Reset query and session_persisted params
        session[pers_id] = nil if pers_id
        redirect_to url_for({}) and return false # requires `or return` in calling action.
      end

      f_params = compute_filterrific_params(model_class, f_params, opts, pers_id)

      filterrific = Filterrific::ParamSet.new(model_class, f_params)
      filterrific.select_options = opts["select_options"]
      session[pers_id] = filterrific.to_hash if pers_id
      filterrific
    end

    # Computes a default persistence id based on controller and action name
    def compute_default_persistence_id
      [controller_name, action_name].join("#")
    end

    # Computes filterrific params using a number of strategies. Limits params
    # to 'available_filters' if given via opts.
    # @param model_class [ActiveRecord::Base]
    # @param filterrific_params [ActionController::Params, Hash]
    # @param opts [Hash]
    # @option opts [Boolean, optional] "sanitize_params"
    #   if true, sanitizes all filterrific params to prevent reflected (or stored) XSS attacks.
    #   Defaults to true.
    # @param persistence_id [String, nil]
    def compute_filterrific_params(model_class, filterrific_params, opts, persistence_id)
      opts = {"sanitize_params" => true}.merge(opts.stringify_keys)
      r = (
        filterrific_params.presence || # start with passed in params
        (persistence_id && session[persistence_id].presence) || # then try session persisted params if persistence_id is present
        opts["default_filter_params"] || # then use passed in opts
        model_class.filterrific_default_filter_params # finally use model_class defaults
      ).stringify_keys
      r.slice!(*opts["available_filters"].map(&:to_s)) if opts["available_filters"]
      # Sanitize params to prevent reflected XSS attack
      if opts["sanitize_params"]
        r.each { |k, v| r[k] = sanitize_filterrific_param(r[k]) }
      end
      r
    end

    # Sanitizes value to prevent xss attack.
    # Uses Rails ActionView::Helpers::SanitizeHelper.
    # @param val [Object] the value to sanitize. Can be any kind of object. Collections
    #   will have their members sanitized recursively.
    def sanitize_filterrific_param(val)
      case val
      when Array
        # Return Array
        val.map { |e| sanitize_filterrific_param(e) }
      when Hash
        # Return Hash
        val.each_with_object({}) { |(k, v), m| m[k] = sanitize_filterrific_param(v); }
      when NilClass
        # Nothing to do, use val as is
        val
      when String
        helpers.sanitize(val)
      else
        # Nothing to do, use val as is
        val
      end
    end
  end
end
