local G=require(script.Parent.Utils)local a=require(script.Parent.Queue)local t=require(script.Parent.LoaderConstants)local d={}function d.createGroupInfo()return G.readonly({scriptInfoMap={};packageScriptInfoMap={};packageSet={}})end function d.groupPackageInfos(G,T)assert(type(G)=="\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(T)=="\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 p=a.new()local o={}for G,a in pairs(G)do if not o[a]then o[a]=true p:PushRight(a)end end local l={}local D=d.createGroupInfo()while not p:IsEmpty()do local G=p:PopLeft()if d.hasAnythingToReplicate(G,T)then if d.canAddPackageInfoToGroup(D,G,T)then d.addPackageInfoToGroup(D,G,T)if t.GROUP_EACH_PACKAGE_INDIVIDUALLY then table.insert(l,D)D=d.createGroupInfo()end elseif t.ALLOW_MULTIPLE_GROUPS then table.insert(l,D)D=d.createGroupInfo()d.addPackageInfoToGroup(D,G,T)else d.addPackageInfoToGroup(D,G,T)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 G,a in pairs(G.dependencySet)do if not o[G]then o[G]=true p:PushRight(G)end end end if next(D.packageSet)then table.insert(l,D)end return l end function d.hasAnythingToReplicate(G,a)return next(G.scriptInfoLookup[a])~=nil end function d.canAddScriptInfoToGroup(G,a,t,d)assert(type(G)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(type(a)=="\116\097\098\108\101","\066\097\100 \115\099\114\105\112\116\073\110\102\111")assert(type(t)=="\115\116\114\105\110\103","\066\097\100 \115\099\114\105\112\116\078\097\109\101")assert(type(d)=="\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 T=d[t]if T and T~=a then return false end local p=G.scriptInfoMap[t]if p and p~=a then return false end return true end function d.canAddPackageInfoToGroup(G,a,t)assert(type(G)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(type(a)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111")assert(type(t)=="\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 T={}for a,t in pairs(a.scriptInfoLookup[t])do if d.canAddScriptInfoToGroup(G,t,a,T)then T[a]=t else return false end end for a,p in pairs(a.dependencySet)do if not G.packageSet[a]then for a,t in pairs(a.scriptInfoLookup[t])do if d.canAddScriptInfoToGroup(G,t,a,T)then T[a]=t else return false end end end end return true end function d.addScriptToGroup(G,a,t)assert(type(G)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(type(t)=="\116\097\098\108\101","\066\097\100 \115\099\114\105\112\116\073\110\102\111")assert(type(a)=="\115\116\114\105\110\103","\066\097\100 \115\099\114\105\112\116\078\097\109\101")local d=G.scriptInfoMap[a]if d and d~=t 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(a))end G.scriptInfoMap[a]=t end function d.addPackageInfoToGroup(G,a,t)G.packageSet[a]=true for a,t in pairs(a.scriptInfoLookup[t])do d.addScriptToGroup(G,a,t)G.packageScriptInfoMap[a]=t end for a,T in pairs(a.dependencySet)do if not G.packageSet[a]then for a,t in pairs(a.scriptInfoLookup[t])do d.addScriptToGroup(G,a,t)end end end end return d


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
