-- ROBLOX deviation: IndexedCall annotated as any for now since we don't have tuple support in luau
type IndexedCall = any

function printReceivedCallsNegative(
	expected: Array<any>,
	indexedCalls: Array<IndexedCall>,
	isOnlyCall: boolean,
	iExpectedCall: number?
)
	if #indexedCalls == 0 then
		return ""
	end

	local label = "Received:       "

	if isOnlyCall then
		return label .. printReceivedArgs(indexedCalls[1], expected) .. "\n"
	end

	local printAligned = getRightAlignedPrinter(label)

	return "Received\n"
		.. Array.reduce(indexedCalls, function(printed: string, iCall: IndexedCall)
			local i = iCall[1]
			local args = iCall[2]

			return printed .. printAligned(tostring(i), i == iExpectedCall) .. printReceivedArgs(args, expected) .. "\n"
		end, "")
end

function printExpectedReceivedCallsPositive(
	expected: Array<any>,
	indexedCalls: Array<IndexedCall>,
	expand: boolean,
	isOnlyCall: boolean,
	iExpectedCall: number?
)
	local expectedLine = string.format("Expected: %s\n", printExpectedArgs(expected))

	if #indexedCalls == 0 then
		return expectedLine
	end

	local label = "Received: "

	if isOnlyCall and (iExpectedCall == 1 or iExpectedCall == nil) then
		local received = indexedCalls[1][2]
		if isLineDiffableCall(expected, received) then
			-- Display diff without indentation.
			local lines = {
				EXPECTED_COLOR("- Expected"),
				RECEIVED_COLOR("+ Received"),
				"",
			}

			local length = math.max(#expected, #received)
			for i = 1, length do
				local added = false

				if i <= #expected and i <= #received then
					if isEqualValue(expected[i], received[i]) then
						table.insert(lines, "  " .. printCommon(received[i]) .. ",")
						added = true
					end
					if not added and isLineDiffableArg(expected[i], received[i]) then
						local difference = diff(expected[i], received[i], { expand })

						if
							typeof(difference) == "string"
							and difference:find("%- Expected")
							and difference:find("%+ Received")
						then
							-- Omit annotation in case multiple args have diff.
							local splitLines = {}
							for s in difference:gmatch("[^\n]+") do
								table.insert(splitLines, s)
							end

							splitLines = Array.slice(splitLines, 3)
							table.insert(lines, Array.join(splitLines, "\n") .. ",")
							added = true
						end
					end
				end
				if not added then
					if i <= #expected then
						table.insert(lines, EXPECTED_COLOR("- " .. stringify(expected[i])) .. ",")
					end
					if i <= #received then
						table.insert(lines, RECEIVED_COLOR("+ " .. stringify(received[i])) .. ",")
					end
				end
				i = i + 1
			end

			return table.concat(lines, "\n") .. "\n"
		end
		return expectedLine .. label .. printReceivedArgs(received, expected) .. "\n"
	end

	local printAligned = getRightAlignedPrinter(label)

	return expectedLine
		.. "Received\n"
		.. Array.reduce(indexedCalls, function(printed: string, indexedCall: IndexedCall)
			local i = indexedCall[1]
			local received = indexedCall[2]

			local aligned = printAligned(tostring(i), i == iExpectedCall)

			if (i == iExpectedCall or iExpectedCall == nil) and isLineDiffableCall(expected, received) then
				return printed
					.. aligned:sub(1, aligned:find(":") - 1)
					.. "\n"
					.. aligned:sub(aligned:find(":") + 1, #aligned)
					.. printDiffCall(expected, received, expand)
					.. "\n"
			else
				return printed .. aligned .. printReceivedArgs(received, expected) .. "\n"
			end
		end, "")
end

local indentation = string.gsub("Received", "[a-zA-Z0-9_]", " ")

function printDiffCall(expected: Array<any>, received: Array<any>, expand: boolean)
	return Array.join(
		Array.map(received, function(arg, i)
			if i <= #expected then
				if isEqualValue(expected[i], arg) then
					return indentation .. "  " .. printCommon(arg) .. ","
				end

				if isLineDiffableArg(expected[i], arg) then
					local difference = diff(expected[i], arg, { expand = expand })

					if
						typeof(difference) == "string"
						and difference:find("%- Expected")
						and difference:find("%+ Received")
					then
						-- Display diff with indentation.
						-- Omit annotation in case multiple args have diff.
						local splitLines = {}
						for s in difference:gmatch("[^\n]+") do
							table.insert(splitLines, s)
						end

						return Array.join(
							Array.map(Array.slice(splitLines, 3), function(line)
								return indentation .. line
							end),
							"\n"
						) .. ","
					end
				end
			end

			-- Display + only if received arg has no corresponding expected arg.
			return indentation
				.. (function()
					if i <= #expected then
						return "  " .. printReceived(arg)
					end

					return RECEIVED_COLOR("+ " .. stringify(arg))
				end)()
				.. ","
		end),
		"\n"
	)
end

function isLineDiffableCall(expected: Array<any>, received: Array<any>): boolean
	return Array.some(expected, function(arg, i)
		return i <= #received and isLineDiffableArg(arg, received[i])
	end)
end