module JsonapiCompliable
  module Base
    extend ActiveSupport::Concern
    include Deserializable

    MAX_PAGE_SIZE = 1_000

    included do
      class_attribute :_jsonapi_compliable
      attr_reader :_jsonapi_scoped

      before_action :parse_fieldsets!
      after_action :reset_scope_flag
    end

    def default_page_number
      1
    end

    def default_page_size
      20
    end

    def default_sort
      'id'
    end

    def jsonapi_scope(scope,
                      filter: true,
                      includes: true,
                      paginate: true,
                      extra_fields: true,
                      sort: true)
      scope = JsonapiCompliable::Scope::DefaultFilter.new(self, scope).apply
      scope = JsonapiCompliable::Scope::Filter.new(self, scope).apply if filter
      scope = JsonapiCompliable::Scope::ExtraFields.new(self, scope).apply if extra_fields
      scope = JsonapiCompliable::Scope::Sideload.new(self, scope).apply if includes
      scope = JsonapiCompliable::Scope::Sort.new(self, scope).apply if sort
      scope = JsonapiCompliable::Scope::Paginate.new(self, scope).apply if paginate
      @_jsonapi_scoped = true
      scope
    end

    def reset_scope_flag
      @_jsonapi_scoped = false
    end

    def parse_fieldsets!
      Util::FieldParams.parse!(params, :fields)
      Util::FieldParams.parse!(params, :extra_fields)
    end

    def render_ams(scope, opts = {})
      scope = jsonapi_scope(scope) if Util::Scoping.apply?(self, scope, opts.delete(:scope))
      options = default_ams_options
      options[:include] = forced_includes || Util::IncludeParams.scrub(self)
      options[:jsonapi] = scope
      options[:fields] = Util::FieldParams.fieldset(params, :fields) if params[:fields]
      options[:extra_fields] = Util::FieldParams.fieldset(params, :extra_fields) if params[:extra_fields]

      options.merge!(opts)
      render(options)
    end

    # render_ams(foo) equivalent to
    # render json: foo, ams_default_options
    def default_ams_options
      {}.tap do |options|
        options[:adapter] = :json_api
      end
    end

    # TODO: This nastiness likely goes away once jsonapi standardizes
    # a spec for nested relationships.
    # See: https://github.com/json-api/json-api/issues/1089
    def forced_includes(data = nil)
      return unless force_includes?
      data = raw_params[:data] unless data

      {}.tap do |forced|
        (data[:relationships] || {}).each_pair do |relation_name, relation|
          if relation[:data].is_a?(Array)
            forced[relation_name] = {}
            relation[:data].each do |datum|
              forced[relation_name].deep_merge!(forced_includes(datum))
            end
          else
            forced[relation_name] = forced_includes(relation[:data])
          end
        end
      end
    end

    def force_includes?
      %w(PUT PATCH POST).include?(request.method) and
        raw_params.try(:[], :data).try(:[], :relationships).present?
    end

    module ClassMethods
      def jsonapi(&blk)
        if !self._jsonapi_compliable
          dsl = JsonapiCompliable::DSL.new
          self._jsonapi_compliable = dsl
        end
        self._jsonapi_compliable.instance_eval(&blk)
      end
    end
  end
end