local h=game:GetService("\067\104\097\110\103\101\072\105\115\116\111\114\121\083\101\114\118\105\099\101")local z=game:GetService("\067\111\108\108\101\099\116\105\111\110\083\101\114\118\105\099\101")local r=game:GetService("\082\117\110\083\101\114\118\105\099\101")local k=game:GetService("\083\101\114\118\101\114\083\116\111\114\097\103\101")local P=require(script.Parent.Constants)local x=require(script.Parent.Util)local w=P.MaxLayers local D="\095\108\097\121\101\114\095\037\115\095\037\115"local t if k:FindFirstChild("\095\108\097\121\101\114\115")then t=k._layers end local function X()if not k:FindFirstChild("\095\108\097\121\101\114\115")then t=Instance.new("\070\111\108\100\101\114")t.Name="\095\108\097\121\101\114\115"t.Parent=k end end local O={Enabled=false,Stack={},Lookup={},numberLayers=0,currentLayer=0}function O._UpdateStack(h)for z,r in pairs(h.Stack)do r.Id=z for r,k in pairs(r.Children)do h:AddChild(z,k.Part)end end h.numberLayers=#h.Stack h.currentLayer=#h.Stack end function O._SaveChildren(z,h)local r=z.Stack[h]if r then for h,z in pairs(r)do if h:IsA("\066\097\115\101\080\097\114\116")then z.Properties.Anchored=h.Anchored z.Properties.Locked=h.Locked end z.Properties.Parent=h.Parent end end end function O._UpdateTag(r,h)local k=r.Stack[h.Id]for r,k in pairs(z:GetTags(h.Part))do if k:find("\095\108\097\121\101\114")then z:RemoveTag(h.Part,k)end end z:AddTag(h.Part,"\095\108\097\121\101\114\101\100")z:AddTag(h.Part,D:format(k.Id,k.Name))if not k.Properties.Visible then if h.Properties.Parent then if h.Properties.Parent.Parent then z:AddTag(h.Part,"\095\108\097\121\101\114\080\097\114\101\110\116\095"..h.Properties.Parent:GetFullName())end end end end function O._UpdateChildren(P,r,k)local x=P.Stack[r]if x then h:SetWaypoint("\095\101\100\105\116\076\097\121\101\114\115"..tick())for h,w in pairs(x.Children)do if h.Parent==nil or z:HasTag(h,"\095\108\097\121\101\114\101\100")==false then P:RemoveChild(r,h)elseif k=="\086\105\115\105\098\108\101"then if x.Properties.Visible==false then X()w.Properties.Parent=h.Parent h.Parent=t elseif x.Properties.Visible==true then P:_UpdateTag(w)if w.Properties.Parent~=nil then if w.Properties.Parent.Parent~=nil then h.Parent=w.Properties.Parent else h.Parent=workspace end else h.Parent=workspace end if#t:GetChildren()==0 then t:Destroy()end end elseif k=="\076\111\099\107\101\100"then if h:IsA("\066\097\115\101\080\097\114\116")then if x.Properties.Locked then w.Properties.Locked=h.Locked end h.Locked=w.Properties.Locked or x.Properties.Locked end end P:_UpdateTag(w)end h:SetWaypoint("\095\101\100\105\116\076\097\121\101\114\115\070\105\110\105\115\104\101\100"..tick())end end function O._GetProperties(z,h)local r={"\065\110\099\104\111\114\101\100";"\076\111\099\107\101\100";"\080\097\114\101\110\116"}local k={}for z,r in pairs(r)do pcall(function()k[r]=h[r]end)end return k end function O.AddChild(k,h,r)if not x.ValidSelection({r})then return false end local P=k.Stack[h]if P then if k.Lookup[r]then k:RemoveChild(k.Lookup[r].Id,r)else k:ResetChild(r)end local x={Id=h,Part=r,Properties=k:_GetProperties(r)}z:AddTag(r,"\095\108\097\121\101\114\101\100")z:AddTag(r,D:format(h,P.Name))if not P.Properties.Visible then X()z:AddTag(r,"\095\108\097\121\101\114\080\097\114\101\110\116\095"..r.Parent:GetFullName())r.Parent=t end if r:IsA("\066\097\115\101\080\097\114\116")then r.Locked=r.Locked or P.Properties.Locked end P.Children[r]=x k.Lookup[r]=x return k.numberLayers end end function O.RemoveChild(r,h,z)local k=r.Stack[h]if k then r:ResetChild(z)k.Children[z]=nil end end function O.ResetChild(r,h)for r,k in pairs(z:GetTags(h))do if k:find("\095\108\097\121\101\114")then z:RemoveTag(h,k)end end local k=r.Lookup[h]if k then if h:IsA("\066\097\115\101\080\097\114\116")then h.Locked=k.Properties.Locked end if h.Parent~=nil then if k.Properties.Parent then if k.Properties.Parent.Parent then h.Parent=k.Properties.Parent end else h.Parent=workspace end end r.Lookup[h]=nil end end function O.AddChildren(r,h,z)local k=r.Stack[h]if k then for z,k in pairs(z)do r:AddChild(h,k)end end end function O.New(z,h)if z.numberLayers+1>w then return false end z.numberLayers=z.numberLayers+1 z.currentLayer=z.currentLayer+1 if not h then h="\076\097\121\101\114 "..z.numberLayers end local r={Id=z.numberLayers,Name=h;Properties={Visible=true;Locked=false},Children={}}table.insert(z.Stack,r)return z.currentLayer end function O.Edit(r,h,z)local k=r.Stack[h]if k then if z.Name then k.Name=z.Name end for h,z in pairs(z)do if k.Properties[h]~=nil then k.Properties[h]=z end end r:_UpdateChildren(h)end end function O.ToggleProperty(r,h,z)local k=r.Stack[h]if k then k.Properties[z]=not k.Properties[z]r:_UpdateChildren(h,z)end end function O.Remove(z,h)if z.numberLayers-1==0 then return false end local r=z.Stack[h]if r then for h,r in pairs(r.Children)do z:AddChild(1,h)end table.remove(z.Stack,h)z:_UpdateStack()return z.currentLayer end end function O.Move(r,h,z)local k=h+z if k<=1 then return end if not r.Stack[k]then return end local P=unpack({r.Stack[k]})r.Stack[k]=r.Stack[h]r.Stack[h]=P r:_UpdateStack()end function O.SetCurrentLayer(z,h)if z.Stack[h]then z.currentLayer=h end end function O.SetEnabled(z,h)z.Enabled=h end function O.Init(h)if r:IsEdit()then h:SetEnabled(true)h:New("\068\101\102\097\117\108\116")local r=z:GetTagged("\095\108\097\121\101\114\101\100")local k={}local P={}local D={}for r,x in pairs(r)do local w=z:GetTags(x)for z,r in pairs(w)do if r:find("\108\097\121\101\114\095")then local z=r:split("\095")local w=tonumber(z[3])local D=z[4]if w==1 and D=="\068\101\102\097\117\108\116"then h:AddChild(1,x)continue end if k[w]then if k[w]~=D then w=#k+1 end end k[w]=D if not P[w]then P[w]={x}else table.insert(P[w],x)end end if r:find("\095\108\097\121\101\114\080\097\114\101\110\116")then local h=(r:split("\095\108\097\121\101\114\080\097\114\101\110\116\095"))[2]:split("\046")local z=game local k=true for r=1,#h,1 do z=z:FindFirstChild(h[r])if not z then k=false break end end D[x]=k and z or workspace end end end local X={}local O={}local Z=1 for h,z in pairs(k)do Z=Z+1 X[Z]=z O[Z]=P[h]end for z,r in pairs(X)do if z>1 and z<=w then h:New(r)end local k=O[z][1].Parent if t then if k==t then h:ToggleProperty(z,"\086\105\115\105\098\108\101")end end local P=O[z]h:AddChildren(z,P)for z,r in pairs(P)do if D[r]then local z=h.Lookup[r]z.Properties.Parent=D[r]end end end workspace.DescendantAdded:Connect(function(z)if h.Enabled then if x.ValidSelection({z})then if not h.Lookup[z]then task.defer(function()h:AddChild(h.currentLayer,z)end)end end end end)workspace.DescendantRemoving:Connect(function(z)if h.Lookup[z]then if z.Parent==nil then h:RemoveChild(h.Lookup[z].Id,z)h.Lookup[z]=nil end end end)end end return O
local ChangeHistoryService = game:GetService("ChangeHistoryService")
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local ServerStorage = game:GetService("ServerStorage")

