-- ROBLOX deviation: matcher does not have RawMatcherFn type annotation and the
-- the function return is not annotated with ThrowingMatcherFn

function makeThrowingMatcher(
	matcher: RawMatcherFn,
	isNot: boolean,
	promise: string,
	actual: any,
	err: JestAssertionError?
): ThrowingMatcherFn
	return function(...)
		local throws = true
		local utils = Object.assign({
			iterableEquality = iterableEquality,
			subsetEquality = subsetEquality,
			-- ROBLOX deviation: Roblox Instance matchers
			-- instanceSubsetEquality = instanceSubsetEquality
		}, matcherUtils)

		local matcherContext = {
			-- When throws is disabled, the matcher will not throw errors during test
			-- execution but instead add them to the global matcher state. If a
			-- matcher throws, test execution is normally stopped immediately. The
			-- snapshot matcher uses it because we want to log all snapshot
			-- failures in a test.
			dontThrow = function()
				throws = false
			end,
			equals = equals,
			error = err,
			isNot = isNot,
			promise = promise,
			utils = utils,
		}
		Object.assign(matcherContext, getState())

		local function processResult(result: SyncExpectationResult, asyncError: JestAssertionError?)
			_validateResult(result)

			getState().assertionCalls = getState().assertionCalls + 1

			if (result.pass and isNot) or (not result.pass and not isNot) then
				-- XOR
				local message = getMessage(result.message)
				local error_

				if err then
					error_ = err
					error_.message = message
				elseif asyncError then
					error("Currently async is not implemented")
				else
					error_ = {}
					error_.message = message
				end

				-- Passing the result of the matcher with the error so that a custom
				-- reporter could access the actual and expected objects of the result
				-- for example in order to display a custom visual diff
				error_.matcherResult = Object.assign({}, result, { message = message })

				if throws then
					error(AssertionError.new({ message = message }))
				else
					table.insert(getState().suppressedErrors, error)
				end
			end
		end

		local potentialResult, preservedStack, result

		local ok = xpcall(function(...)
			-- ROBLOX TODO: Implement INTERNAL_MATCHER_FLAG cases
			potentialResult = matcher(matcherContext, actual, ...)

			local syncResult = potentialResult

			return processResult(syncResult)
		end, function(e)
			preservedStack = debug.traceback(nil, 7)
			result = e
		end, ...)

		if not ok then
			if typeof(result) == "table" and typeof(result.message) == "string" then
				local errorTable = AssertionError.new({ message = result.message })
				errorTable.stack = preservedStack
				error(errorTable)
			else
				local errorTable = AssertionError.new({ message = result })
				errorTable.stack = preservedStack
				error(errorTable)
			end
		end
	end
end

function _validateResult(result: any)
	if
		typeof(result) ~= "table"
		or typeof(result.pass) ~= "boolean"
		or (result.message and typeof(result.message) ~= "string" and typeof(result.message) ~= "function")
	then
		error(
			"Unexpected return from a matcher function.\n"
				.. "Matcher functions should "
				.. "return an object in the following format:\n"
				.. "  {message?: string | function, pass: boolean}\n"
				.. matcherUtils.stringify(result)
				.. " was returned"
		)
	end
end

local Expect = {}
