local L=script.Parent local C=require(script.Parent.BounceTemplateUtils)local t=require(script.Parent.GroupInfoUtils)local l=require(script.Parent.PackageInfoUtils)local g=require(script.Parent.ScriptInfoUtils)local I=require(script.Parent.Utils)local R={}R.Utils=I R.ContextTypes=I.readonly({CLIENT="\099\108\105\101\110\116";SERVER="\115\101\114\118\101\114"})R.IncludeBehavior=I.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 R.toWallyFormat(L)assert(typeof(L)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")local C={}R.discoverTopLevelPackages(C,L)R.injectLoader(C)local I={}local A={}for L,C in pairs(C)do local t=l.getOrCreatePackageInfo(C,A,"")table.insert(I,t)end l.fillDependencySet(I)local p=t.groupPackageInfos(I,g.ModuleReplicationTypes.CLIENT)local T=t.groupPackageInfos(I,g.ModuleReplicationTypes.SERVER)local K=t.groupPackageInfos(I,g.ModuleReplicationTypes.SHARED)local Q=R.getPublishPackageInfoSet(I)local O=Instance.new("\070\111\108\100\101\114")O.Name="\080\097\099\107\097\103\101\115"local z=Instance.new("\070\111\108\100\101\114")z.Name="\083\104\097\114\101\100\080\097\099\107\097\103\101\115"local B=Instance.new("\070\111\108\100\101\114")B.Name="\080\097\099\107\097\103\101\115"R.reifyGroupList(p,Q,O,g.ModuleReplicationTypes.CLIENT)R.reifyGroupList(T,Q,B,g.ModuleReplicationTypes.SERVER)R.reifyGroupList(K,Q,z,g.ModuleReplicationTypes.SHARED)return O,B,z end function R.isPackage(L)assert(typeof(L)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")for L,C in pairs(L:GetChildren())do if C:IsA("\070\111\108\100\101\114")then if C.Name=="\083\101\114\118\101\114"or C.Name=="\067\108\105\101\110\116"or C.Name=="\083\104\097\114\101\100"or C.Name==g.DEPENDENCY_FOLDER_NAME then return true end end end return false end function R.injectLoader(C)for C,t in pairs(C)do if t==L or L:IsDescendantOf(t)then return end end table.insert(C,L)end function R.discoverTopLevelPackages(L,C)assert(type(L)=="\116\097\098\108\101","\066\097\100 \112\097\099\107\097\103\101\115")assert(typeof(C)=="\073\110\115\116\097\110\099\101","\066\097\100 \105\110\115\116\097\110\099\101")if R.isPackage(C)then table.insert(L,C)else for C,t in pairs(C:GetChildren())do if t:IsA("\070\111\108\100\101\114")then R.discoverTopLevelPackages(L,t)elseif t:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then table.insert(L,t)end end end end function R.reifyGroupList(L,t,l,g)assert(type(L)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111\076\105\115\116")assert(type(t)=="\116\097\098\108\101","\066\097\100 \112\117\098\108\105\115\104\083\101\116")assert(typeof(l)=="\073\110\115\116\097\110\099\101","\066\097\100 \112\097\114\101\110\116")assert(type(g)=="\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 I=Instance.new("\070\111\108\100\101\114")I.Name="\095\073\110\100\101\120"for L,C in pairs(L)do if R.needToReify(C,g)then R.reifyGroup(C,I,g)end end for L,t in pairs(t)do for L,t in pairs(L.scriptInfoLookup[g])do local g=C.create(t.instance,L)g.Parent=l end end I.Parent=l end function R.getPublishPackageInfoSet(L)local C={}for L,t in pairs(L)do C[t]=true for L,t in pairs(t.explicitDependencySet)do C[L]=true end end return C end function R.needToReify(L,C)for L,t in pairs(L.packageScriptInfoMap)do if t.replicationMode==C then return true end end return false end function R.reifyGroup(t,l,g)assert(type(t)=="\116\097\098\108\101","\066\097\100 \103\114\111\117\112\073\110\102\111")assert(typeof(l)=="\073\110\115\116\097\110\099\101","\066\097\100 \112\097\114\101\110\116")assert(type(g)=="\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 I=Instance.new("\070\111\108\100\101\114")I.Name=assert((next(t.packageSet)).fullName,"\066\097\100 \112\097\099\107\097\103\101 \102\117\108\108\078\097\109\101")for t,l in pairs(t.packageScriptInfoMap)do assert(l.name==t,"\066\097\100 \115\099\114\105\112\116\073\110\102\111\046\110\097\109\101")if l.replicationMode==g then if l.instance==L and L.Parent==game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")then local L=C.create(l.instance,t)L.Parent=I else l.instance.Name=t l.instance.Parent=I end else if l.instance==L then local L=C.create(l.instance,t)L.Parent=I else local L=C.createLink(l.instance,t)L.Parent=I end end end for l,g in pairs(t.scriptInfoMap)do assert(g.name==l,"\066\097\100 \115\099\114\105\112\116\073\110\102\111\046\110\097\109\101")if not t.packageScriptInfoMap[l]then if g.instance==L then local L=C.create(g.instance,l)L.Parent=I else local L=C.createLink(g.instance,l)L.Parent=I end end end I.Parent=l end return R


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)
	assert(typeof(instance) == "Instance", "Bad instance")

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

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

	PackageInfoUtils.fillDependencySet(packageInfoList)

	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

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)
	else
		-- Loop through all folders
		for _, item in pairs(instance:GetChildren()) do
			if item:IsA("Folder") then
				LoaderUtils.discoverTopLevelPackages(packages, item)
			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
