local D={}D.ClassName="\077\097\105\100"function D.new()return setmetatable({_tasks={}},D)end function D.isMaid(D)return type(D)=="\116\097\098\108\101"and D.ClassName=="\077\097\105\100"end function D.__index(s,E)if D[E]then return D[E]else return s._tasks[E]end end function D.__newindex(d,E,s)if D[E]~=nil then error(("\067\097\110\110\111\116 \117\115\101 \'\037\115\' \097\115 \097 \077\097\105\100 \107\101\121"):format(tostring(E)),2)end local V=d._tasks local B=V[E]if B==s then return end V[E]=s if B then if type(B)=="\102\117\110\099\116\105\111\110"then B()elseif typeof(B)=="\082\066\088\083\099\114\105\112\116\067\111\110\110\101\099\116\105\111\110"then B:Disconnect()elseif B.Destroy then B:Destroy()end end end function D.GiveTask(E,D)if not D then error("\084\097\115\107 \099\097\110\110\111\116 \098\101 \102\097\108\115\101 \111\114 \110\105\108",2)end local s=#E._tasks+1 E[s]=D if type(D)=="\116\097\098\108\101"and not D.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 s end function D.GivePromise(E,D)if not D:IsPending()then return D end local s=D.resolved(D)local d=E:GiveTask(s)s:Finally(function()E[d]=nil end)return s end function D.DoCleaning(D)local E=D._tasks for D,s in pairs(E)do if typeof(s)=="\082\066\088\083\099\114\105\112\116\067\111\110\110\101\099\116\105\111\110"then E[D]=nil s:Disconnect()end end local s,d=next(E)while d~=nil do E[s]=nil if type(d)=="\102\117\110\099\116\105\111\110"then d()elseif typeof(d)=="\082\066\088\083\099\114\105\112\116\067\111\110\110\101\099\116\105\111\110"then d:Disconnect()elseif d.Destroy then d:Destroy()end s,d=next(E)end end D.Destroy=D.DoCleaning return D
--[=[
	Manages the cleaning of events and other things. Useful for
	encapsulating state and make deconstructors easy.

	See the [Five Powerful Code Patterns talk](https://developer.roblox.com/en-us/videos/5-powerful-code-patterns-behind-top-roblox-games)
	for a more in-depth look at Maids in top games.

	```lua
	local maid = Maid.new()

	maid:GiveTask(function()
		print("Cleaning up")
	end)

	maid:GiveTask(workspace.ChildAdded:Connect(print))

	-- Disconnects all events, and executes all functions
	maid:DoCleaning()
	```

	@class Maid
]=]
-- luacheck: pop

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

--[=[
	Constructs a new Maid object

	```lua
	local maid = Maid.new()
	```

	@return Maid
]=]
function Maid.new()
	return setmetatable({
		_tasks = {}
	}, Maid)
end

--[=[
	Returns true if the class is a maid, and false otherwise.

	```lua
	print(Maid.isMaid(Maid.new())) --> true
	print(Maid.isMaid(nil)) --> false
	```

	@param value any
	@return boolean
]=]
function Maid.isMaid(value)
	return type(value) == "table" and value.ClassName == "Maid"
end

--[=[
	Returns Maid[key] if not part of Maid metatable

	```lua
	local maid = Maid.new()
	maid._current = Instance.new("Part")
	print(maid._current) --> Part

	maid._current = nil
	print(maid._current) --> nil
	```

	@param index any
	@return MaidTask
]=]
function Maid:__index(index)
	if Maid[index] then
		return Maid[index]
	else
		return self._tasks[index]
	end
end

--[=[
	Add a task to clean up. Tasks given to a maid will be cleaned when
	maid[index] is set to a different value.

	Task cleanup is such that if the task is an event, it is disconnected.
	If it is an object, it is destroyed.

	```
	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.
	```

	@param index any
	@param newTask MaidTask
]=]
function Maid:__newindex(index, newTask)
	if Maid[index] ~= nil then
		error(("Cannot use '%s' as a Maid key"):format(tostring(index)), 2)
	end

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

	if oldTask == newTask then
		return
	end

	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

--[=[
	Gives a task to the maid for cleanup, but uses an incremented number as a key.

	@param task MaidTask -- An item to clean
	@return number -- taskId
]=]
function Maid:GiveTask(task)
	if not task then
		error("Task cannot be false or nil", 2)
	end

	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

--[=[
	Gives a promise to the maid for clean.

	@param promise Promise<T>
	@return Promise<T>
]=]
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 and removes them as entries from the Maid.

	:::note
	Signals that are already connected are always disconnected first. After that
	any signals added during a cleaning phase will be disconnected at random times.
	:::

	:::tip
	DoCleaning() may be recursively invoked. This allows the you to ensure that
	tasks or other tasks. Each task will be executed once.

	However, adding tasks while cleaning is not generally a good idea, as if you add a
	function that adds itself, this will loop indefinitely.
	:::
]=]
function Maid:DoCleaning()
	local tasks = self._tasks

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

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

--[=[
	Alias for [Maid.DoCleaning()](/api/Maid#DoCleaning)

	@function Destroy
	@within Maid
]=]
Maid.Destroy = Maid.DoCleaning

return Maid
