local x=game:GetService("\082\117\110\083\101\114\118\105\099\101")local X=x.Heartbeat local Z=newproxy(true);(getmetatable(Z)).__tostring=function()return"\073\110\100\105\099\101\115\082\101\102\101\114\101\110\099\101"end local m=newproxy(true);(getmetatable(m)).__tostring=function()return"\076\105\110\107\084\111\073\110\115\116\097\110\099\101\073\110\100\101\120"end local M="\079\098\106\101\099\116 \037\115 \100\111\101\115\110\'\116 \104\097\118\101 \109\101\116\104\111\100 \037\115\044 \097\114\101 \121\111\117 \115\117\114\101 \121\111\117 \119\097\110\116 \116\111 \097\100\100 \105\116\063 \084\114\097\099\101\098\097\099\107\058 \037\115"local c={ClassName="\074\097\110\105\116\111\114",__index={CurrentlyCleaning=true,[Z]=nil}}local I={["\102\117\110\099\116\105\111\110"]=true;RBXScriptConnection="\068\105\115\099\111\110\110\101\099\116"}function c.new()return setmetatable({CurrentlyCleaning=false,[Z]=nil},c)end function c.Is(x)return type(x)=="\116\097\098\108\101"and getmetatable(x)==c end c.is=c.Is function c.__index.Add(c,x,X,m)if m==nil then m=newproxy(false)end if m then c:Remove(m)local X=c[Z]if not X then X={}c[Z]=X end X[m]=x end X=X or I[typeof(x)]or"\068\101\115\116\114\111\121"if type(x)~="\102\117\110\099\116\105\111\110"and not x[X]then warn(string.format(M,tostring(x),tostring(X),debug.traceback(nil,2)))end c[x]=X return x,m end c.__index.Give=c.__index.Add function c.__index.AddObject(X,x)local Z=newproxy(false)return X:Add(x,false,Z),Z end c.__index.GiveObject=c.__index.AddObject function c.__index.Remove(X,x)local m=X[Z]if m then local Z=m[x]if Z then local M=X[Z]if M then if M==true then Z()else local x=Z[M]if x then x(Z)end end X[Z]=nil end m[x]=nil end end return X end function c.__index.Get(X,x)local m=X[Z]if m then return m[x]end return nil end function c.__index.Cleanup(x)if not x.CurrentlyCleaning then x.CurrentlyCleaning=nil for X,m in next,x do if X==Z then continue end local M=type(X)if M=="\115\116\114\105\110\103"or M=="\110\117\109\098\101\114"then x[X]=nil continue end if m==true then X()else local x=X[m]if x then x(X)end end x[X]=nil end local X=x[Z]if X then for x in next,X do X[x]=nil end x[Z]={}end x.CurrentlyCleaning=false end end c.__index.Clean=c.__index.Cleanup function c.__index.Destroy(x)x:Cleanup()end c.__call=c.__index.Cleanup local t={Connected=true}t.__index=t function t.Disconnect(x)if x.Connected then x.Connected=false x.Connection:Disconnect()end end function t.__tostring(x)return"\068\105\115\099\111\110\110\101\099\116\060"..(tostring(x.Connected).."\062")end function c.__index.LinkToInstance(M,x,Z)local c local I=Z and newproxy(false)or m local W=x.Parent==nil local A=setmetatable({},t)local function C(x,Z)if A.Connected then x=nil W=Z==nil if W then(coroutine.wrap(function()X:Wait()if not A.Connected then return elseif not c.Connected then M:Cleanup()else while W and(c.Connected and A.Connected)do X:Wait()end if A.Connected and W then M:Cleanup()end end end))()end end end c=x.AncestryChanged:Connect(C)A.Connection=c if W then C(nil,x.Parent)end x=nil return M:Add(A,"\068\105\115\099\111\110\110\101\099\116",I)end function c.__index.LinkToInstances(x,...)local X=c.new()for Z,m in ipairs({...})do X:Add(x:LinkToInstance(m,true),"\068\105\115\099\111\110\110\101\099\116")end return X end for x,X in next,c.__index do local Z=string.sub(string.lower(x),1,1)..string.sub(x,2)c.__index[Z]=X end return c
--@ignore
-- Janitor
-- Original by Validark
-- Modifications by pobammer
-- roblox-ts support by OverHash and Validark
-- LinkToInstance fixed by Elttob.

local RunService = game:GetService("RunService")
local Heartbeat = RunService.Heartbeat

local IndicesReference = newproxy(true)
getmetatable(IndicesReference).__tostring = function()
	return "IndicesReference"
end

local LinkToInstanceIndex = newproxy(true)
getmetatable(LinkToInstanceIndex).__tostring = function()
	return "LinkToInstanceIndex"
end

local METHOD_NOT_FOUND_ERROR =
	"Object %s doesn't have method %s, are you sure you want to add it? Traceback: %s"
--local NOT_A_PROMISE = "Invalid argument #1 to 'Janitor:AddPromise' (Promise expected, got %s (%s))"

local Janitor = {
	ClassName = "Janitor",
	__index = {
		CurrentlyCleaning = true,
		[IndicesReference] = nil,
	},
}

local TypeDefaults = {
	["function"] = true,
	RBXScriptConnection = "Disconnect",
}

--[[**
	Instantiates a new Janitor object.
	@returns [t:Janitor]
**--]]
function Janitor.new()
	return setmetatable({
		CurrentlyCleaning = false,
		[IndicesReference] = nil,
	}, Janitor)
end

function Janitor.Is(Object)
	return type(Object) == "table" and getmetatable(Object) == Janitor