local Constants = require(script.Parent.Constants)
local Util = require(script.Parent.Util)

local MAX_LAYERS = Constants.MaxLayers
local TAG_FORMAT = "_layer_%s_%s"

local ParentFolder;
if ServerStorage:FindFirstChild("_layers") then
	ParentFolder = ServerStorage._layers
end

local function makeParentFolder()
	if not ServerStorage:FindFirstChild("_layers") then
		ParentFolder = Instance.new("Folder")
		ParentFolder.Name = "_layers"
		ParentFolder.Parent = ServerStorage
	end
end

local Layers = {
	Enabled = false,
	Stack = {},
	Lookup = {},
	numberLayers = 0,
	currentLayer = 0
}

-- Private
function Layers:_UpdateStack()
	for index, layer in pairs(self.Stack) do
		layer.Id = index
		for _, child in pairs(layer.Children) do
			self:AddChild(index, child.Part)
		end
	end
	
	self.numberLayers = #self.Stack
	self.currentLayer = #self.Stack
end

function Layers:_SaveChildren(layerId)
	local layer = self.Stack[layerId]

	if layer then
		for instance, child in pairs(layer) do
			if instance:IsA("BasePart") then
				child.Properties.Anchored = instance.Anchored
				child.Properties.Locked = instance.Locked
			end
			child.Properties.Parent = instance.Parent
			--child.Properties.Transparency = instance.Transparency
		end
	end
