local J={}J.ClassName="\077\097\105\100"function J.new()local T={}T._tasks={}return setmetatable(T,J)end function J.__index(c,T)if J[T]then return J[T]else return c._tasks[T]end end function J.__newindex(V,T,c)if J[T]~=nil then error(("\'\037\115\' \105\115 \114\101\115\101\114\118\101\100"):format(tostring(T)),2)end local M=V._tasks local v=M[T]M[T]=c if v then if type(v)=="\102\117\110\099\116\105\111\110"then v()elseif typeof(v)=="\082\066\088\083\099\114\105\112\116\067\111\110\110\101\099\116\105\111\110"then v:Disconnect()elseif v.Destroy then v:Destroy()end end end function J.GiveTask(T,J)assert(J,"\084\097\115\107 \099\097\110\110\111\116 \098\101 \102\097\108\115\101 \111\114 \110\105\108")local c=#T._tasks+1 T[c]=J if type(J)=="\116\097\098\108\101"and not J.Destroy then warn("\091\077\097\105\100\046\071\105\118\101\084\097\115\107\093 - \071\097\118\101 \116\097\098\108\101 \116\097\115\107 \119\105\116\104\111\117\116 \046\068\101\115\116\114\111\121\n\n"..debug.traceback())end return c end function J.GivePromise(T,J)if not J:IsPending()then return J end local c=J.resolved(J)local V=T:GiveTask(c)c:Finally(function()T[V]=nil end)return c end function J.DoCleaning(J)local T=J._tasks for J,c in pairs(T)do if typeof(c)=="\082\066\088\083\099\114\105\112\116\067\111\110\110\101\099\116\105\111\110"then T[J]=nil c:Disconnect()end end local c,V=next(T)while V~=nil do T[c]=nil if type(V)=="\102\117\110\099\116\105\111\110"then V()elseif typeof(V)=="\082\066\088\083\099\114\105\112\116\067\111\110\110\101\099\116\105\111\110"then V:Disconnect()elseif V.Destroy then V:Destroy()end c,V=next(T)end end J.Destroy=J.DoCleaning return J
-- Manages the cleaning of events and other things.
-- Useful for encapsulating state and make deconstructors easy
-- @classmod Maid
-- @see Signal

local Maid = {}
Maid.ClassName = "Maid"

--- Returns a new Maid object
-- @constructor Maid.new()
-- @treturn Maid
function Maid.new()
	local self = {}

	self._tasks = {}

	return setmetatable(self, Maid)
end

--- Returns Maid[key] if not part of Maid metatable
-- @return Maid[key] value
function Maid:__index(index)
	if Maid[index] then
		return Maid[index]
	else
		return self._tasks[index]
	end
end

--- Add a task to clean up
-- @usage
-- Maid[key] = (function)         Adds a task to perform
-- Maid[key] = (event connection) Manages an event connection
-- Maid[key] = (Maid)             Maids can act as an event connection, allowing a Maid to have other maids to clean up.
-- Maid[key] = (Object)           Maids can cleanup objects with a `Destroy` method
-- Maid[key] = nil                Removes a named task. If the task is an event, it is disconnected. If it is an object,
--                                it is destroyed.
function Maid:__newindex(index, newTask)
	if Maid[index] ~= nil then
		error(("'%s' is reserved"):format(tostring(index)), 2)
	end

	local tasks = self._tasks
	local oldTask = tasks[index]
	tasks[index] = newTask

	if oldTask then
		if type(oldTask) == "function" then
			oldTask()
		elseif typeof(oldTask) == "RBXScriptConnection" then
			oldTask:Disconnect()
		elseif oldTask.Destroy then
			oldTask:Destroy()
		end
	end
end

--- Same as indexing, but uses an incremented number as a key.
-- @param task An item to clean
-- @treturn number taskId
function Maid:GiveTask(task)
	assert(task, "Task cannot be false or nil")

	local taskId = #self._tasks + 1
	self[taskId] = task

	if type(task) == "table" and not task.Destroy then
		warn("[Maid.GiveTask] - Gave table task without .Destroy\n\n" .. debug.traceback())
	end

	return taskId
end

function Maid:GivePromise(promise)
	if not promise:IsPending() then
		return promise
	end

	local newPromise = promise.resolved(promise)
	local id = self:GiveTask(newPromise)

	-- Ensure GC
	newPromise:Finally(function()
		self[id] = nil
	end)

	return newPromise
end

--- Cleans up all tasks.
-- @alias Destroy
function Maid:DoCleaning()
	local tasks = self._tasks

	-- Disconnect all events first as we know this is safe
	for index, task in pairs(tasks) do
		if typeof(task) == "RBXScriptConnection" then
			tasks[index] = nil
			task:Disconnect()
		end
	end

	-- Clear out tasks table completely, even if clean up tasks add more tasks to the maid
	local index, task = next(tasks)
	while task ~= nil do
		tasks[index] = nil
		if type(task) == "function" then
			task()
		elseif typeof(task) == "RBXScriptConnection" then
			task:Disconnect()
		elseif task.Destroy then
			task:Destroy()
		end
		index, task = next(tasks)
	end
end

--- Alias for DoCleaning()
-- @function Destroy
Maid.Destroy = Maid.DoCleaning

return Maid
