-- ROBLOX TODO: add type constraint <EachCallback extends Global.TestCallback>

local function default<EachCallback>(cb_: GlobalCallback?, supportsDone_: boolean?)
	local supportsDone = if supportsDone_ ~= nil then supportsDone_ else true
	local cb = cb_ or unavailableFn
	return function(
		table_: Global_EachTable,
		...: any --[[ ROBLOX comment: Upstream type: <Global.TemplateData> (Array<unknown>) ]]
	)
		local taggedTemplateData = if select("#", ...) > 0 then { ... } else {}

		local function eachBind(title: string, test: Global_EachTestFn<EachCallback>, timeout: number?): ()
			local ok, result = pcall(function()
				local tests = if isArrayTable(taggedTemplateData)
					then buildArrayTests(title, table_)
					else buildTemplateTests(title, table_, taggedTemplateData)

				return Array.forEach(tests, function(row)
					-- ROBLOX FIXME Luau: supports done is known to be a boolean at this point
					return cb(row.title, applyArguments(supportsDone, row.arguments, test), timeout)
				end) :: any
			end)

			if not ok then
				local error_ = ErrorWithStack.new(result.message, eachBind)
				return cb(title, function()
					error(error_)
				end)
			end

			return result
		end
		return eachBind
	end
end
exports.default = default

function isArrayTable(data: Global_TemplateData)
	return #data == 0
end

function buildArrayTests(title: string, table_: Global_EachTable): EachTests
	validateArrayTable(table_)
	return convertArrayTable(title, table_ :: Global_ArrayTable)
end

function buildTemplateTests(title: string, table_: Global_EachTable, taggedTemplateData: Global_TemplateData): EachTests
	local headings = getHeadingKeys((table_ :: Array<string>)[1] :: string)
	validateTemplateTableArguments(headings, taggedTemplateData)
	return convertTemplateTable(title, headings, taggedTemplateData)
end

function getHeadingKeys(headings: string): Array<string>
	return String.split(extractValidTemplateHeadings(headings):gsub("%s+", ""), "|")
end
