-- ROBLOX deviation: toBeInstanceOf checks for Lua prototypical classes, major deviation from upstream
local function toBeInstanceOf(
	-- ROBLOX deviation: self param in Lua needs to be explicitely defined
	self: MatcherState,
	received: any,
	expected: any
)
	local matcherName = "toBeInstanceOf"
	local isNot = self.isNot
	local options: MatcherHintOptions = {
		isNot = isNot,
		promise = self.promise,
	}

	if typeof(expected) ~= "table" then
		error(
			Error(
				matcherErrorMessage(
					matcherHint(matcherName, nil, nil, options),
					string.format("%s value must be a prototype class", EXPECTED_COLOR("expected")),
					printWithType("Expected", expected, printExpected)
				)
			)
		)
	end

	local pass = instanceof(received, expected)
	local receivedPrototype = nil
	if typeof(getmetatable(received)) == "table" and typeof(getmetatable(received).__index) == "table" then
		receivedPrototype = getmetatable(received).__index
	end

	local message
	if pass then
		message = function()
			local retval = matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. printExpectedConstructorNameNot("Expected constructor", expected)

			if receivedPrototype and receivedPrototype ~= expected then
				retval = retval .. printReceivedConstructorNameNot("Received constructor", receivedPrototype, expected)
			end

			return retval
		end
	else
		message = function()
			local retval = matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. printExpectedConstructorName("Expected constructor", expected)
			if isPrimitive(received) or receivedPrototype == nil then
				retval = retval
					.. string.format("\nReceived value has no prototype\nReceived value: %s", printReceived(received))
			else
				retval = retval .. printReceivedConstructorName("Received constructor", receivedPrototype)
			end

			return retval
		end
	end

	return { message = message, pass = pass }
end

local function toBeLessThan(
	-- ROBLOX deviation: self param in Lua needs to be explicitely defined
	self: MatcherState,
	received: number,
	expected: number
)
	local matcherName = "toBeLessThan"
	local isNot = self.isNot
	local options: MatcherHintOptions = {
		isNot = isNot,
		promise = self.promise,
	}
	ensureNumbers(received, expected, matcherName, options)

	local pass = received < expected

	local message = function()
		return matcherHint(matcherName, nil, nil, options)
			.. "\n\n"
			.. string.format("Expected:%s < %s\n", isNot and " never" or "", printExpected(expected))
			.. string.format("Received:%s   %s", isNot and "      " or "", printReceived(received))
	end

	return { message = message, pass = pass }
end

local function toBeLessThanOrEqual(
	-- ROBLOX deviation: self param in Lua needs to be explicitely defined
	self: MatcherState,
	received: number,
	expected: number
)
	local matcherName = "toBeLessThanOrEqual"
	local isNot = self.isNot
	local options: MatcherHintOptions = {
		isNot = isNot,
		promise = self.promise,
	}
	ensureNumbers(received, expected, matcherName, options)

	local pass = received <= expected

	local message = function()
		return matcherHint(matcherName, nil, nil, options)
			.. "\n\n"
			.. string.format("Expected:%s <= %s\n", isNot and " never" or "", printExpected(expected))
			.. string.format("Received:%s    %s", isNot and "      " or "", printReceived(received))
	end

	return { message = message, pass = pass }
end

local function toBeNan(
	-- ROBLOX deviation: self param in Lua needs to be explicitely defined
	self: MatcherState,
	received: any,
	expected: nil
)
	local matcherName = "toBeNan"
	local options: MatcherHintOptions = {
		isNot = self.isNot,
		promise = self.promise,
	}
	ensureNoExpected(expected, matcherName, options)

	local pass = Number.isNaN(received)

	local message = function()
		return matcherHint(matcherName, nil, "", options)
			.. "\n\n"
			.. string.format("Received: %s", printReceived(received))
	end

	return { message = message, pass = pass }
end

local function toBeNil(
	-- ROBLOX deviation: self param in Lua needs to be explicitely defined
	self: MatcherState,
	received: any,
	expected: nil
)
	local matcherName = "toBeNil"
	local options: MatcherHintOptions = {
		isNot = self.isNot,
		promise = self.promise,
	}
	ensureNoExpected(expected, matcherName, options)

	local pass = received == nil

	local message = function()
		return matcherHint(matcherName, nil, "", options)
			.. "\n\n"
			.. string.format("Received: %s", printReceived(received))
	end

	return { message = message, pass = pass }
end

local function toBeTruthy(
	-- ROBLOX deviation: self param in Lua needs to be explicitely defined
	self: MatcherState,
	received: any,
	expected: nil
)
	local matcherName = "toBeTruthy"
	local options: MatcherHintOptions = {
		isNot = self.isNot,
		promise = self.promise,
	}
	ensureNoExpected(expected, matcherName, options)

	local pass = not not received

	local message = function()
		return matcherHint(matcherName, nil, "", options)
			.. "\n\n"
			.. string.format("Received: %s", printReceived(received))
	end

	return { message = message, pass = pass }
end