module JSONAPIonify::Api
  module Resource::Defaults::RequestContexts
    extend ActiveSupport::Concern

    included do
      context(:request_body, readonly: true, persisted: true) do |request:|
        request.body.read
      end

      context(:request_object, readonly: true, persisted: true) do |context, request_body:|
        JSONAPIonify.parse(request_body).as(:client).tap do |input|
          error_now(:request_object_invalid, context, input) unless input.validate
        end
      end

      context(:id, readonly: true, persisted: true) do |request:|
        request.env['jsonapionify.id']
      end

      context(:request_id, readonly: true, persisted: true) do |request_data:|
        request_data[:id]
      end

      context(:request_attributes, readonly: true, persisted: true) do |context, request:, action_name:, id:, request_data:|
        should_error = false

        request_attributes = request_data.fetch(:attributes, {})

        # Check for required attributes
        self.attributes.each do |attr|
          next unless attr.required_for_action?(action_name, context)
          if attr.read? || id
            example_id = self.build_id(instance: context.instance)
            next unless attr.resolve(
              context.instance, context, example_id: example_id
            ).nil?
          end
          unless request_attributes.has_key?(attr.name)
            error :attribute_required, attr.name
            should_error = true
          end
        end

        request_attributes.each_with_object({}) do |(attr, v), attributes|
          resource_attribute = self.attributes.find { |a| a.name == attr }
          is_actionable      = !!resource_attribute&.supports_write_for_action?(action_name, context)
          unless is_actionable
            error :attribute_not_permitted, attr
            should_error = true
          end

          begin
            attributes[attr] = resource_attribute.type.load(v)
          rescue JSONAPIonify::Types::LoadError
            error :attribute_type_error, attr
            should_error = true
          end
        end.tap do
          halt if should_error
        end
      end

      context(:request_relationships, readonly: true, persisted: true) do |request_data:|
        if request_data[:relationships]
          request_data[:relationships].each_with_object({}) do |(name, rel), obj|
            pointer = "data/relationships/#{name}/data"
            case rel[:data]
            when JSONAPIonify::Structure::Collections::Base
              obj[name] = find_instances(rel[:data], pointer: pointer)
            when JSONAPIonify::Structure::Objects::Base
              obj[name] = find_instance(rel[:data], pointer: pointer)
            end
          end
        else
          {}
        end
      end

      context(:request_instances, readonly: true, persisted: true) do |request_data:|
        (request_data ? find_instances(request_data, pointer: '/data') : [])
      end

      context(:request_instance, readonly: true, persisted: true) do |request_data:|
        find_instance(request_data, pointer: 'data')
      end

      context(:request_resource, readonly: true, persisted: true) do |request_data:|
        find_resource request_data, pointer: 'data'
      end

      context(:request_data, readonly: true, persisted: true) do |request_object:|
        request_object.fetch(:data) {
          error_now(:data_missing)
        }
      end

      context(:authentication, readonly: true, persisted: true) do
        OpenStruct.new
      end
    end

    def find_instances(items, pointer:)
      should_error = false
      instances    = items.map.each_with_index do |item, i|
        begin
          find_instance item, pointer: "#{pointer}/#{i}"
        rescue Errors::RequestError
          should_error = true
        end
      end
      halt if should_error
      instances
    end

    def find_instance(item, pointer:)
      should_error = false
      resource     = find_resource(item, pointer: pointer)
      unless (instance = resource.find_instance item[:id])
        should_error = true
        error :resource_invalid do
          self.pointer pointer
          self.detail "could not find resource: `#{item[:type]}` with id: #{item[:id]}"
        end
      end
      halt if should_error
      instance
    end

    def find_resource(item, pointer:)
      should_error = false
      unless (resource = self.class.api.resource item[:type])
        should_error = true
        error :resource_invalid do
          self.pointer pointer
          self.detail "could not find resource: `#{item[:type]}`"
        end
      end
      halt if should_error
      resource
    end

  end
end