local y=require(script.Parent.BounceTemplateUtils)local J=require(script.Parent.LoaderConstants)local K=require(script.Parent.Queue)local l=require(script.Parent.ScriptInfoUtils)local d=require(script.Parent.Utils)local C={}function C.createPackageInfo(y,J,K,l)assert(typeof(y)=="\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(J)=="\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(K)=="\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(l)=="\115\116\114\105\110\103","\066\097\100 \102\117\108\108\078\097\109\101")return d.readonly({name=y.Name,fullName=l,instance=y,explicitDependencySet=J;dependencySet=false;scriptInfoLookup=K})end function C.createDependencyQueueInfo(y,J)assert(type(y)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111")assert(type(J)=="\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 d.readonly({packageInfo=y,implicitDependencySet=J})end function C.fillDependencySet(y)assert(type(y)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\076\105\115\116")local J=K.new()local l={}do local K={}for y,d in pairs(y)do if not l[d]then K[d]=d l[d]=true J:PushRight(C.createDependencyQueueInfo(d,K))end end end while not J:IsEmpty()do local y=J:PopLeft()assert(not y.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 K=C.computePackageDependencySet(y.packageInfo,y.implicitDependencySet)y.packageInfo.dependencySet=K for y,d in pairs(y.packageInfo.explicitDependencySet)do if not l[y]then l[y]=true J:PushRight(C.createDependencyQueueInfo(y,K))end end end end function C.computePackageDependencySet(y,K)assert(type(y)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111")assert(type(K)=="\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 l={}if J.INCLUDE_IMPLICIT_DEPENDENCIES then for y,J in pairs(K)do l[y.name]=y end end for y,J in pairs(y.explicitDependencySet)do l[y.name]=y end l[y.name]=nil local d={}for y,J in pairs(l)do d[J]=true end return d end function C.getOrCreatePackageInfo(y,J,K)assert(typeof(y)=="\073\110\115\116\097\110\099\101"and(y:IsA("\070\111\108\100\101\114")or y: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(J)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")assert(type(K)=="\115\116\114\105\110\103","\066\097\100 \115\099\111\112\101")if J[y]then return J[y]end local d=l.createScriptInfoLookup()l.populateScriptInfoLookup(y,d,l.ModuleReplicationTypes.SHARED)local E={}local c if K==""then c=y.Name else c=K..("\047"..y.Name)end local D=C.createPackageInfo(y,E,d,c)J[y]=D C.fillExplicitPackageDependencySet(E,y,J)return D end function C.getPackageInfoListFromDependencyFolder(y,J)assert(typeof(y)=="\073\110\115\116\097\110\099\101"and y:IsA("\070\111\108\100\101\114"),"\066\097\100 \102\111\108\100\101\114")assert(type(J)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")local K={}for y,l in pairs(y:GetChildren())do if l:IsA("\070\111\108\100\101\114")then if l.Name:sub(1,1)=="\064"then local y=l.Name for l,d in pairs(l:GetChildren())do C.tryLoadPackageFromInstance(K,J,d,y)end else C.tryLoadPackageFromInstance(K,J,l,"")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(l:GetFullName()))end end return K end function C.tryLoadPackageFromInstance(J,K,l,d)assert(type(J)=="\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(K)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")assert(typeof(l)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")assert(type(d)=="\115\116\114\105\110\103","\066\097\100 \115\099\111\112\101")if y.isBounceTemplate(l)then return end if l:IsA("\070\111\108\100\101\114")or l:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then table.insert(J,C.getOrCreatePackageInfo(l,K,d))elseif l:IsA("\079\098\106\101\099\116\086\097\108\117\101")then local y=l.Value if y and(y:IsA("\070\111\108\100\101\114")or y:IsA("\077\111\100\117\108\101\083\099\114\105\112\116"))then table.insert(J,C.getOrCreatePackageInfo(y,K,d))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(l.Name,l:GetFullName()))end end end
--[=[
	Utility methods to build a virtual graph of the existing package information set
	@private
	@class PackageInfoUtils
]=]

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
