local w=require(script.Parent.Types)local O=require(script.Parent.Constants)local T=game:GetService("\067\111\108\108\101\099\116\105\111\110\083\101\114\118\105\099\101")local I=(game:GetService("\082\117\110\083\101\114\118\105\099\101")):IsServer()local q={}q.__index=q function q.new(w)local O=setmetatable({rocs=w},q)local I=O:getTagName();(T:GetInstanceAddedSignal(I)):Connect(function(...)O:_handleModule(...)end)for w,T in ipairs(T:GetTagged(I))do O:_handleModule(T)end return O end function q.getTagName(w)return O.TAG:format(w.rocs.name)end function q.warn(O,w,...)return O.rocs:warn("\091\067\104\097\105\110\105\110\103\093 "..w,...)end function q._handleModule(T,O)if not O:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then return T:warn("\037\115 \105\115 \110\111\116 \097 \077\111\100\117\108\101\083\099\114\105\112\116\033",O)end local q=require(O)local B,f=w.module(q)if not B then T:warn("\073\110\118\097\108\105\100 \099\104\097\105\110\105\110\103 \099\111\110\102\105\103\117\114\097\116\105\111\110\058\n\n\037\115",f)end local function L(w)if q[w]then T:chain(O.Parent,q[w])end end if I then L("\115\101\114\118\101\114")else L("\099\108\105\101\110\116")end L("\115\104\097\114\101\100")end function q._connect(T,w,O)for O,I in ipairs(O)do w:listen(I.event,function()local w=(T.rocs:getEntity(I.target,"\099\104\097\105\110\105\110\103")):getComponent(I.component)if w then w[I.call](w)else T:warn("\067\111\109\112\111\110\101\110\116 \037\115 \105\115 \109\105\115\115\105\110\103 \102\114\111\109 \037\115 \119\104\105\099\104 \105\115 \110\101\101\100\101\100 \102\111\114 \097 \099\111\109\112\111\110\101\110\116 \099\104\097\105\110\033",I.component,I.target)end end)end end function q._chainAggregate(I,w,O,T)local q=I.rocs:getEntity(w,"\099\104\097\105\110\105\110\103")local B=q:getComponent(O)if B then I:_connect(B,T)end I.rocs:registerEntityComponentHook(w,O,"\105\110\105\116\105\097\108\105\122\101",function(w)I:_connect(w,T)end)if O.chainingEvents==nil then I:warn("\099\104\097\105\110\105\110\103\069\118\101\110\116\115 \097\114\114\097\121 \105\115 \109\105\115\115\105\110\103 \102\114\111\109 \099\111\109\112\111\110\101\110\116 \037\115\033 \080\108\101\097\115\101 \108\105\115\116 \097\108\108 \099\104\097\105\110\097\098\108\101 \101\118\101\110\116\115 \117\112\111\110 \114\101\103\105\115\116\114\097\116\105\111\110\046",O)end end function q.chain(T,w,O)for O,I in pairs(O)do local q=T.rocs:resolveAggregate(O)if q then T:_chainAggregate(w,q,I)else T:warn("\067\111\117\108\100 \110\111\116 \114\101\115\111\108\118\101 \099\111\109\112\111\110\101\110\116 \037\115 \105\110 \099\104\097\105\110\105\110\103 \099\111\110\102\105\103\117\114\097\116\105\111\110 \102\111\114 \037\115",O,w)end end end return q
local types = require(script.Parent.Types)
local Constants = require(script.Parent.Constants)
local CollectionService = game:GetService("CollectionService")
local IS_SERVER = game:GetService("RunService"):IsServer()

local Chainer = {}
Chainer.__index = Chainer

function Chainer.new(rocs)
	local self = setmetatable({
		rocs = rocs
	}, Chainer)

	local tagName = self:getTagName()
	CollectionService:GetInstanceAddedSignal(tagName):Connect(function(...)
		self:_handleModule(...)
	end)

	for _, module in ipairs(CollectionService:GetTagged(tagName)) do
		self:_handleModule(module)
	end

	return self
end

function Chainer:getTagName()
	return Constants.TAG:format(self.rocs.name)
end

function Chainer:warn(text, ...)
	return self.rocs:warn("[Chaining] " .. text, ...)
end

function Chainer:_handleModule(module)
	if not module:IsA("ModuleScript") then
		return self:warn("%s is not a ModuleScript!", module)
	end

	local contents = require(module)

	local isValid, typeError = types.module(contents)
	if not isValid then
		self:warn("Invalid chaining configuration:\n\n%s", typeError)
	end

	local function chain(section)
		if contents[section] then
			self:chain(module.Parent, contents[section])
		end
	end

	if IS_SERVER then
		chain("server")
	else
		chain("client")
	end

	chain("shared")
end

function Chainer:_connect(aggregate, entries)
	for _, entry in ipairs(entries) do
		aggregate:listen(entry.event, function()
			local targetAggregate = self.rocs:getEntity(entry.target, "chaining"):getComponent(entry.component)

			if targetAggregate then
				targetAggregate[entry.call](targetAggregate) -- TODO: Arguments
			else
				self:warn(
					"Component %s is missing from %s which is needed for a component chain!",
					entry.component,
					entry.target
				)
			end
		end)
	end
end

function Chainer:_chainAggregate(sourceInstance, staticAggregate, entries)
	local entity = self.rocs:getEntity(sourceInstance, "chaining")

	local currentAggregate = entity:getComponent(staticAggregate)
	if currentAggregate then
		self:_connect(currentAggregate, entries)
	end

	self.rocs:registerEntityComponentHook(
		sourceInstance,
		staticAggregate,
		"initialize",
		function (aggregate)
			self:_connect(aggregate, entries)
		end
	)

	if staticAggregate.chainingEvents == nil then
		self:warn("chainingEvents array is missing from component %s! Please list all chainable events upon registration.", staticAggregate)
	end
end

function Chainer:chain(sourceInstance, structure)
	for componentResolvable, entries in pairs(structure) do
		local staticAggregate = self.rocs:resolveAggregate(componentResolvable)

		if staticAggregate then
			self:_chainAggregate(sourceInstance, staticAggregate, entries)
		else
			self:warn("Could not resolve component %s in chaining configuration for %s", componentResolvable, sourceInstance)
		end
	end
end

return Chainer
