local w=script.Parent local G=require(script.Parent.BounceTemplateUtils)local y=require(script.Parent.GroupInfoUtils)local g=require(script.Parent.PackageInfoUtils)local N=require(script.Parent.ScriptInfoUtils)local n=require(script.Parent.Utils)local I={}I.Utils=n I.ContextTypes=n.readonly({CLIENT="\099\108\105\101\110\116";SERVER="\115\101\114\118\101\114"})I.IncludeBehavior=n.readonly({NO_INCLUDE="\110\111\073\110\099\108\117\100\101",INCLUDE_ONLY="\105\110\099\108\117\100\101\079\110\108\121",INCLUDE_SHARED="\105\110\099\108\117\100\101\083\104\097\114\101\100"})function I.toWallyFormat(w,G)assert(typeof(w)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")assert(type(G)=="\098\111\111\108\101\097\110","\066\097\100 \105\115\080\108\117\103\105\110")local n={}I.discoverTopLevelPackages(n,w)I.injectLoader(n)local J={}local o={}local s=G and N.ModuleReplicationTypes.PLUGIN or N.ModuleReplicationTypes.SHARED for w,G in pairs(n)do local y=g.getOrCreatePackageInfo(G,o,"",s)table.insert(J,y)end g.fillDependencySet(J)if G then local w=y.groupPackageInfos(J,N.ModuleReplicationTypes.PLUGIN)local G=I.getPublishPackageInfoSet(J)local g=Instance.new("\070\111\108\100\101\114")g.Name="\080\108\117\103\105\110\080\097\099\107\097\103\101\115"I.reifyGroupList(w,G,g,N.ModuleReplicationTypes.PLUGIN)return g else local w=y.groupPackageInfos(J,N.ModuleReplicationTypes.CLIENT)local G=y.groupPackageInfos(J,N.ModuleReplicationTypes.SERVER)local g=y.groupPackageInfos(J,N.ModuleReplicationTypes.SHARED)local n=I.getPublishPackageInfoSet(J)local o=Instance.new("\070\111\108\100\101\114")o.Name="\080\097\099\107\097\103\101\115"local s=Instance.new("\070\111\108\100\101\114")s.Name="\083\104\097\114\101\100\080\097\099\107\097\103\101\115"local Q=Instance.new("\070\111\108\100\101\114")Q.Name="\080\097\099\107\097\103\101\115"I.reifyGroupList(w,n,o,N.ModuleReplicationTypes.CLIENT)I.reifyGroupList(G,n,Q,N.ModuleReplicationTypes.SERVER)I.reifyGroupList(g,n,s,N.ModuleReplicationTypes.SHARED)return o,Q,s end end function I.isPackage(w)assert(typeof(w)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")for w,G in pairs(w:GetChildren())do if G:IsA("\070\111\108\100\101\114")then if G.Name=="\083\101\114\118\101\114"or G.Name=="\067\108\105\101\110\116"or G.Name=="\083\104\097\114\101\100"or G.Name==N.DEPENDENCY_FOLDER_NAME then return true end end end return false end function I.injectLoader(G)for G,y in pairs(G)do if y==w or w:IsDescendantOf(y)then return end end table.insert(G,w)end function I.discoverTopLevelPackages(w,G)assert(type(w)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\115")assert(typeof(G)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")if I.isPackage(G)then table.insert(w,G)elseif G:IsA("\079\098\106\101\099\116\086\097\108\117\101")then local y=G.Value if y and I.isPackage(y)then table.insert(w,y)end else for G,y in pairs(G:GetChildren())do if y:IsA("\070\111\108\100\101\114")then I.discoverTopLevelPackages(w,y)elseif y:IsA("\079\098\106\101\099\116\086\097\108\117\101")then local G=y.Value if G and I.isPackage(G)then table.insert(w,G)end elseif y:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then table.insert(w,y)end end end end function I.reifyGroupList(w,y,g,N)assert(type(w)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111\076\105\115\116")assert(type(y)=="\116\097\098\108\101","\066\097\100 \112\117\098\108\105\115\104\083\101\116")assert(typeof(g)=="\073\110\115\116\097\110\099\101","\066\097\100 \112\097\114\101\110\116")assert(type(N)=="\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 n=Instance.new("\070\111\108\100\101\114")n.Name="\095\073\110\100\101\120"for w,G in pairs(w)do if I.needToReify(G,N)then I.reifyGroup(G,n,N)end end for w,y in pairs(y)do for w,y in pairs(w.scriptInfoLookup[N])do local N=G.create(y.instance,w)N.Parent=g end end n.Parent=g end function I.getPublishPackageInfoSet(w)local G={}for w,y in pairs(w)do G[y]=true for w,y in pairs(y.explicitDependencySet)do G[w]=true end end return G end function I.needToReify(w,G)for w,y in pairs(w.packageScriptInfoMap)do if y.replicationMode==G then return true end end return false end function I.reifyGroup(y,g,N)assert(type(y)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(typeof(g)=="\073\110\115\116\097\110\099\101","\066\097\100 \112\097\114\101\110\116")assert(type(N)=="\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 n=Instance.new("\070\111\108\100\101\114")n.Name=assert((next(y.packageSet)).fullName,"\066\097\100 \112\097\099\107\097\103\101 \102\117\108\108\078\097\109\101")for y,g in pairs(y.packageScriptInfoMap)do assert(g.name==y,"\066\097\100 \115\099\114\105\112\116\073\110\102\111\046\110\097\109\101")if g.replicationMode==N then if g.instance==w and w.Parent==game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")then local w=G.create(g.instance,y)w.Parent=n else g.instance.Name=y g.instance.Parent=n end else if g.instance==w then local w=G.create(g.instance,y)w.Parent=n else local w=G.createLink(g.instance,y)w.Parent=n end end end for g,N in pairs(y.scriptInfoMap)do assert(N.name==g,"\066\097\100 \115\099\114\105\112\116\073\110\102\111\046\110\097\109\101")if not y.packageScriptInfoMap[g]then if N.instance==w then local w=G.create(N.instance,g)w.Parent=n else local w=G.createLink(N.instance,g)w.Parent=n end end end n.Parent=g end return I
--[=[
	@private
	@class LoaderUtils
]=]

local loader = script.Parent
local BounceTemplateUtils = require(script.Parent.BounceTemplateUtils)
local GroupInfoUtils = require(script.Parent.GroupInfoUtils)
local PackageInfoUtils = require(script.Parent.PackageInfoUtils)
local ScriptInfoUtils = require(script.Parent.ScriptInfoUtils)
local Utils = require(script.Parent.Utils)

local LoaderUtils = {}
LoaderUtils.Utils = Utils -- TODO: Remove this

LoaderUtils.ContextTypes = Utils.readonly({
	CLIENT = "client",
	SERVER = "server",
})
LoaderUtils.IncludeBehavior = Utils.readonly({
	NO_INCLUDE = "noInclude",
	INCLUDE_ONLY = "includeOnly",
	INCLUDE_SHARED = "includeShared",
})

function LoaderUtils.toWallyFormat(instance, isPlugin)
	assert(typeof(instance) == "Instance", "Bad instance")
	assert(type(isPlugin) == "boolean", "Bad isPlugin")

	local topLevelPackages = {}
	LoaderUtils.discoverTopLevelPackages(topLevelPackages, instance)
	LoaderUtils.injectLoader(topLevelPackages)

	local packageInfoList = {}
	local packageInfoMap = {}
	local defaultReplicationType = isPlugin and ScriptInfoUtils.ModuleReplicationTypes.PLUGIN
		or ScriptInfoUtils.ModuleReplicationTypes.SHARED

	for _, folder in pairs(topLevelPackages) do
		local packageInfo = PackageInfoUtils.getOrCreatePackageInfo(folder, packageInfoMap, "", defaultReplicationType)
		table.insert(packageInfoList, packageInfo)
	end

	PackageInfoUtils.fillDependencySet(packageInfoList)

	if isPlugin then
		local pluginGroup =
			GroupInfoUtils.groupPackageInfos(packageInfoList, ScriptInfoUtils.ModuleReplicationTypes.PLUGIN)

		local publishSet = LoaderUtils.getPublishPackageInfoSet(packageInfoList)

		local pluginFolder = Instance.new("Folder")
		pluginFolder.Name = "PluginPackages"

		LoaderUtils.reifyGroupList(pluginGroup, publishSet, pluginFolder, ScriptInfoUtils.ModuleReplicationTypes.PLUGIN)

		return pluginFolder
	else
		local clientGroupList =
			GroupInfoUtils.groupPackageInfos(packageInfoList, ScriptInfoUtils.ModuleReplicationTypes.CLIENT)
		local serverGroupList =
			GroupInfoUtils.groupPackageInfos(packageInfoList, ScriptInfoUtils.ModuleReplicationTypes.SERVER)
		local sharedGroupList =
			GroupInfoUtils.groupPackageInfos(packageInfoList, ScriptInfoUtils.ModuleReplicationTypes.SHARED)

		local publishSet = LoaderUtils.getPublishPackageInfoSet(packageInfoList)

		local clientFolder = Instance.new("Folder")
		clientFolder.Name = "Packages"

		local sharedFolder = Instance.new("Folder")
		sharedFolder.Name = "SharedPackages"

		local serverFolder = Instance.new("Folder")
		serverFolder.Name = "Packages"

		LoaderUtils.reifyGroupList(
			clientGroupList,
			publishSet,
			clientFolder,
			ScriptInfoUtils.ModuleReplicationTypes.CLIENT
		)
		LoaderUtils.reifyGroupList(
			serverGroupList,
			publishSet,
			serverFolder,
			ScriptInfoUtils.ModuleReplicationTypes.SERVER
		)
		LoaderUtils.reifyGroupList(
			sharedGroupList,
			publishSet,
			sharedFolder,
			ScriptInfoUtils.ModuleReplicationTypes.SHARED
		)

		return clientFolder, serverFolder, sharedFolder
	end
end

function LoaderUtils.isPackage(folder)
	assert(typeof(folder) == "Instance", "Bad instance")

	for _, item in pairs(folder:GetChildren()) do
		if item:IsA("Folder") then
			if
				item.Name == "Server"
				or item.Name == "Client"
				or item.Name == "Shared"
				or item.Name == ScriptInfoUtils.DEPENDENCY_FOLDER_NAME
			then
				return true
			end
		end
	end

	return false
end

function LoaderUtils.injectLoader(topLevelPackages)
	for _, item in pairs(topLevelPackages) do
		-- If we're underneath the hierachy or if we're in the actual item...
		if item == loader or loader:IsDescendantOf(item) then
			return
		end
	end

	-- We need the loader injected!
	table.insert(topLevelPackages, loader)
end

function LoaderUtils.discoverTopLevelPackages(packages, instance)
	assert(type(packages) == "table", "Bad packages")
	assert(typeof(instance) == "Instance", "Bad instance")

	if LoaderUtils.isPackage(instance) then
		table.insert(packages, instance)
	elseif instance:IsA("ObjectValue") then
		local linkedValue = instance.Value
		if linkedValue and LoaderUtils.isPackage(linkedValue) then
			table.insert(packages, linkedValue)
		end
	else
		-- Loop through all folders
		for _, item in pairs(instance:GetChildren()) do
			if item:IsA("Folder") then
				LoaderUtils.discoverTopLevelPackages(packages, item)
			elseif item:IsA("ObjectValue") then
				local linkedValue = item.Value
				if linkedValue and LoaderUtils.isPackage(linkedValue) then
					table.insert(packages, linkedValue)
				end
			elseif item:IsA("ModuleScript") then
				table.insert(packages, item)
			end
		end
	end
end

function LoaderUtils.reifyGroupList(groupInfoList, publishSet, parent, replicationMode)
	assert(type(groupInfoList) == "table", "Bad groupInfoList")
	assert(type(publishSet) == "table", "Bad publishSet")
	assert(typeof(parent) == "Instance", "Bad parent")
	assert(type(replicationMode) == "string", "Bad replicationMode")

	local folder = Instance.new("Folder")
	folder.Name = "_Index"

	for _, groupInfo in pairs(groupInfoList) do
		if LoaderUtils.needToReify(groupInfo, replicationMode) then
			LoaderUtils.reifyGroup(groupInfo, folder, replicationMode)
		end
	end

	-- Publish
	for packageInfo, _ in pairs(publishSet) do
		for scriptName, scriptInfo in pairs(packageInfo.scriptInfoLookup[replicationMode]) do
			local link = BounceTemplateUtils.create(scriptInfo.instance, scriptName)
			link.Parent = parent
		end
	end

	folder.Parent = parent
end

function LoaderUtils.getPublishPackageInfoSet(packageInfoList)
	local packageInfoSet = {}
	for _, packageInfo in pairs(packageInfoList) do
		packageInfoSet[packageInfo] = true
		-- First level declared dependencies too (assuming we're importing just one item)
		for dependentPackageInfo, _ in pairs(packageInfo.explicitDependencySet) do
			packageInfoSet[dependentPackageInfo] = true
		end
	end
	return packageInfoSet
end

function LoaderUtils.needToReify(groupInfo, replicationMode)
	for _, scriptInfo in pairs(groupInfo.packageScriptInfoMap) do
		if scriptInfo.replicationMode == replicationMode then
			return true
		end
	end

	return false
end

function LoaderUtils.reifyGroup(groupInfo, parent, replicationMode)
	assert(type(groupInfo) == "table", "Bad groupInfo")
	assert(typeof(parent) == "Instance", "Bad parent")
	assert(type(replicationMode) == "string", "Bad replicationMode")

	local folder = Instance.new("Folder")
	folder.Name = assert(next(groupInfo.packageSet).fullName, "Bad package fullName")

	for scriptName, scriptInfo in pairs(groupInfo.packageScriptInfoMap) do
		assert(scriptInfo.name == scriptName, "Bad scriptInfo.name")

		if scriptInfo.replicationMode == replicationMode then
			if scriptInfo.instance == loader and loader.Parent == game:GetService("ReplicatedStorage") then
				-- Hack to prevent reparenting of loader in legacy mode
				local link = BounceTemplateUtils.create(scriptInfo.instance, scriptName)
				link.Parent = folder
			else
				scriptInfo.instance.Name = scriptName
				scriptInfo.instance.Parent = folder
			end
		else
			if scriptInfo.instance == loader then
				local link = BounceTemplateUtils.create(scriptInfo.instance, scriptName)
				link.Parent = folder
			else
				-- Turns out creating these links are a LOT faster than cloning a module script
				local link = BounceTemplateUtils.createLink(scriptInfo.instance, scriptName)
				link.Parent = folder
			end
		end
	end

	-- Link all of the other dependencies
	for scriptName, scriptInfo in pairs(groupInfo.scriptInfoMap) do
		assert(scriptInfo.name == scriptName, "Bad scriptInfo.name")

		if not groupInfo.packageScriptInfoMap[scriptName] then
			if scriptInfo.instance == loader then
				local link = BounceTemplateUtils.create(scriptInfo.instance, scriptName)
				link.Parent = folder
			else
				-- Turns out creating these links are a LOT faster than cloning a module script
				local link = BounceTemplateUtils.createLink(scriptInfo.instance, scriptName)
				link.Parent = folder
			end
		end
	end

	folder.Parent = parent
end

return LoaderUtils
