-- ROBLOX deviation: upstream defines matchers as a single monolithic object but we split it up for readability

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

	local pass = Object.is(received, expected)

	local message
	if pass then
		message = function()
			return matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. string.format("Expected: never %s", printExpected(expected))
		end
	else
		message = function()
			local deepEqualityName = nil
			-- ROBLOX deviation: no map or set types for now so this check is always true so we omit it
			-- if expectedType ~= 'map' and expectedType ~= 'set' then
			-- If deep equality passes when referential identity fails,
			-- but exclude map and set until review of their equality logic.
			-- ROBLOX deviation: no strict equality in lua
			if equals(received, expected, { iterableEquality }) then
				deepEqualityName = "toEqual"
			end
			-- end

			local retval = matcherHint(matcherName, nil, nil, options) .. "\n\n"
			if deepEqualityName ~= nil then
				retval = retval
					.. DIM_COLOR(
						string.format(
							'If it should pass with deep equality, replace "%s" with "%s"',
							matcherName,
							deepEqualityName
						)
					)
					.. "\n\n"
			end
			return retval
				.. printDiffOrStringify(expected, received, EXPECTED_LABEL, RECEIVED_LABEL, isExpand(self.expand))
		end
	end

	-- Passing the actual and expected objects so that a custom reporter
	-- could access them, for example in order to display a custom visual diff,
	-- or create a different error message
	return { actual = received, expected = expected, message = message, name = matcherName, pass = pass }
end

local function toBeCloseTo(
	-- ROBLOX deviation: self param in Lua needs to be explicitely defined
	self: MatcherState,
	received: number,
	expected: number,
	precision: number
)
	-- ROBLOX deviation: instead of getting argument count, just see if last arg is nil
	local secondArgument
	if precision then
		secondArgument = "precision"
	else
		precision = 2
	end
	local matcherName = "toBeCloseTo"
	local isNot = self.isNot
	local options: MatcherHintOptions = {
		isNot = isNot,
		promise = self.promise,
		secondArgument = secondArgument,
		secondArgumentColor = function(arg: string)
			return arg
		end,
	}

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

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

	local pass = false
	local expectedDiff = 0
	local receivedDiff = 0

	if received == math.huge and expected == math.huge then
		pass = true -- Infinity - Infinity is NaN
	elseif received == -math.huge and expected == -math.huge then
		pass = true -- -Infinity - -Infinity is NaN
	else
		expectedDiff = (10 ^ -precision) / 2
		receivedDiff = math.abs(expected - received)
		pass = receivedDiff < expectedDiff
	end

	local message
	if pass then
		message = function()
			local retval = matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. string.format("Expected: never %s\n", printExpected(expected))
			if receivedDiff == 0 then
				return retval
			end
			return retval
				.. string.format("Received:       %s\n", printReceived(received))
				.. "\n"
				.. printCloseTo(receivedDiff, expectedDiff, precision, isNot)
		end
	else
		message = function()
			return matcherHint(matcherName, nil, nil, options)
				.. "\n\n"
				.. string.format("Expected: %s\n", printExpected(expected))
				.. string.format("Received: %s\n", printReceived(received))
				.. "\n"
				.. printCloseTo(receivedDiff, expectedDiff, precision, isNot)
		end
	end

	return { message = message, pass = pass }
end
