local y=game:GetService("\082\117\110\083\101\114\118\105\099\101")local g=game:GetService("\080\108\097\121\101\114\115")local H=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local k=require(script.Parent.BuiltInSerializers)local o=require(script.Parent.Util)local T=y:IsServer()local w="\082\111\099\115\069\118\101\110\116"local d="\082\111\099\115\073\110\105\116\105\097\108"local z=require(script.Parent.Parent.Shared.Inspect)local X={}X.__index=X local function D(...)return...end local function m(y,g,H)local k=y:FindFirstChild(g)if not k then k=Instance.new(H)k.Name=g k.Parent=y end return k end local function b(y,g,H)return g and(g.playerMasks and(g.playerMasks[H]and o.clipMask(y,g.playerMasks[H]))or g.mask and o.clipMask(y,g.mask))or y end function X.new(y)local z={rocs=y,_serializers=setmetatable({},k.serializers),_deserializers=setmetatable({},k.deserializers)}if T then z._event=m(H,w,"\082\101\109\111\116\101\069\118\101\110\116")z._eventInit=m(H,d,"\082\101\109\111\116\101\069\118\101\110\116")z._component=y:registerComponent({name="\082\101\112\108\105\099\097\116\101\100";reducer=y.reducers.structure({players=y.reducers.concatArray;mask=y.reducers.mergeTable;playerMasks=y.reducers.mergeTable});check=function(y)return type(y)=="\116\097\098\108\101"end;onUpdated=function(y)y:dispatch("\111\110\080\097\114\101\110\116\085\112\100\097\116\101\100",true)end;onParentUpdated=function(H,k)local T=H.instance local w=z:_serialize(T.instance)local d=not k or y.comparators.value(H.data and H.data.mask,H.lastData and H.lastData.mask)or y.comparators.value(H.data and H.data.playerMasks,H.lastData and H.lastData.playerMasks)local X={}local D if H.data and(H.lastData and(H.data.players and H.lastData.players))then for y,g in ipairs(H.lastData.players)do if o.find(H.data.players,g)==nil then X=X table.insert(X,g)end end if not d then for y,g in ipairs(H.data.players)do if o.find(H.lastData.players)==nil then D=D or{}table.insert(D,g)end end end end D=D or H.data and H.data.players or g:GetPlayers()for y,g in ipairs(D)do z:_replicate(g,T.name,w,b(T.data,H.data,g))end for y,g in ipairs(X)do z:_replicate(g,T.name,w,nil)end end;g.PlayerAdded:Connect(function(g)local H={}for y,k in ipairs(y:getComponents(z._component))do if k.data.players==nil then local y=k.instance table.insert(H,{target=z:_serialize(y.instance),data=b(y.data,k.data,g),component=y.name})end end z:_replicatePayload(g,H,z._eventInit)end)})else z._event=H:WaitForChild(w)z._eventInit=H:WaitForChild(d)local function y(y,g)if y~=z.rocs.name then return end z:_reifyPayload(g)end spawn(function()y(z._eventInit.OnClientEvent:Wait())z._event.OnClientEvent:Connect(y)end)end return setmetatable(z,X)end function X._reifyPayload(g,y)for y,H in ipairs(y)do if H.target then local y=g:_deserialize(H.target)local k=g.rocs:getEntity(y,"\095\114\101\109\111\116\101",g.rocs.Internal)k:addComponent(H.component,H.data)else warn(("\077\105\115\115\105\110\103 \116\097\114\103\101\116 \102\114\111\109 \112\097\121\108\111\097\100\044 \100\111\101\115 \116\104\101 \099\108\105\101\110\116 \104\097\118\101 \097\099\099\101\115\115 \116\111 \116\104\105\115 \105\110\115\116\097\110\099\101\063 \n\n \037\115"):format(z(H)))end end end function X._deserialize(g,y)if typeof(y)=="\073\110\115\116\097\110\099\101"then return y end local H=g._deserializers[y.type]if not H then error("\085\110\097\098\108\101 \116\111 \100\101\115\101\114\105\097\108\105\122\101 \111\098\106\101\099\116")end local k=H(y,g.rocs)return k or error("\068\101\115\101\114\105\097\108\105\122\097\116\105\111\110 \102\097\105\108\101\100 \102\111\114 \111\098\106\101\099\116")end function X._serialize(g,y)local H=typeof(y)=="\073\110\115\116\097\110\099\101"and D or g:findSerializer(y)return H and H(y,g.rocs)or error(("\085\110\097\098\108\101 \116\111 \115\101\114\105\097\108\105\122\101 \114\101\112\108\105\099\097\116\101\100 \099\111\109\112\111\110\101\110\116 \037\115"):format(y))end function X._replicate(o,y,g,H,k)return o:_replicatePayload(y,{{target=H,data=k,component=g}})end function X._replicatePayload(k,y,g,H)(H or k._event):FireClient(y,k.rocs.name,g)end function X.registerSerializer(H,y,g)H._serializers[y]=g end function X.registerDeserializer(H,y,g)assert(type(y)=="\115\116\114\105\110\103","\068\101\115\101\114\105\097\108\105\122\101\114 \116\121\112\101 \109\117\115\116 \098\101 \097 \115\116\114\105\110\103")H._deserializers[y]=g end local function n(y,g)if g[y]then return g[y]end local H=getmetatable(y)if H then return n(H,g)end end function X.findSerializer(g,y)return n(y,g._serializers)end function X.findDeserializer(g,y)return g._deserializers[y]end return X
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local BuiltInSerializers = require(script.Parent.BuiltInSerializers)
local Util = require(script.Parent.Util)

