module JSONAPIonify::Api
  module Resource::Definitions::Params

    def self.extended(klass)
      klass.class_eval do
        extend JSONAPIonify::InheritedAttributes
        inherited_hash_attribute :param_definitions

        context(:params, readonly: true, persisted: true) do |context|
          should_error = false

          params = self.class.param_definitions.select do |_, v|
            v.actions.blank? || v.actions.include?(action_name)
          end

          context.request.params.replace(
            [*params.values.select(&:has_default?).map(&:default), context.request.params].reduce(:deep_merge)
          )

          required_params = params.select do |_, v|
            v.required
          end

          # Check for validity
          context.request.params.each do |k, v|
            keypath  = ParamOptions.hash_to_keypaths(k => v)[0]
            reserved = ParamOptions.reserved?(k)
            allowed  = params.keys.include? keypath
            valid    = ParamOptions.valid?(k) || v.is_a?(Hash)
            unless reserved || (allowed && valid) || !context.root_request?
              should_error = true
              error :parameter_invalid, ParamOptions.keypath_to_string(*keypath)
            end
          end unless context.request.options?

          # Check for requirement
          missing_params =
            ParamOptions.missing_parameters(
              context.request.params,
              required_params.values.map(&:keypath)
            )
          if context.root_request? && missing_params.present?
            error :parameters_missing, missing_params
          end

          halt if should_error

          # Return the params
          context.request.params
        end

      end
    end

    def param(*keypath, **options)
      param_definitions[keypath] = ParamOptions.new(*keypath, **options)
    end

    def sticky_params(params)
      sticky_param_definitions = param_definitions.values.select(&:sticky)
      ParamOptions.hash_to_keypaths(params).map do |keypath|
        definition = sticky_param_definitions.find do |definition|
          definition.keypath == keypath
        end
        next {} unless definition
        value = definition.extract_value(params)
        if definition.default_value?(value)
          {}
        else
          definition.with_value(value)
        end
      end.reduce(:deep_merge)
    end

  end
end