end

function Layers:_UpdateTag(child)
	local layer = self.Stack[child.Id]

	for _, tag in pairs(CollectionService:GetTags(child.Part)) do
		if tag:find("_layer") then
			CollectionService:RemoveTag(child.Part, tag)
		end
	end

	CollectionService:AddTag(child.Part, "_layered")
	CollectionService:AddTag(child.Part, TAG_FORMAT:format(layer.Id, layer.Name))

	if not layer.Properties.Visible then
		if child.Properties.Parent then
			if child.Properties.Parent.Parent then
				CollectionService:AddTag(child.Part, "_layerParent_" .. child.Properties.Parent:GetFullName())
			end
		end
	end
end

function Layers:_UpdateChildren(layerId, property)
	local layer = self.Stack[layerId]
	
	if layer then
		ChangeHistoryService:SetWaypoint("_editLayers" .. tick())
		for instance, child in pairs(layer.Children) do

			if instance.Parent == nil or CollectionService:HasTag(instance, "_layered") == false then
				self:RemoveChild(layerId, instance)
			elseif property == "Visible" then
				if layer.Properties.Visible == false then

					makeParentFolder()

					child.Properties.Parent = instance.Parent
					instance.Parent = ParentFolder

				elseif layer.Properties.Visible == true then

					self:_UpdateTag(child)

					if child.Properties.Parent ~= nil then
						if child.Properties.Parent.Parent ~= nil then
							instance.Parent = child.Properties.Parent
						else
							instance.Parent = workspace
						end
					else
						instance.Parent = workspace
					end

					if #ParentFolder:GetChildren() == 0 then
						ParentFolder:Destroy()
					end
				end
			elseif property == "Locked" then
				if instance:IsA("BasePart") then
					if layer.Properties.Locked then
						child.Properties.Locked = instance.Locked
					end
					instance.Locked = child.Properties.Locked or layer.Properties.Locked
				end
			end

			self:_UpdateTag(child)
		end
		ChangeHistoryService:SetWaypoint("_editLayersFinished" .. tick())
	end