local IS_SERVER = RunService:IsServer()
local EVENT_NAME = "RocsEvent"
local EVENT_NAME_INIT = "RocsInitial"
local inspect = require(script.Parent.Parent.Shared.Inspect)

local Replicator = {}
Replicator.__index = Replicator

local function identity(...)
	return ...
end

local function getOrCreate(parent, name, class)
	local instance = parent:FindFirstChild(name)

	if not instance then
		instance = Instance.new(class)
		instance.Name = name
		instance.Parent = parent
	end

	return instance
end

local function getData(data, replicated, player)
	return
		replicated and (
			replicated.playerMasks
			and replicated.playerMasks[player]
			and Util.clipMask(data, replicated.playerMasks[player])
			or replicated.mask
			and Util.clipMask(data, replicated.mask)
		) or data
end

function Replicator.new(rocs)
	local self = {
		rocs = rocs;
		_serializers = setmetatable({}, BuiltInSerializers.serializers);
		_deserializers = setmetatable({}, BuiltInSerializers.deserializers);
	}

	if IS_SERVER then
		self._event = getOrCreate(
			ReplicatedStorage,
			EVENT_NAME,
			"RemoteEvent"
		)

		self._eventInit = getOrCreate(
			ReplicatedStorage,
			EVENT_NAME_INIT,
			"RemoteEvent"
		)

		self._component = rocs:registerComponent({
			name = "Replicated";
			reducer = rocs.reducers.structure({
				players = rocs.reducers.concatArray;
				mask = rocs.reducers.mergeTable;
				playerMasks = rocs.reducers.mergeTable;
			});
			check = function(value)
				return type(value) == "table"
			end;
			onUpdated = function(replicated)
				replicated:dispatch("onParentUpdated", true)
			end;
			onParentUpdated = function(replicated, fromSelf)
				local aggregate = replicated.instance

				local serializedTarget = self:_serialize(aggregate.instance)

				local shouldBroadcast =
					not fromSelf
					or rocs.comparators.value(
						replicated.data and replicated.data.mask,
						replicated.lastData and replicated.lastData.mask
					)
					or rocs.comparators.value(
						replicated.data and replicated.data.playerMasks,
						replicated.lastData and replicated.lastData.playerMasks
					)

				local removedPlayers = {}
				local players
				if
					replicated.data
					and replicated.lastData
					and replicated.data.players
					and replicated.lastData.players
				then
					for _, player in ipairs(replicated.lastData.players) do
						if Util.find(replicated.data.players, player) == nil then
							removedPlayers = removedPlayers

							table.insert(removedPlayers, player)
						end
					end

					if not shouldBroadcast then
						for _, player in ipairs(replicated.data.players) do
							if Util.find(replicated.lastData.players) == nil then
								players = players or {}

								table.insert(players, player)
							end
						end
					end
				end

				players = players or (replicated.data and replicated.data.players) or Players:GetPlayers()

				for _, player in ipairs(players) do
					self:_replicate(
						player,
						aggregate.name,
						serializedTarget,
						getData(aggregate.data, replicated.data, player)
					)
				end

				for _, player in ipairs(removedPlayers) do
					self:_replicate(
						player,
						aggregate.name,
						serializedTarget,
						nil
					)
				end
			end;

			Players.PlayerAdded:Connect(function(player)
				local payload = {}

				for _, replicated in ipairs(rocs:getComponents(self._component)) do
					-- Only do this because if the player is added to an exclusive
					-- one, they'll be gotten in the onUpdated
					if replicated.data.players == nil then
						local aggregate = replicated.instance
						table.insert(payload, {
							target = self:_serialize(aggregate.instance);
							data = getData(aggregate.data, replicated.data, player);
							component = aggregate.name;
						})
					end
				end

				self:_replicatePayload(player, payload, self._eventInit)
			end)
		})
	else
		self._event = ReplicatedStorage:WaitForChild(EVENT_NAME)
		self._eventInit = ReplicatedStorage:WaitForChild(EVENT_NAME_INIT)

		local function handleEvent(rocsName, payload)
			if rocsName ~= self.rocs.name then
				return
			end

			self:_reifyPayload(payload)
		end

		-- Delay until later to allow registration to complete
		spawn(function()
			handleEvent(self._eventInit.OnClientEvent:Wait())
			self._event.OnClientEvent:Connect(handleEvent)
		end)
	end

	return setmetatable(self, Replicator)
