local T=require(script.Parent.BounceTemplateUtils)local G=require(script.Parent.LoaderConstants)local o=require(script.Parent.Queue)local H=require(script.Parent.ScriptInfoUtils)local y=require(script.Parent.Utils)local R={}function R.createPackageInfo(T,G,o,H)assert(typeof(T)=="\073\110\115\116\097\110\099\101","\066\097\100 \112\097\099\107\097\103\101\070\111\108\100\101\114")assert(type(G)=="\116\097\098\108\101","\066\097\100 \101\120\112\108\105\099\105\116\068\101\112\101\110\100\101\110\099\121\083\101\116")assert(type(o)=="\116\097\098\108\101","\066\097\100 \115\099\114\105\112\116\073\110\102\111\076\111\111\107\117\112")assert(type(H)=="\115\116\114\105\110\103","\066\097\100 \102\117\108\108\078\097\109\101")return y.readonly({name=T.Name;fullName=H,instance=T,explicitDependencySet=G;dependencySet=false,scriptInfoLookup=o})end function R.createDependencyQueueInfo(T,G)assert(type(T)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111")assert(type(G)=="\116\097\098\108\101","\066\097\100 \105\109\112\108\105\099\105\116\068\101\112\101\110\100\101\110\099\121\083\101\116")return y.readonly({packageInfo=T;implicitDependencySet=G})end function R.fillDependencySet(T)assert(type(T)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\076\105\115\116")local G=o.new()local H={}do local o={}for T,y in pairs(T)do if not H[y]then o[y]=y H[y]=true G:PushRight(R.createDependencyQueueInfo(y,o))end end end while not G:IsEmpty()do local T=G:PopLeft()assert(not T.packageInfo.dependencySet,"\065\108\114\101\097\100\121 \119\114\111\116\101 \100\101\112\101\110\100\101\110\099\121\083\101\116")local o=R.computePackageDependencySet(T.packageInfo,T.implicitDependencySet)T.packageInfo.dependencySet=o for T,y in pairs(T.packageInfo.explicitDependencySet)do if not H[T]then H[T]=true G:PushRight(R.createDependencyQueueInfo(T,o))end end end end function R.computePackageDependencySet(T,o)assert(type(T)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111")assert(type(o)=="\116\097\098\108\101","\066\097\100 \105\109\112\108\105\099\105\116\068\101\112\101\110\100\101\110\099\121\083\101\116")local H={}if G.INCLUDE_IMPLICIT_DEPENDENCIES then for T,G in pairs(o)do H[T.name]=T end end for T,G in pairs(T.explicitDependencySet)do H[T.name]=T end H[T.name]=nil local y={}for T,G in pairs(H)do y[G]=true end return y end function R.getOrCreatePackageInfo(T,G,o)assert(typeof(T)=="\073\110\115\116\097\110\099\101"and(T:IsA("\070\111\108\100\101\114")or T:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")),"\066\097\100 \112\097\099\107\097\103\101\070\111\108\100\101\114")assert(type(G)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")assert(type(o)=="\115\116\114\105\110\103","\066\097\100 \115\099\111\112\101")if G[T]then return G[T]end local y=H.createScriptInfoLookup()H.populateScriptInfoLookup(T,y,H.ModuleReplicationTypes.SHARED)local f={}local N if o==""then N=T.Name else N=o..("\047"..T.Name)end local j=R.createPackageInfo(T,f,y,N)G[T]=j R.fillExplicitPackageDependencySet(f,T,G)return j end function R.getPackageInfoListFromDependencyFolder(T,G)assert(typeof(T)=="\073\110\115\116\097\110\099\101"and T:IsA("\070\111\108\100\101\114"),"\066\097\100 \102\111\108\100\101\114")assert(type(G)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")local o={}for T,H in pairs(T:GetChildren())do if H:IsA("\070\111\108\100\101\114")then if H.Name:sub(1,1)=="\064"then local T=H.Name for H,y in pairs(H:GetChildren())do R.tryLoadPackageFromInstance(o,G,y,T)end else R.tryLoadPackageFromInstance(o,G,H,"")end else warn(("\085\110\107\110\111\119\110 \105\110\115\116\097\110\099\101 \105\110 \100\101\112\101\110\100\101\110\099\121\070\111\108\100\101\114 - \037\113"):format(H:GetFullName()))end end return o end function R.tryLoadPackageFromInstance(G,o,H,y)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(o)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")assert(typeof(H)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")assert(type(y)=="\115\116\114\105\110\103","\066\097\100 \115\099\111\112\101")if T.isBounceTemplate(H)then return end if H:IsA("\070\111\108\100\101\114")or H:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then table.insert(G,R.getOrCreatePackageInfo(H,o,y))elseif H:IsA("\079\098\106\101\099\116\086\097\108\117\101")then local T=H.Value if T and(T:IsA("\070\111\108\100\101\114")or T:IsA("\077\111\100\117\108\101\083\099\114\105\112\116"))then table.insert(G,R.getOrCreatePackageInfo(T,o,y))else error(("\073\110\118\097\108\105\100 \037\113 \079\098\106\101\099\116\086\097\108\117\101 \105\110 \112\097\099\107\097\103\101 \108\105\110\107\105\110\103 \116\111 \110\111\116\104\105\110\103 \099\097\110\110\111\116 \098\101 \114\101\115\111\108\118\101\100 \105\110\116\111 \112\097\099\107\097\103\101 \100\101\112\101\110\100\101\110\099\121\n\t-\062 \037\115"):format(H.Name,H:GetFullName()))end end end


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

