--[[
	Yield until the promise is completed.

	This matches the execution model of normal Roblox functions.
]]

function Promise:await()
	self._unhandledRejection = false

	if self._status == Promise.Status.Started then
		local result
		local bindable = Instance.new("BindableEvent")

		self:andThen(function(...)
			result = {...}
			bindable:Fire(true)
		end, function(...)
			result = {...}
			bindable:Fire(false)
		end)

		local ok = bindable.Event:Wait()
		bindable:Destroy()

		if not ok then
			error(tostring(result[1]), 2)
		end

		return unpack(result)
	elseif self._status == Promise.Status.Resolved then
		return unpack(self._value)
	elseif self._status == Promise.Status.Rejected then
		error(tostring(self._value[1]), 2)
	end
end

function Promise:_resolve(...)
	if self._status ~= Promise.Status.Started then
		return
	end

	-- If the resolved value was a Promise, we chain onto it!
	if Promise.is((...)) then
		-- Without this warning, arguments sometimes mysteriously disappear
		if select("#", ...) > 1 then
			local message = ("When returning a Promise from andThen, extra arguments are discarded! See:\n\n%s"):format(
				self._source
			)
			warn(message)
		end

		(...):andThen(function(...)
			self:_resolve(...)
		end, function(...)
			self:_reject(...)
		end)

		return
	end

	self._status = Promise.Status.Resolved
	self._value = {...}

	-- We assume that these callbacks will not throw errors.
	for _, callback in ipairs(self._queuedResolve) do
		callback(...)
	end
end

function Promise:_reject(...)
	if self._status ~= Promise.Status.Started then
		return
	end

	self._status = Promise.Status.Rejected
	self._value = {...}

	-- If there are any rejection handlers, call those!
	if not isEmpty(self._queuedReject) then
		-- We assume that these callbacks will not throw errors.
		for _, callback in ipairs(self._queuedReject) do
			callback(...)
		end
	else
		-- At this point, no one was able to observe the error.
		-- An error handler might still be attached if the error occurred
		-- synchronously. We'll wait one tick, and if there are still no
		-- observers, then we should put a message in the console.

		self._unhandledRejection = true
		-- Rather than trying to figure out how to pack/represent the rejection values,
		-- we just stringify the first value and leave the rest alone
		local err = tutils.toString((...))

		spawn(function()
			-- Someone observed the error, hooray!
			if not self._unhandledRejection then
				return
			end

			-- Build a reasonable message
			local message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
				err,
				self._source
			)
			warn(message)
		end)
	end
end

return Promise
