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


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") or item:IsA("Camera") 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") or item:IsA("Camera") 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