local PackageInfoUtils = {}

function PackageInfoUtils.createPackageInfo(packageFolder, explicitDependencySet, scriptInfoLookup, fullName)
	assert(typeof(packageFolder) == "Instance", "Bad packageFolder")
	assert(type(explicitDependencySet) == "table", "Bad explicitDependencySet")
	assert(type(scriptInfoLookup) == "table", "Bad scriptInfoLookup")
	assert(type(fullName) == "string", "Bad fullName")

	return Utils.readonly({
		name = packageFolder.Name;
		fullName = fullName;
		instance = packageFolder;
		explicitDependencySet = explicitDependencySet;
		dependencySet = false; -- will be filled in later, contains ALL expected dependencies
		scriptInfoLookup = scriptInfoLookup;
	})
end

function PackageInfoUtils.createDependencyQueueInfo(packageInfo, implicitDependencySet)
	assert(type(packageInfo) == "table", "Bad packageInfo")
	assert(type(implicitDependencySet) == "table", "Bad implicitDependencySet")

	return Utils.readonly({
		packageInfo = packageInfo;
		implicitDependencySet = implicitDependencySet;
	})
end

function PackageInfoUtils.fillDependencySet(packageInfoList)
	assert(type(packageInfoList) == "table", "Bad packageInfoList")

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

	do
		local topDependencySet = {}
		for _, packageInfo in pairs(packageInfoList) do
			if not seen[packageInfo] then
				topDependencySet[packageInfo] = packageInfo
				seen[packageInfo] = true
				queue:PushRight(PackageInfoUtils.createDependencyQueueInfo(packageInfo, topDependencySet))
			end
		end
	end

	-- Flaw: We can enter this dependency chain from multiple paths (we're a cyclic directed graph, not a tree)
	-- TODO: Determine node_modules behavior and copy it (hopefully any link upwards words)
	-- For now we do breadth first resolution of this to ensure minimal dependencies are accumulated for deep trees
	while not queue:IsEmpty() do
		local queueInfo = queue:PopLeft()
		assert(not queueInfo.packageInfo.dependencySet, "Already wrote dependencySet")

		local dependencySet = PackageInfoUtils
			.computePackageDependencySet(queueInfo.packageInfo, queueInfo.implicitDependencySet)
		queueInfo.packageInfo.dependencySet = dependencySet

		-- Process all explicit dependencies for the next level
		for packageInfo, _ in pairs(queueInfo.packageInfo.explicitDependencySet) do
			if not seen[packageInfo] then
				seen[packageInfo] = true
				queue:PushRight(PackageInfoUtils.createDependencyQueueInfo(packageInfo, dependencySet))
			end
		end
	end
end

function PackageInfoUtils.computePackageDependencySet(packageInfo, implicitDependencySet)
	assert(type(packageInfo) == "table", "Bad packageInfo")
	assert(type(implicitDependencySet) == "table", "Bad implicitDependencySet")

	-- assume folders with the same name are the same module
	local dependencyNameMap = {}

	-- Set implicit dependencies
	if LoaderConstants.INCLUDE_IMPLICIT_DEPENDENCIES then
		for entry, _ in pairs(implicitDependencySet) do
			dependencyNameMap[entry.name] = entry
		end
	end

	-- These override implicit ones
	for entry, _ in pairs(packageInfo.explicitDependencySet) do
		dependencyNameMap[entry.name] = entry
	end

	-- clear ourself as a dependency
	dependencyNameMap[packageInfo.name] = nil

	-- Note we leave conflicting scripts here as unresolved. This will output an error later.
	local dependencySet = {}
	for _, entry in pairs(dependencyNameMap) do
		dependencySet[entry] = true
	end

	return dependencySet
end

function PackageInfoUtils.getOrCreatePackageInfo(packageFolder, packageInfoMap, scope)
	assert(typeof(packageFolder) == "Instance"
		and (packageFolder:IsA("Folder") or packageFolder:IsA("ModuleScript")), "Bad packageFolder")
	assert(type(packageInfoMap) == "table", "Bad packageInfoMap")
	assert(type(scope) == "string", "Bad scope")

	if packageInfoMap[packageFolder] then
		return packageInfoMap[packageFolder]
	end

	local scriptInfoLookup = ScriptInfoUtils.createScriptInfoLookup()
	ScriptInfoUtils.populateScriptInfoLookup(
		packageFolder,
		scriptInfoLookup,
		ScriptInfoUtils.ModuleReplicationTypes.SHARED)

	local explicitDependencySet = {}
	local fullName
	if scope == "" then
		fullName = packageFolder.Name
	else
		fullName = scope .. "/" .. packageFolder.Name
	end

	local packageInfo = PackageInfoUtils
		.createPackageInfo(packageFolder, explicitDependencySet, scriptInfoLookup, fullName)
	packageInfoMap[packageFolder] = packageInfo

	-- Fill this after we've registered ourselves, in case we're somehow in a recursive dependency set
	PackageInfoUtils.fillExplicitPackageDependencySet(explicitDependencySet, packageFolder, packageInfoMap)

	return packageInfo
end

function PackageInfoUtils.getPackageInfoListFromDependencyFolder(folder, packageInfoMap)
	assert(typeof(folder) == "Instance" and folder:IsA("Folder"), "Bad folder")
	assert(type(packageInfoMap) == "table", "Bad packageInfoMap")

	local packageInfoList = {}

	-- Assume we are at the dependency level
	for _, instance in pairs(folder:GetChildren()) do
		if instance:IsA("Folder") then
			-- We loop through each "@quenty" or "@blah" and convert to a package
			if instance.Name:sub(1, 1) == "@" then
				local scope = instance.Name
				for _, child in pairs(instance:GetChildren()) do
					PackageInfoUtils.tryLoadPackageFromInstance(packageInfoList, packageInfoMap, child, scope)
				end
			else
				PackageInfoUtils.tryLoadPackageFromInstance(packageInfoList, packageInfoMap, instance, "")
			end
		else
			warn(("Unknown instance in dependencyFolder - %q"):format(instance:GetFullName()))
		end
	end

	return packageInfoList
end

function PackageInfoUtils.tryLoadPackageFromInstance(packageInfoList, packageInfoMap, instance, scope)
	assert(type(packageInfoList) == "table", "Bad packageInfoList")
	assert(type(packageInfoMap) == "table", "Bad packageInfoMap")
	assert(typeof(instance) == "Instance", "Bad instance")
	assert(type(scope) == "string", "Bad scope")

	if BounceTemplateUtils.isBounceTemplate(instance) then
		return
	end

	if instance:IsA("Folder") or instance:IsA("ModuleScript") then
		table.insert(packageInfoList, PackageInfoUtils.getOrCreatePackageInfo(instance, packageInfoMap, scope))
	elseif instance:IsA("ObjectValue") then
		local value = instance.Value
		if value and (value:IsA("Folder") or value:IsA("ModuleScript")) then
			table.insert(packageInfoList, PackageInfoUtils.getOrCreatePackageInfo(value, packageInfoMap, scope))
		else
			error(("Invalid %q ObjectValue in package linking to nothing cannot be resolved into package dependency\n\t-> %s")
				:format(instance.Name, instance:GetFullName()))
		end
	end
end
