local g=require(script.Parent.BounceTemplateUtils)local R=require(script.Parent.LoaderConstants)local Z=require(script.Parent.Queue)local j=require(script.Parent.ScriptInfoUtils)local O=require(script.Parent.Utils)local w={}function w.createPackageInfo(g,R,Z,j)assert(typeof(g)=="\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(R)=="\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(Z)=="\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(j)=="\115\116\114\105\110\103","\066\097\100 \102\117\108\108\078\097\109\101")return O.readonly({name=g.Name,fullName=j;instance=g;explicitDependencySet=R,dependencySet=false,scriptInfoLookup=Z})end function w.createDependencyQueueInfo(g,R)assert(type(g)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111")assert(type(R)=="\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 O.readonly({packageInfo=g;implicitDependencySet=R})end function w.fillDependencySet(g)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")local R=Z.new()local j={}do local Z={}for g,O in pairs(g)do if not j[O]then Z[O]=O j[O]=true R:PushRight(w.createDependencyQueueInfo(O,Z))end end end while not R:IsEmpty()do local g=R:PopLeft()assert(not g.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 Z=w.computePackageDependencySet(g.packageInfo,g.implicitDependencySet)g.packageInfo.dependencySet=Z for g,O in pairs(g.packageInfo.explicitDependencySet)do if not j[g]then j[g]=true R:PushRight(w.createDependencyQueueInfo(g,Z))end end end end function w.computePackageDependencySet(g,Z)assert(type(g)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111")assert(type(Z)=="\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 j={}if R.INCLUDE_IMPLICIT_DEPENDENCIES then for g,R in pairs(Z)do j[g.name]=g end end for g,R in pairs(g.explicitDependencySet)do j[g.name]=g end j[g.name]=nil local O={}for g,R in pairs(j)do O[R]=true end return O end function w.getOrCreatePackageInfo(g,R,Z,O)assert(typeof(g)=="\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(R)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")assert(type(Z)=="\115\116\114\105\110\103","\066\097\100 \115\099\111\112\101")assert(O,"\078\111 \100\101\102\097\117\108\116\082\101\112\108\105\099\097\116\105\111\110\084\121\112\101")if R[g]then return R[g]end local h=j.createScriptInfoLookup()j.populateScriptInfoLookup(g,h,O)local U={}local i if Z==""then i=g.Name else i=Z..("\047"..g.Name)end local k=w.createPackageInfo(g,U,h,i)R[g]=k w.fillExplicitPackageDependencySet(U,g,R,O)return k end function w.getPackageInfoListFromDependencyFolder(g,R,Z)assert(typeof(g)=="\073\110\115\116\097\110\099\101"and g:IsA("\070\111\108\100\101\114"),"\066\097\100 \102\111\108\100\101\114")assert(type(R)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")assert(Z,"\078\111 \100\101\102\097\117\108\116\082\101\112\108\105\099\097\116\105\111\110\084\121\112\101")local j={}for g,O in pairs(g:GetChildren())do if O:IsA("\070\111\108\100\101\114")then if O.Name:sub(1,1)=="\064"then local g=O.Name for O,h in pairs(O:GetChildren())do w.tryLoadPackageFromInstance(j,R,h,g,Z)end else w.tryLoadPackageFromInstance(j,R,O,"",Z)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(O:GetFullName()))end end return j end function w.tryLoadPackageFromInstance(R,Z,j,O,h)assert(type(R)=="\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(Z)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\073\110\102\111\077\097\112")assert(typeof(j)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")assert(type(O)=="\115\116\114\105\110\103","\066\097\100 \115\099\111\112\101")assert(h,"\078\111 \100\101\102\097\117\108\116\082\101\112\108\105\099\097\116\105\111\110\084\121\112\101")if g.isBounceTemplate(j)then return end if j:IsA("\070\111\108\100\101\114")or j:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then table.insert(R,w.getOrCreatePackageInfo(j,Z,O,h))elseif j:IsA("\079\098\106\101\099\116\086\097\108\117\101")then local g=j.Value if g and(g:IsA("\070\111\108\100\101\114")or g:IsA("\077\111\100\117\108\101\083\099\114\105\112\116"))then table.insert(R,w.getOrCreatePackageInfo(g,Z,O,h))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(j.Name,j: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, defaultReplicationType)
	assert(typeof(packageFolder) == "Instance", "Bad packageFolder")
	assert(type(packageInfoMap) == "table", "Bad packageInfoMap")
	assert(type(scope) == "string", "Bad scope")
	assert(defaultReplicationType, "No defaultReplicationType")

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

	local scriptInfoLookup = ScriptInfoUtils.createScriptInfoLookup()
	ScriptInfoUtils.populateScriptInfoLookup(packageFolder, scriptInfoLookup, defaultReplicationType)

	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,
		defaultReplicationType
	)

	return packageInfo
end

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

	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,
						defaultReplicationType
					)
				end
			else
				PackageInfoUtils.tryLoadPackageFromInstance(
					packageInfoList,
					packageInfoMap,
					instance,
					"",
					defaultReplicationType
				)
			end
		else
			warn(("Unknown instance in dependencyFolder - %q"):format(instance:GetFullName()))
		end
	end

	return packageInfoList
end

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

	if BounceTemplateUtils.isBounceTemplate(instance) then
		return
	end

	if instance:IsA("Folder") or instance:IsA("ModuleScript") then
		table.insert(
			packageInfoList,
			PackageInfoUtils.getOrCreatePackageInfo(instance, packageInfoMap, scope, defaultReplicationType)
		)
	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, defaultReplicationType)
			)
		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