end

function Layers:_GetProperties(instance)
	local match = {"Anchored", "Locked", "Parent"}
	local properties = {}
	
	for _, property in pairs(match) do
		pcall(function()
			properties[property] = instance[property]
		end)
	end
	
	return properties
end

-- Public
function Layers:AddChild(layerId, instance)
	
	if not Util.ValidSelection({instance}) then
		return false
	end
	
	local layer = self.Stack[layerId]
	
	if layer then
		
		-- sanitize the instance
		if self.Lookup[instance] then
			self:RemoveChild(self.Lookup[instance].Id, instance)
		else
			self:ResetChild(instance)
		end
		
		local Child = {
			Id = layerId,
			Part = instance,
			Properties = self:_GetProperties(instance)
		}
		
		CollectionService:AddTag(instance, "_layered")
		CollectionService:AddTag(instance, TAG_FORMAT:format(layerId, layer.Name))
		
		if not layer.Properties.Visible then
			makeParentFolder()
			CollectionService:AddTag(instance, "_layerParent_" .. instance.Parent:GetFullName())
			instance.Parent = ParentFolder
		end

		if instance:IsA("BasePart") then
			instance.Locked = instance.Locked or layer.Properties.Locked
			--instance.Transparency = (layer.Properties.Visible and instance.Transparency or 1)
			--instance.Anchored = (layer.Properties.Visible and instance.Anchored or true)
		end
		
		layer.Children[instance] = Child
		self.Lookup[instance] = Child

		return self.numberLayers
		
	end
end

function Layers:RemoveChild(layerId, instance)
	local layer = self.Stack[layerId]
	
	if layer then
		self:ResetChild(instance)
		layer.Children[instance] = nil
	end
end

function Layers:ResetChild(instance)
	-- remove tags
	for _, tag in pairs(CollectionService:GetTags(instance)) do
		if tag:find("_layer") then
			CollectionService:RemoveTag(instance, tag)
		end
	end

	local Child = self.Lookup[instance]
	
	if Child then
		-- reset properties
		if instance:IsA("BasePart") then
			instance.Locked = Child.Properties.Locked
		end

		if instance.Parent ~= nil then
			if Child.Properties.Parent then
				if Child.Properties.Parent.Parent then
					instance.Parent = Child.Properties.Parent
					--instance.Parent = workspace
				end
			else
				instance.Parent = workspace
			end
		end
		--instance.Transparency = Child.Properties.Transparency
		
		-- remove lookup
		self.Lookup[instance] = nil
	end
end

function Layers:AddChildren(layerId, children)
	local layer = self.Stack[layerId]

	if layer then
		for _, instance in pairs(children) do
			self:AddChild(layerId, instance)
		end
	end
end

function Layers:New(name)
	
	if self.numberLayers + 1 > MAX_LAYERS then
		return false
	end
	
	self.numberLayers = self.numberLayers + 1
	self.currentLayer = self.currentLayer + 1
	
	if not name then
		name = "Layer " .. self.numberLayers
	end
	
	local Layer = {
		Id = self.numberLayers,
		Name = name,
		Properties = {
			Visible = true,
			Locked = false
		},
		Children = {}
	}
	
	table.insert(self.Stack, Layer)
	
	return self.currentLayer
	
end

function Layers:Edit(layerId, newProperties)
	local layer = self.Stack[layerId]
	
	if layer then

		if newProperties["Name"] then
			layer.Name = newProperties.Name
		end

		for key, value in pairs(newProperties) do
			if layer.Properties[key] ~= nil then
				layer.Properties[key] = value
			end
		end

		self:_UpdateChildren(layerId)
	end
end

