local S=require(script.Parent.Utils)local z=require(script.Parent.Queue)local m=require(script.Parent.LoaderConstants)local r={}function r.createGroupInfo()return S.readonly({scriptInfoMap={},packageScriptInfoMap={},packageSet={}})end function r.groupPackageInfos(S,Y)assert(type(S)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\076\105\115\116")assert(type(Y)=="\115\116\114\105\110\103","\066\097\100 \114\101\112\108\105\099\097\116\105\111\110\077\111\100\101")local O=z.new()local G={}for S,z in pairs(S)do if not G[z]then G[z]=true O:PushRight(z)end end local I={}local R=r.createGroupInfo()while not O:IsEmpty()do local S=O:PopLeft()if r.hasAnythingToReplicate(S,Y)then if r.canAddPackageInfoToGroup(R,S,Y)then r.addPackageInfoToGroup(R,S,Y)if m.GROUP_EACH_PACKAGE_INDIVIDUALLY then table.insert(I,R)R=r.createGroupInfo()end elseif m.ALLOW_MULTIPLE_GROUPS then table.insert(I,R)R=r.createGroupInfo()r.addPackageInfoToGroup(R,S,Y)else r.addPackageInfoToGroup(R,S,Y)error("\067\097\110\110\111\116 \097\100\100 \112\097\099\107\097\103\101 \116\111 \103\114\111\117\112")end end for S,z in pairs(S.dependencySet)do if not G[S]then G[S]=true O:PushRight(S)end end end if next(R.packageSet)then table.insert(I,R)end return I end function r.hasAnythingToReplicate(S,z)return next(S.scriptInfoLookup[z])~=nil end function r.canAddScriptInfoToGroup(S,z,m,r)assert(type(S)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(type(z)=="\116\097\098\108\101","\066\097\100 \115\099\114\105\112\116\073\110\102\111")assert(type(m)=="\115\116\114\105\110\103","\066\097\100 \115\099\114\105\112\116\078\097\109\101")assert(type(r)=="\116\097\098\108\101","\066\097\100 \116\101\109\112\083\099\114\105\112\116\073\110\102\111\077\097\112")local Y=r[m]if Y and Y~=z then return false end local O=S.scriptInfoMap[m]if O and O~=z then return false end return true end function r.canAddPackageInfoToGroup(S,z,m)assert(type(S)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(type(z)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111")assert(type(m)=="\115\116\114\105\110\103","\066\097\100 \114\101\112\108\105\099\097\116\105\111\110\077\111\100\101")local Y={}for z,m in pairs(z.scriptInfoLookup[m])do if r.canAddScriptInfoToGroup(S,m,z,Y)then Y[z]=m else return false end end for z,O in pairs(z.dependencySet)do if not S.packageSet[z]then for z,m in pairs(z.scriptInfoLookup[m])do if r.canAddScriptInfoToGroup(S,m,z,Y)then Y[z]=m else return false end end end end return true end function r.addScriptToGroup(S,z,m)assert(type(S)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(type(m)=="\116\097\098\108\101","\066\097\100 \115\099\114\105\112\116\073\110\102\111")assert(type(z)=="\115\116\114\105\110\103","\066\097\100 \115\099\114\105\112\116\078\097\109\101")local r=S.scriptInfoMap[z]if r and r~=m then error(("\067\097\110\110\111\116 \097\100\100 \116\111 \112\097\099\107\097\103\101 \103\114\111\117\112\044 \099\111\110\102\108\105\099\116\105\110\103 \115\099\114\105\112\116\073\110\102\111 \102\111\114 \037\113 \097\108\114\101\097\100\121 \116\104\101\114\101"):format(z))end S.scriptInfoMap[z]=m end function r.addPackageInfoToGroup(S,z,m)S.packageSet[z]=true for z,m in pairs(z.scriptInfoLookup[m])do r.addScriptToGroup(S,z,m)S.packageScriptInfoMap[z]=m end for z,Y in pairs(z.dependencySet)do if not S.packageSet[z]then for z,m in pairs(z.scriptInfoLookup[m])do r.addScriptToGroup(S,z,m)end end end end return r
--[=[
	Nevermore loader utility library
	@private
	@class GroupInfoUtils
]=]

local Utils = require(script.Parent.Utils)
local Queue = require(script.Parent.Queue)
local LoaderConstants = require(script.Parent.LoaderConstants)

local GroupInfoUtils = {}

function GroupInfoUtils.createGroupInfo()
	return Utils.readonly({
		scriptInfoMap = {}, -- [name] = scriptInfo (required link packages)
		packageScriptInfoMap = {},
		packageSet = {}, -- [packageInfo] = true (actually included packages)
	})
end

function GroupInfoUtils.groupPackageInfos(packageInfoList, replicationMode)
	assert(type(packageInfoList) == "table", "Bad packageInfoList")
	assert(type(replicationMode) == "string", "Bad replicationMode")

	local queue = Queue.new()
	local seen = {}

	for _, packageInfo in pairs(packageInfoList) do
		if not seen[packageInfo] then
			seen[packageInfo] = true
			queue:PushRight(packageInfo)
		end
	end

	local built = {}
	local current = GroupInfoUtils.createGroupInfo()
	while not queue:IsEmpty() do
		local packageInfo = queue:PopLeft()
		if GroupInfoUtils.hasAnythingToReplicate(packageInfo, replicationMode) then
			if GroupInfoUtils.canAddPackageInfoToGroup(current, packageInfo, replicationMode) then
				GroupInfoUtils.addPackageInfoToGroup(current, packageInfo, replicationMode)

				if LoaderConstants.GROUP_EACH_PACKAGE_INDIVIDUALLY then
					table.insert(built, current)
					current = GroupInfoUtils.createGroupInfo()
				end
			elseif LoaderConstants.ALLOW_MULTIPLE_GROUPS then
				-- Create a new group
				table.insert(built, current)
				current = GroupInfoUtils.createGroupInfo()
				GroupInfoUtils.addPackageInfoToGroup(current, packageInfo, replicationMode)
			else
				-- Force generate error
				GroupInfoUtils.addPackageInfoToGroup(current, packageInfo, replicationMode)

				error("Cannot add package to group")
			end
		end

		for dependentPackageInfo, _ in pairs(packageInfo.dependencySet) do
			if not seen[dependentPackageInfo] then
				seen[dependentPackageInfo] = true
				queue:PushRight(dependentPackageInfo)
			end
		end
	end

	if next(current.packageSet) then
		table.insert(built, current)
	end

	return built
end

function GroupInfoUtils.hasAnythingToReplicate(packageInfo, replicationMode)
	return next(packageInfo.scriptInfoLookup[replicationMode]) ~= nil
end

function GroupInfoUtils.canAddScriptInfoToGroup(groupInfo, scriptInfo, scriptName, tempScriptInfoMap)
	assert(type(groupInfo) == "table", "Bad groupInfo")
	assert(type(scriptInfo) == "table", "Bad scriptInfo")
	assert(type(scriptName) == "string", "Bad scriptName")
	assert(type(tempScriptInfoMap) == "table", "Bad tempScriptInfoMap")

	local wouldHaveInfo = tempScriptInfoMap[scriptName]
	if wouldHaveInfo and wouldHaveInfo ~= scriptInfo then
		return false
	end

	local currentScriptInfo = groupInfo.scriptInfoMap[scriptName]
	if currentScriptInfo and currentScriptInfo ~= scriptInfo then
		return false
	end

	return true
end

function GroupInfoUtils.canAddPackageInfoToGroup(groupInfo, packageInfo, replicationMode)
	assert(type(groupInfo) == "table", "Bad groupInfo")
	assert(type(packageInfo) == "table", "Bad packageInfo")
	assert(type(replicationMode) == "string", "Bad replicationMode")

	local tempScriptInfoMap = {}

	-- Existing scripts must be added
	for scriptName, scriptInfo in pairs(packageInfo.scriptInfoLookup[replicationMode]) do
		if GroupInfoUtils.canAddScriptInfoToGroup(groupInfo, scriptInfo, scriptName, tempScriptInfoMap) then
			tempScriptInfoMap[scriptName] = scriptInfo
		else
			return false
		end
	end

	-- Dependencies are expected at parent level
	for dependencyPackageInfo, _ in pairs(packageInfo.dependencySet) do
		if not groupInfo.packageSet[dependencyPackageInfo] then
			-- Lookup dependencies and try to merge them
			-- O(p*d*s)
			for scriptName, scriptInfo in pairs(dependencyPackageInfo.scriptInfoLookup[replicationMode]) do
				if GroupInfoUtils.canAddScriptInfoToGroup(groupInfo, scriptInfo, scriptName, tempScriptInfoMap) then
					tempScriptInfoMap[scriptName] = scriptInfo
				else
					return false
				end
			end
		end
	end

	return true
end

function GroupInfoUtils.addScriptToGroup(groupInfo, scriptName, scriptInfo)
	assert(type(groupInfo) == "table", "Bad groupInfo")
	assert(type(scriptInfo) == "table", "Bad scriptInfo")
	assert(type(scriptName) == "string", "Bad scriptName")

	local currentScriptInfo = groupInfo.scriptInfoMap[scriptName]
	if currentScriptInfo and currentScriptInfo ~= scriptInfo then
		error(("Cannot add to package group, conflicting scriptInfo for %q already there"):format(scriptName))
	end

	groupInfo.scriptInfoMap[scriptName] = scriptInfo
end

function GroupInfoUtils.addPackageInfoToGroup(groupInfo, packageInfo, replicationMode)
	groupInfo.packageSet[packageInfo] = true

	-- Existing scripts must be added
	for scriptName, scriptInfo in pairs(packageInfo.scriptInfoLookup[replicationMode]) do
		GroupInfoUtils.addScriptToGroup(groupInfo, scriptName, scriptInfo)
		groupInfo.packageScriptInfoMap[scriptName] = scriptInfo
	end

	-- Dependencies are expected at parent level
	for dependencyPackageInfo, _ in pairs(packageInfo.dependencySet) do
		if not groupInfo.packageSet[dependencyPackageInfo] then
			-- Lookup dependencies and try to merge them
			-- O(p*d*s)
			for scriptName, scriptInfo in pairs(dependencyPackageInfo.scriptInfoLookup[replicationMode]) do
				GroupInfoUtils.addScriptToGroup(groupInfo, scriptName, scriptInfo)
			end
		end
	end
end

return GroupInfoUtils
