-- ROBLOX deviation: no need for regex patterns

_diff_cleanupMerge = function(diffs: Array<Diff>)
	diffs[#diffs + 1] = Diff.new(DIFF_EQUAL, "") -- Add a dummy entry at the end.
	local pointer = 1
	local count_delete, count_insert = 0, 0
	local text_delete, text_insert = "", ""
	local commonlength
	while diffs[pointer] do
		local diff_type = diffs[pointer][1]
		if diff_type == DIFF_INSERT then
			count_insert = count_insert + 1
			text_insert = text_insert .. diffs[pointer][2]
			pointer = pointer + 1
		elseif diff_type == DIFF_DELETE then
			count_delete = count_delete + 1
			text_delete = text_delete .. diffs[pointer][2]
			pointer = pointer + 1
		elseif diff_type == DIFF_EQUAL then
			-- Upon reaching an equality, check for prior redundancies.
			if count_delete + count_insert > 1 then
				if count_delete > 0 and count_insert > 0 then
					-- Factor out any common prefixies.
					commonlength = _diff_commonPrefix(text_insert, text_delete)
					if commonlength > 0 then
						local back_pointer = pointer - count_delete - count_insert
						if back_pointer > 1 and diffs[back_pointer - 1][1] == DIFF_EQUAL then
							diffs[back_pointer - 1][2] = diffs[back_pointer - 1][2]
								.. strsub(text_insert, 1, commonlength)
						else
							tinsert(diffs, 1, Diff.new(DIFF_EQUAL, strsub(text_insert, 1, commonlength)))
							pointer = pointer + 1
						end
						text_insert = strsub(text_insert, commonlength + 1)
						text_delete = strsub(text_delete, commonlength + 1)
					end
					-- Factor out any common suffixies.
					commonlength = _diff_commonSuffix(text_insert, text_delete)
					if commonlength ~= 0 then
						diffs[pointer][2] = strsub(text_insert, -commonlength) .. diffs[pointer][2]
						text_insert = strsub(text_insert, 1, -commonlength - 1)
						text_delete = strsub(text_delete, 1, -commonlength - 1)
					end
				end
				-- Delete the offending records and add the merged ones.
				pointer = pointer - count_delete - count_insert
				for i = 1, count_delete + count_insert do
					tremove(diffs, pointer)
				end
				if #text_delete > 0 then
					tinsert(diffs, pointer, Diff.new(DIFF_DELETE, text_delete))
					pointer = pointer + 1
				end
				if #text_insert > 0 then
					tinsert(diffs, pointer, Diff.new(DIFF_INSERT, text_insert))
					pointer = pointer + 1
				end
				pointer = pointer + 1
			elseif pointer > 1 and diffs[pointer - 1][1] == DIFF_EQUAL then
				-- Merge this equality with the previous one.
				diffs[pointer - 1][2] = diffs[pointer - 1][2] .. diffs[pointer][2]
				tremove(diffs, pointer)
			else
				pointer = pointer + 1
			end
			count_insert, count_delete = 0, 0
			text_delete, text_insert = "", ""
		end
	end
	if diffs[#diffs][2] == "" then
		diffs[#diffs] = nil -- Remove the dummy entry at the end.
	end

	-- Second pass: look for single edits surrounded on both sides by equalities
	-- which can be shifted sideways to eliminate an equality.
	-- e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
	local changes = false
	pointer = 2
	-- Intentionally ignore the first and last element (don't need checking).
	while pointer < #diffs do
		local prevDiff, nextDiff = diffs[pointer - 1], diffs[pointer + 1]
		if prevDiff[1] == DIFF_EQUAL and nextDiff[1] == DIFF_EQUAL then
			-- This is a single edit surrounded by equalities.
			local diff = diffs[pointer]
			local currentText = diff[2]
			local prevText = prevDiff[2]
			local nextText = nextDiff[2]
			if #prevText == 0 then
				tremove(diffs, pointer - 1)
				changes = true
			elseif strsub(currentText, -#prevText) == prevText then
				-- Shift the edit over the previous equality.
				diff[2] = prevText .. strsub(currentText, 1, -#prevText - 1)
				nextDiff[2] = prevText .. nextDiff[2]
				tremove(diffs, pointer - 1)
				changes = true
			elseif strsub(currentText, 1, #nextText) == nextText then
				-- Shift the edit over the next equality.
				prevDiff[2] = prevText .. nextText
				diff[2] = strsub(currentText, #nextText + 1) .. nextText
				tremove(diffs, pointer + 1)
				changes = true
			end
		end
		pointer = pointer + 1
	end
	-- If shifts were made, the diff needs reordering and another shift sweep.
	if changes then
		-- LUANOTE: no return value, but necessary to use 'return' to get
		-- tail calls.
		return _diff_cleanupMerge(diffs)
	end
	return
end

return {
	Diff = Diff,
	DIFF_EQUAL = DIFF_EQUAL,
	DIFF_DELETE = DIFF_DELETE,
	DIFF_INSERT = DIFF_INSERT,
	cleanupSemantic = diff_cleanupSemantic,

	-- private, used for testing only
	_diff_commonPrefix = _diff_commonPrefix,
	_diff_commonSuffix = _diff_commonSuffix,
	_diff_commonOverlap = _diff_commonOverlap,
	_diff_cleanupMerge = _diff_cleanupMerge,
	_diff_cleanupSemanticLossless = _diff_cleanupSemanticLossless,
}