function Layers:ToggleProperty(layerId, property)
	local layer = self.Stack[layerId]
	
	if layer then
		layer.Properties[property] = not layer.Properties[property]
		self:_UpdateChildren(layerId, property)
	end
end

function Layers:Remove(layerId)
	
	if self.numberLayers - 1 == 0 then
		return false
	end
	
	local layer = self.Stack[layerId]
	if layer then
		
		-- move children to the default layer
		for instance, _ in pairs(layer.Children) do
			self:AddChild(1, instance)
		end
		
		-- remove the layer
		table.remove(self.Stack, layerId)
		
		-- update the stack
		self:_UpdateStack()

		return self.currentLayer

	end
	
end

function Layers:Move(layerId, direction)
	local newId = layerId + direction

	if newId <= 1 then
		return
	end

	-- only update if there's a layer to switch with
	if not self.Stack[newId] then
		return
	end

	local copy = unpack({self.Stack[newId]})
	self.Stack[newId] = self.Stack[layerId]
	self.Stack[layerId] = copy

	self:_UpdateStack()
	
end

function Layers:SetCurrentLayer(layerId)
	if self.Stack[layerId] then
		self.currentLayer = layerId
	end
end

function Layers:SetEnabled(Enabled)
	self.Enabled = Enabled
end

function Layers:Init()
	if RunService:IsEdit() then

		self:SetEnabled(true)
		self:New("Default")

		local tagged = CollectionService:GetTagged("_layered")
		local hashmap = {}
		local correspondingTagsMap = {}
		local parentMap = {}

		for _, object in pairs(tagged) do
			local tags = CollectionService:GetTags(object)
			for _, tag in pairs(tags) do
				if tag:find("layer_") then
					local data = tag:split("_")
					local id = tonumber(data[3])
					local name = data[4]

					if id == 1 and name == "Default" then
						self:AddChild(1, object)
						continue
					end

					-- prevent conflicting layers
					if hashmap[id] then
						if hashmap[id] ~= name then
							id = #hashmap + 1
						end
					end

					hashmap[id] = name
					if not correspondingTagsMap[id] then
						correspondingTagsMap[id] = {object}
					else
						table.insert(correspondingTagsMap[id], object)
					end
				end

				if tag:find("_layerParent") then
					-- this is a terrible temporary solution
					local pathTable = tag:split("_layerParent_")[2]:split(".")
					local path = game
					local valid = true

					for i = 1, #pathTable do
						path = path:FindFirstChild(pathTable[i])
						if not path then
							valid = false
							break
						end
					end

					parentMap[object] = valid and path or workspace

				end
			end
		end

		local sorted = {}
		local correspondingTags = {}
		local index = 1
		for id, name in pairs(hashmap) do
			index = index + 1
			sorted[index] = name
			correspondingTags[index] = correspondingTagsMap[id]
		end

		for id, name in pairs(sorted) do
			if id > 1 and id <= MAX_LAYERS then
				self:New(name)
			end

			local Parent = correspondingTags[id][1].Parent
			if ParentFolder then
				if Parent == ParentFolder then
					self:ToggleProperty(id, "Visible")
				end
			end

			local children = correspondingTags[id]
			self:AddChildren(id, children)

			for _, obj in pairs(children) do
				if parentMap[obj] then
					local child = self.Lookup[obj]
					child.Properties.Parent = parentMap[obj]
				end
			end
		end

		workspace.DescendantAdded:Connect(function(child)
			if self.Enabled then
				if Util.ValidSelection({child}) then
					if not self.Lookup[child] then
						task.defer(function()
							self:AddChild(self.currentLayer, child)
						end)
					end
				end
			end
		end)

		-- if destroyed parts aren't removed, you get weird Selection related issues
		-- also keeping references to destroyed parts causes memory leaks
		workspace.DescendantRemoving:Connect(function(child)
			if self.Lookup[child] then
				if child.Parent == nil then
					self:RemoveChild(self.Lookup[child].Id, child)
					self.Lookup[child] = nil
				end
			end
		end)
		
	end
end

return Layers