end

Janitor.is = Janitor.Is

function Janitor.__index:Add(Object, MethodName, Index)
	if Index == nil then
		Index = newproxy(false)
	end

	if Index then
		self:Remove(Index)

		local This = self[IndicesReference]
		if not This then
			This = {}
			self[IndicesReference] = This
		end

		This[Index] = Object
	end
	--[[
	if Promise.is(Object) then
		local Id = newproxy(false)
		if Object:getStatus() == Promise.Status.Started then
			local NewPromise = self:Add(Promise.resolve(Object), "cancel", Id)
			NewPromise:finallyCall(self.Remove, self, Id)
			return NewPromise, Id
		else
			return Object
		end
	end--]]

	MethodName = MethodName or TypeDefaults[typeof(Object)] or "Destroy"
	if type(Object) ~= "function" and not Object[MethodName] then
		warn(
			string.format(
				METHOD_NOT_FOUND_ERROR,
				tostring(Object),
				tostring(MethodName),
				debug.traceback(nil, 2)
			)
		)
	end

	self[Object] = MethodName
	return Object, Index
end

Janitor.__index.Give = Janitor.__index.Add

-- This will assume whether or not the object is a Promise or a regular object.
function Janitor.__index:AddObject(Object)
	local Id = newproxy(false)
	return self:Add(Object, false, Id), Id
end

Janitor.__index.GiveObject = Janitor.__index.AddObject

function Janitor.__index:Remove(Index)
	local This = self[IndicesReference]

	if This then
		local Object = This[Index]

		if Object then
			local MethodName = self[Object]

			if MethodName then
				if MethodName == true then
					Object()
				else
					local ObjectMethod = Object[MethodName]
					if ObjectMethod then
						ObjectMethod(Object)
					end
				end

				self[Object] = nil
			end

			This[Index] = nil
		end
	end

	return self
end

function Janitor.__index:Get(Index)
	local This = self[IndicesReference]
	if This then
		return This[Index]
	end

	return nil
end

--[[**
	Calls each Object's `MethodName` (or calls the Object if `MethodName == true`) and removes them from the Janitor. Also clears the namespace. This function is also called when you call a Janitor Object (so it can be used as a destructor callback).
	@returns [t:void]
**--]]
function Janitor.__index:Cleanup()
	if not self.CurrentlyCleaning then
		self.CurrentlyCleaning = nil
		for Object, MethodName in next, self do
			if Object == IndicesReference then
				continue
			end

			-- Weird decision to rawset directly to the janitor in Agent. This should protect against it though.
			local TypeOf = type(Object)
			if TypeOf == "string" or TypeOf == "number" then
				self[Object] = nil
				continue
			end

			if MethodName == true then
				Object()
			else
				local ObjectMethod = Object[MethodName]
				if ObjectMethod then
					ObjectMethod(Object)
				end
			end

			self[Object] = nil
		end

		local This = self[IndicesReference]
		if This then
			for Index in next, This do
				This[Index] = nil
			end

			self[IndicesReference] = {}
		end

		self.CurrentlyCleaning = false
	end
end

Janitor.__index.Clean = Janitor.__index.Cleanup

--[[**
	Calls `:Cleanup()` and renders the Janitor unusable.
	@returns [t:void]
**--]]
function Janitor.__index:Destroy()
	self:Cleanup()
	--table.clear(self)
	--setmetatable(self, nil)
end

Janitor.__call = Janitor.__index.Cleanup

local Disconnect = { Connected = true }
Disconnect.__index = Disconnect
function Disconnect:Disconnect()
	if self.Connected then
		self.Connected = false
		self.Connection:Disconnect()
	end
end

function Disconnect:__tostring()
	return "Disconnect<" .. tostring(self.Connected) .. ">"
end

function Janitor.__index:LinkToInstance(Object, AllowMultiple)
	local Connection
	local IndexToUse = AllowMultiple and newproxy(false) or LinkToInstanceIndex
	local IsNilParented = Object.Parent == nil
	local ManualDisconnect = setmetatable({}, Disconnect)

	local function ChangedFunction(_DoNotUse, NewParent)
		if ManualDisconnect.Connected then
			_DoNotUse = nil
			IsNilParented = NewParent == nil

			if IsNilParented then
				coroutine.wrap(function()
					Heartbeat:Wait()
					if not ManualDisconnect.Connected then
						return
					elseif not Connection.Connected then
						self:Cleanup()
					else
						while
							IsNilParented
							and Connection.Connected
							and ManualDisconnect.Connected
						do
							Heartbeat:Wait()
						end

						if ManualDisconnect.Connected and IsNilParented then
							self:Cleanup()
						end
					end
				end)()
			end
		end
	end

	Connection = Object.AncestryChanged:Connect(ChangedFunction)
	ManualDisconnect.Connection = Connection

	if IsNilParented then
		ChangedFunction(nil, Object.Parent)
	end

	Object = nil
	return self:Add(ManualDisconnect, "Disconnect", IndexToUse)
end

function Janitor.__index:LinkToInstances(...)
	local ManualCleanup = Janitor.new()
	for _, Object in ipairs({ ... }) do
		ManualCleanup:Add(self:LinkToInstance(Object, true), "Disconnect")
	end

	return ManualCleanup
end

for FunctionName, Function in next, Janitor.__index do
	local NewFunctionName = string.sub(string.lower(FunctionName), 1, 1)
		.. string.sub(FunctionName, 2)
	Janitor.__index[NewFunctionName] = Function
end

return Janitor