end

function Replicator:_reifyPayload(payload)
	for _, entry in ipairs(payload) do

		if entry.target then
			local instance = self:_deserialize(entry.target)

			local entity = self.rocs:getEntity(instance, "_remote", self.rocs.Internal)

			entity:addComponent(entry.component, entry.data)
		else
			warn(("Missing target from payload, does the client have access to this instance? \n\n %s"):format(inspect(entry)))
		end
	end
end

function Replicator:_deserialize(serializedTarget)
	if typeof(serializedTarget) == "Instance" then
		return serializedTarget
	end

	local deserializer = self._deserializers[serializedTarget.type]
	if not deserializer then
		error("Unable to deserialize object") -- TODO: Dump inspect of object
	end

	local object = deserializer(serializedTarget, self.rocs)

	return object or error("Deserialization failed for object")
end

function Replicator:_serialize(object)
	local serializer =
		typeof(object) == "Instance"
			and identity
			or self:findSerializer(object)

	return
		serializer and serializer(object, self.rocs)
		or error(("Unable to serialize replicated component %s"):format(object))
end

function Replicator:_replicate(player, component, target, data)
	return self:_replicatePayload(player, {{
		target = target;
		data = data;
		component = component;
	}})
end

function Replicator:_replicatePayload(player, payload, eventOverride)
	(eventOverride or self._event):FireClient(player, self.rocs.name, payload)
end

function Replicator:registerSerializer(class, callback)
	self._serializers[class] = callback
end

function Replicator:registerDeserializer(name, callback)
	assert(type(name) == "string", "Deserializer type must be a string")
	self._deserializers[name] = callback
end

local function find(class, map)
	if map[class] then
		return map[class]
	end

	local metatable = getmetatable(class)

	if metatable then
		return find(metatable, map)
	end
end

function Replicator:findSerializer(class)
	return find(class, self._serializers)
end

function Replicator:findDeserializer(name)
	return self._deserializers[name]
end

return Replicator
