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