-- FIXME: After CLI-39007, change expected to have type string | RegExp type
-- ROBLOX deviation: toMatch accepts Lua string patterns or RegExp polyfill but not simple substring
local function toMatch(
	-- ROBLOX deviation: self param in Lua needs to be explicitely defined
	self: MatcherState,
	received: string,
	expected: any
)
	local matcherName = "toMatch"
	local options: MatcherHintOptions = {
		isNot = self.isNot,
		promise = self.promise,
	}

	if typeof(received) ~= "string" then
		error(
			Error(
				matcherErrorMessage(
					matcherHint(matcherName, nil, nil, options),
					string.format("%s value must be a string", RECEIVED_COLOR("received")),
					printWithType("Received", received, printReceived)
				)
			)
		)
	end

	if typeof(expected) ~= "string" and getType(expected) ~= "regexp" then
		error(
			Error(
				matcherErrorMessage(
					matcherHint(matcherName, nil, nil, options),
					string.format("%s value must be a string or regular expression", EXPECTED_COLOR("expected")),
					printWithType("Expected", expected, printExpected)
				)
			)
		)
	end

	local pass
	if typeof(expected) == "string" then
		-- ROBLOX deviation: escape chalk sequences if necessary
		expected = string.gsub(expected, string.char(27) .. "%[", string.char(27) .. "%%[")
		pass = received:find(expected) ~= nil
	else
		pass = expected:test(received)
	end

	-- ROBLOX deviation: We print "expected pattern" in both cases because this function
	-- treats strings as Lua patterns
	local message
	if pass then
		message = function()
			local retval = matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. string.format("Expected pattern: never %s\n", printExpected(expected))
			if typeof(expected) == "string" then
				retval = retval
					.. string.format(
						"Received string:        %s",
						printReceivedStringContainExpectedSubstring(received, received:find(expected), #expected)
					)
			else
				retval = retval
					.. string.format(
						"Received string:        %s",
						printReceivedStringContainExpectedResult(received, expected:exec(received))
					)
			end

			return retval
		end
	else
		message = function()
			local labelExpected = "Expected pattern"
			local labelReceived = "Received string"
			local printLabel = getLabelPrinter(labelExpected, labelReceived)

			return matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. string.format("%s%s\n", printLabel(labelExpected), printExpected(expected))
				.. string.format("%s%s", printLabel(labelReceived), printReceived(received))
		end
	end

	return { message = message, pass = pass }
end

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

	if typeof(received) ~= "table" or received == nil then
		error(
			Error(
				matcherErrorMessage(
					matcherHint(matcherName, nil, nil, options),
					string.format("%s value must be a non-nil object", RECEIVED_COLOR("received")),
					printWithType("Received", received, printReceived)
				)
			)
		)
	end

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

	-- ROBLOX TODO: Revisit usage of subsetEquality
	local pass = equals(received, expected, { iterableEquality, subsetEquality })

	local message
	if pass then
		message = function()
			local retval = matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. string.format("Expected: never %s", printExpected(expected))
			if stringify(expected) ~= stringify(received) then
				return retval .. string.format("\nReceived:       %s", printReceived(received))
			end
			return retval
		end
	else
		message = function()
			return matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. printDiffOrStringify(
					expected,
					getObjectSubset(received, expected),
					EXPECTED_LABEL,
					RECEIVED_LABEL,
					isExpand(self.expand)
				)
		end
	end

	return { message = message, pass = pass }
end