local W=script.Parent local Q=require(script.Parent.BounceTemplateUtils)local i=require(script.Parent.GroupInfoUtils)local f=require(script.Parent.PackageInfoUtils)local F=require(script.Parent.ScriptInfoUtils)local N=require(script.Parent.Utils)local y={}y.Utils=N y.ContextTypes=N.readonly({CLIENT="\099\108\105\101\110\116",SERVER="\115\101\114\118\101\114"})y.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 y.toWallyFormat(W,Q)assert(typeof(W)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")assert(type(Q)=="\098\111\111\108\101\097\110","\066\097\100 \105\115\080\108\117\103\105\110")local N={}y.discoverTopLevelPackages(N,W)y.injectLoader(N)local p={}local H={}local e=Q and F.ModuleReplicationTypes.PLUGIN or F.ModuleReplicationTypes.SHARED for W,Q in pairs(N)do local i=f.getOrCreatePackageInfo(Q,H,"",e)table.insert(p,i)end f.fillDependencySet(p)if Q then local W=i.groupPackageInfos(p,F.ModuleReplicationTypes.PLUGIN)local Q=y.getPublishPackageInfoSet(p)local f=Instance.new("\070\111\108\100\101\114")f.Name="\080\108\117\103\105\110\080\097\099\107\097\103\101\115"y.reifyGroupList(W,Q,f,F.ModuleReplicationTypes.PLUGIN)return f else local W=i.groupPackageInfos(p,F.ModuleReplicationTypes.CLIENT)local Q=i.groupPackageInfos(p,F.ModuleReplicationTypes.SERVER)local f=i.groupPackageInfos(p,F.ModuleReplicationTypes.SHARED)local N=y.getPublishPackageInfoSet(p)local H=Instance.new("\070\111\108\100\101\114")H.Name="\080\097\099\107\097\103\101\115"local e=Instance.new("\070\111\108\100\101\114")e.Name="\083\104\097\114\101\100\080\097\099\107\097\103\101\115"local V=Instance.new("\070\111\108\100\101\114")V.Name="\080\097\099\107\097\103\101\115"y.reifyGroupList(W,N,H,F.ModuleReplicationTypes.CLIENT)y.reifyGroupList(Q,N,V,F.ModuleReplicationTypes.SERVER)y.reifyGroupList(f,N,e,F.ModuleReplicationTypes.SHARED)return H,V,e end end function y.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,Q in pairs(W:GetChildren())do if Q:IsA("\070\111\108\100\101\114")then if Q.Name=="\083\101\114\118\101\114"or Q.Name=="\067\108\105\101\110\116"or Q.Name=="\083\104\097\114\101\100"or Q.Name==F.DEPENDENCY_FOLDER_NAME then return true end end end return false end function y.injectLoader(Q)for Q,i in pairs(Q)do if i==W or W:IsDescendantOf(i)then return end end table.insert(Q,W)end function y.discoverTopLevelPackages(W,Q)assert(type(W)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\115")assert(typeof(Q)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")if y.isPackage(Q)then table.insert(W,Q)elseif Q:IsA("\079\098\106\101\099\116\086\097\108\117\101")then local i=Q.Value if i and y.isPackage(i)then table.insert(W,i)end else for Q,i in pairs(Q:GetChildren())do if i:IsA("\070\111\108\100\101\114")then y.discoverTopLevelPackages(W,i)elseif i:IsA("\079\098\106\101\099\116\086\097\108\117\101")then local Q=i.Value if Q and y.isPackage(Q)then table.insert(W,Q)end elseif i:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then table.insert(W,i)end end end end function y.reifyGroupList(W,i,f,F)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(i)=="\116\097\098\108\101","\066\097\100 \112\117\098\108\105\115\104\083\101\116")assert(typeof(f)=="\073\110\115\116\097\110\099\101","\066\097\100 \112\097\114\101\110\116")assert(type(F)=="\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,Q in pairs(W)do if y.needToReify(Q,F)then y.reifyGroup(Q,N,F)end end for W,i in pairs(i)do for W,i in pairs(W.scriptInfoLookup[F])do local F=Q.create(i.instance,W)F.Parent=f end end N.Parent=f end function y.getPublishPackageInfoSet(W)local Q={}for W,i in pairs(W)do Q[i]=true for W,i in pairs(i.explicitDependencySet)do Q[W]=true end end return Q end function y.needToReify(W,Q)for W,i in pairs(W.packageScriptInfoMap)do if i.replicationMode==Q then return true end end return false end function y.reifyGroup(i,f,F)assert(type(i)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(typeof(f)=="\073\110\115\116\097\110\099\101","\066\097\100 \112\097\114\101\110\116")assert(type(F)=="\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(i.packageSet)).fullName,"\066\097\100 \112\097\099\107\097\103\101 \102\117\108\108\078\097\109\101")for i,f in pairs(i.packageScriptInfoMap)do assert(f.name==i,"\066\097\100 \115\099\114\105\112\116\073\110\102\111\046\110\097\109\101")if f.replicationMode==F then if f.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=Q.create(f.instance,i)W.Parent=N else f.instance.Name=i f.instance.Parent=N end else if f.instance==W then local W=Q.create(f.instance,i)W.Parent=N else local W=Q.createLink(f.instance,i)W.Parent=N end end end for f,F in pairs(i.scriptInfoMap)do assert(F.name==f,"\066\097\100 \115\099\114\105\112\116\073\110\102\111\046\110\097\109\101")if not i.packageScriptInfoMap[f]then if F.instance==W then local W=Q.create(F.instance,f)W.Parent=N else local W=Q.createLink(F.instance,f)W.Parent=N end end end N.Parent=f end return y


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
