
local module = {}

local running = coroutine.running
local resume = coroutine.resume
local yield = coroutine.yield
local insert = table.insert
local remove = table.remove

function module.init(shared)
	---@class quad_module_signal
	local new = {}
	local warn = shared.warn

	-- roblox connections disconnecter
	local disconnecters = {}
	new.disconnecters = disconnecters
	local disconnecterClass = {__type = "quad_disconnecter"}
	disconnecterClass.__index = disconnecterClass
	function disconnecterClass:Add(connection)
		insert(self,connection)
	end
	function disconnecterClass:Disconnect()
		for i,v in pairs(self) do
			pcall(v.Disconnect,v)
			self[i] = nil
		end
	end
	function disconnecterClass:Destroy()
		local id = self.id
		if id then
			disconnecters[id] = nil
		end
		for i,v in pairs(self) do
			pcall(v.Disconnect,v)
			self[i] = nil
		end
		setmetatable(self,nil)
	end
	function disconnecterClass.New(id)
		if id then
			local old = disconnecters[id];
			if old then
				return old;
			end
		end
		local this = setmetatable({id = id},disconnecterClass);
		if id then
			disconnecters[id] = this;
		end
		return this;
	end
	new.Disconnecter = disconnecterClass

	local connection = {}
	connection.__index = connection
	function connection.New(signal,func)
		return setmetatable({signal = signal,func = func},connection)
	end
	function connection:Disconnect(slient)
		local signal = self.signal
		local onceConnection = signal.onceConnection
		local connections = signal.connection

		for i,v in pairs(connections) do
			if v.connection == self then
				remove(connections,i)
				return
			end
		end
		for i,v in pairs(onceConnection) do
			if v.connection == self then
				remove(onceConnection,i)
				return
			end
		end

		if not slient then
			warn(("Connection %s is not found from signal %s, but tried :Disconnect(). Maybe disconnected aleady?"):format(tostring(self),tostring(signal)))
		end
	end
	function connection:Destroy()
		return self:Disconnect(true)
	end
	new.Connection = connection

	local signals = {}
	local signal = {}
	signal.__index = signal
	function signal:Fire(...)
		local waitting = self.waitting
		local onceConnection = self.onceConnection
		self.waitting = {}
		self.onceConnection = {}

		for _,v in pairs(waitting) do
			task.spawn(resume,v,...)
		end

		for _,v in pairs(self.connection) do
			task.spawn(v.func,...)
		end

		for _,v in pairs(onceConnection) do
			task.spawn(v.func,...)
		end
	end
	function signal:DisconnectAll()
		local waitting = self.waitting
		self.waitting = {}
		self.onceConnection = {}
		self.connection = {}
		for _,v in pairs(waitting) do
			task.spawn(resume,v,nil)
		end
	end
	function signal:Wait()
		insert(self.waitting,running())
		return yield()
	end
	function signal:CheckConnected(func)
		local onceConnection = self.onceConnection
		local connections = self.connection
		for _,v in pairs(connections) do
			if v.func == func then
				return true
			end
		end
		for _,v in pairs(onceConnection) do
			if v.func == func then
				return true
			end
		end
		return false
	end
	function signal:Connect(func)
		if self:CheckConnected(func) then
			warn(("[Quad] Function %s Connected already on signal %s"):format(tostring(func),tostring(self)))
		end
		local thisConnection = connection.New(self,func)
		insert(self.connection,{func=func,connection=thisConnection})
		return thisConnection
	end
	function signal:Once(func)
		if self:CheckConnected(func) then
			warn(("[Quad] Function %s Connected already on signal %s"):format(tostring(func),tostring(self)))
		end
		local thisConnection = connection.New(self,func)
		insert(self.onceConnection,{func=func,connection=thisConnection})
		return thisConnection
	end
	function signal:Destroy()
		local id = self.id
		if id then
			signals[id] = nil
		end
		self:DisconnectAll()
		setmetatable(self,nil)
	end
	function signal.New(id)
		if id and signals[id] then
			return signals[id]
		end
		local this = {id=id,waitting={},onceConnection={},connection={}}
		setmetatable(this,signal)
		if id then
			signals[id] = this
		end
		return this
	end
	new.Bindable = signal

	return new
end

return module

