local V={}V.Version=script.Parent.Version.Value V.Services={}V.Util=script.Parent.Util local L=Instance.new("\070\111\108\100\101\114")L.Name="\083\101\114\118\105\099\101\115"local B=require(V.Util.Promise)local U=require(V.Util.Thread)local A=require(V.Util.Signal)local m=require(V.Util.Loader)local X=require(V.Util.Ser)local d=require(V.Util.Remote.RemoteSignal)local i=require(V.Util.Remote.RemoteProperty)local f=require(V.Util.TableUtil)local Z=false local h=false local g=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local function q(V)local L=Instance.new("\070\111\108\100\101\114")L.Name=V return L end local function Y(V,L)local B=V:FindFirstChild(L)if not B then B=Instance.new("\070\111\108\100\101\114")B.Name=L B.Parent=V end return B end local function I(V,B,U)if U then B.Parent=Y(V._knit_rep_folder,U)elseif B:IsA("\082\101\109\111\116\101\070\117\110\099\116\105\111\110")then B.Parent=Y(V._knit_rep_folder,"\082\070")elseif B:IsA("\082\101\109\111\116\101\069\118\101\110\116")then B.Parent=Y(V._knit_rep_folder,"\082\069")elseif B:IsA("\086\097\108\117\101\066\097\115\101")then B.Parent=Y(V._knit_rep_folder,"\082\080")else error("\073\110\118\097\108\105\100 \114\101\112 \111\098\106\101\099\116\058 "..B.ClassName)end if not V._knit_rep_folder.Parent then V._knit_rep_folder.Parent=L end end function V.IsService(V)return type(V)=="\116\097\098\108\101"and V._knit_is_service==true end function V.CreateService(L)assert(type(L)=="\116\097\098\108\101","\083\101\114\118\105\099\101 \109\117\115\116 \098\101 \097 \116\097\098\108\101\059 \103\111\116 "..type(L))assert(type(L.Name)=="\115\116\114\105\110\103","\083\101\114\118\105\099\101\046\078\097\109\101 \109\117\115\116 \098\101 \097 \115\116\114\105\110\103\059 \103\111\116 "..type(L.Name))assert(#L.Name>0,"\083\101\114\118\105\099\101\046\078\097\109\101 \109\117\115\116 \098\101 \097 \110\111\110-\101\109\112\116\121 \115\116\114\105\110\103")assert(V.Services[L.Name]==nil,"\083\101\114\118\105\099\101 \""..(L.Name.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))L=f.Assign(L,{_knit_is_service=true,_knit_rf={};_knit_re={},_knit_rp={};_knit_rep_folder=q(L.Name)})if type(L.Client)~="\116\097\098\108\101"then L.Client={Server=L}else if L.Client.Server~=L then L.Client.Server=L end end V.Services[L.Name]=L return L end function V.AddServices(V)return m.LoadChildren(V)end function V.AddServicesDeep(V)return m.LoadDescendants(V)end function V.GetService(L)assert(type(L)=="\115\116\114\105\110\103","\083\101\114\118\105\099\101\078\097\109\101 \109\117\115\116 \098\101 \097 \115\116\114\105\110\103\059 \103\111\116 "..type(L))return assert(V.Services[L],"\067\111\117\108\100 \110\111\116 \102\105\110\100 \115\101\114\118\105\099\101 \""..(L.."\""))end function V.BindRemoteEvent(V,L,B)assert(V._knit_re[L]==nil,"\082\101\109\111\116\101\069\118\101\110\116 \""..(L.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))local U=B._remote U.Name=L V._knit_re[L]=U I(V,U)end function V.BindRemoteFunction(V,L,B)assert(V._knit_rf[L]==nil,"\082\101\109\111\116\101\070\117\110\099\116\105\111\110 \""..(L.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))local U=Instance.new("\082\101\109\111\116\101\070\117\110\099\116\105\111\110")U.Name=L V._knit_rf[L]=U I(V,U)function U.OnServerInvoke(...)return X.SerializeArgsAndUnpack(B(V.Client,X.DeserializeArgsAndUnpack(...)))end end function V.BindRemoteProperty(V,L,B)assert(V._knit_rp[L]==nil,"\082\101\109\111\116\101\080\114\111\112\101\114\116\121 \""..(L.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))B._object.Name=L V._knit_rp[L]=B I(V,B._object,"\082\080")end function V.Start()if Z then return B.Reject("\075\110\105\116 \097\108\114\101\097\100\121 \115\116\097\114\116\101\100")end Z=true local m=V.Services return(B.new(function(L)for L,B in pairs(m)do for L,U in pairs(B.Client)do if type(U)=="\102\117\110\099\116\105\111\110"then V.BindRemoteFunction(B,L,U)elseif d.Is(U)then V.BindRemoteEvent(B,L,U)elseif i.Is(U)then V.BindRemoteProperty(B,L,U)elseif A.Is(U)then warn("\070\111\117\110\100 \083\105\103\110\097\108 \105\110\115\116\101\097\100 \111\102 \082\101\109\111\116\101\083\105\103\110\097\108 \040\075\110\105\116\046\085\116\105\108\046\082\101\109\111\116\101\083\105\103\110\097\108\041\046 \080\108\101\097\115\101 \099\104\097\110\103\101 \116\111 \082\101\109\111\116\101\083\105\103\110\097\108\046 \091"..(B.Name..("\046\067\108\105\101\110\116\046"..(L.."\093"))))end end end local U={}for V,L in pairs(m)do if type(L.KnitInit)=="\102\117\110\099\116\105\111\110"then table.insert(U,B.new(function(V)L:KnitInit()V()end))end end L(B.All(U))end)):Then(function()for V,L in pairs(m)do if type(L.KnitStart)=="\102\117\110\099\116\105\111\110"then U.SpawnNow(L.KnitStart,L)end end h=true g:Fire()U.Spawn(function()g:Destroy()end)L.Parent=script.Parent end)end function V.OnStart()if h then return B.Resolve()else return B.FromEvent(g.Event)end end return V
--[[

	Knit.CreateService(service): Service
	Knit.AddServices(folder): Service[]
	Knit.AddServicesDeep(folder): Service[]
	Knit.Start(): Promise<void>
	Knit.OnStart(): Promise<void>

--]]

local KnitServer = {}

KnitServer.Version = script.Parent.Version.Value
KnitServer.Services = {}
KnitServer.Util = script.Parent.Util

local knitRepServiceFolder = Instance.new("Folder")
knitRepServiceFolder.Name = "Services"

local Promise = require(KnitServer.Util.Promise)
local Thread = require(KnitServer.Util.Thread)
local Signal = require(KnitServer.Util.Signal)
local Loader = require(KnitServer.Util.Loader)
local Ser = require(KnitServer.Util.Ser)
local RemoteSignal = require(KnitServer.Util.Remote.RemoteSignal)
local RemoteProperty = require(KnitServer.Util.Remote.RemoteProperty)
local TableUtil = require(KnitServer.Util.TableUtil)

local started = false
local startedComplete = false
local onStartedComplete = Instance.new("BindableEvent")

local function CreateRepFolder(serviceName)
	local folder = Instance.new("Folder")
	folder.Name = serviceName
	return folder
end

local function GetFolderOrCreate(parent, name)
	local f = parent:FindFirstChild(name)
	if not f then
		f = Instance.new("Folder")
		f.Name = name
		f.Parent = parent
	end
	return f
end

local function AddToRepFolder(service, remoteObj, folderOverride)
	if folderOverride then
		remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, folderOverride)
	elseif remoteObj:IsA("RemoteFunction") then
		remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RF")
	elseif remoteObj:IsA("RemoteEvent") then
		remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RE")
	elseif remoteObj:IsA("ValueBase") then
		remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RP")
	else
		error("Invalid rep object: " .. remoteObj.ClassName)
	end
	if not service._knit_rep_folder.Parent then
		service._knit_rep_folder.Parent = knitRepServiceFolder
	end
end

function KnitServer.IsService(object)
	return type(object) == "table" and object._knit_is_service == true
end

function KnitServer.CreateService(service)
	assert(type(service) == "table", "Service must be a table; got " .. type(service))
	assert(type(service.Name) == "string", "Service.Name must be a string; got " .. type(service.Name))
	assert(#service.Name > 0, "Service.Name must be a non-empty string")
	assert(KnitServer.Services[service.Name] == nil, 'Service "' .. service.Name .. '" already exists')
	service = TableUtil.Assign(service, {
		_knit_is_service = true,
		_knit_rf = {},
		_knit_re = {},
		_knit_rp = {},
		_knit_rep_folder = CreateRepFolder(service.Name),
	})
	if type(service.Client) ~= "table" then
		service.Client = { Server = service }
	else
		if service.Client.Server ~= service then
			service.Client.Server = service
		end
	end
	KnitServer.Services[service.Name] = service
	return service
end

function KnitServer.AddServices(folder)
	return Loader.LoadChildren(folder)
end

function KnitServer.AddServicesDeep(folder)
	return Loader.LoadDescendants(folder)
end

function KnitServer.GetService(serviceName)
	assert(type(serviceName) == "string", "ServiceName must be a string; got " .. type(serviceName))
	return assert(KnitServer.Services[serviceName], 'Could not find service "' .. serviceName .. '"')
end

function KnitServer.BindRemoteEvent(service, eventName, remoteEvent)
	assert(service._knit_re[eventName] == nil, 'RemoteEvent "' .. eventName .. '" already exists')
	local re = remoteEvent._remote
	re.Name = eventName
	service._knit_re[eventName] = re
	AddToRepFolder(service, re)
end

function KnitServer.BindRemoteFunction(service, funcName, func)
	assert(service._knit_rf[funcName] == nil, 'RemoteFunction "' .. funcName .. '" already exists')
	local rf = Instance.new("RemoteFunction")
	rf.Name = funcName
	service._knit_rf[funcName] = rf
	AddToRepFolder(service, rf)
	function rf.OnServerInvoke(...)
		return Ser.SerializeArgsAndUnpack(func(service.Client, Ser.DeserializeArgsAndUnpack(...)))
	end
end

function KnitServer.BindRemoteProperty(service, propName, prop)
	assert(service._knit_rp[propName] == nil, 'RemoteProperty "' .. propName .. '" already exists')
	prop._object.Name = propName
	service._knit_rp[propName] = prop
	AddToRepFolder(service, prop._object, "RP")
end

function KnitServer.Start()
	if started then
		return Promise.Reject("Knit already started")
	end

	started = true

	local services = KnitServer.Services

	return Promise.new(function(resolve)
		-- Bind remotes:
		for _, service in pairs(services) do
			for k, v in pairs(service.Client) do
				if type(v) == "function" then
					KnitServer.BindRemoteFunction(service, k, v)
				elseif RemoteSignal.Is(v) then
					KnitServer.BindRemoteEvent(service, k, v)
				elseif RemoteProperty.Is(v) then
					KnitServer.BindRemoteProperty(service, k, v)
				elseif Signal.Is(v) then
					warn(
						"Found Signal instead of RemoteSignal (Knit.Util.RemoteSignal). Please change to RemoteSignal. ["
							.. service.Name
							.. ".Client."
							.. k
							.. "]"
					)
				end
			end
		end

		-- Init:
		local promisesInitServices = {}
		for _, service in pairs(services) do
			if type(service.KnitInit) == "function" then
				table.insert(
					promisesInitServices,
					Promise.new(function(r)
						service:KnitInit()
						r()
					end)
				)
			end
		end

		resolve(Promise.All(promisesInitServices))
	end):Then(function()
		-- Start:
		for _, service in pairs(services) do
			if type(service.KnitStart) == "function" then
				Thread.SpawnNow(service.KnitStart, service)
			end
		end

		startedComplete = true
		onStartedComplete:Fire()

		Thread.Spawn(function()
			onStartedComplete:Destroy()
		end)

		-- Expose service remotes to everyone:
		knitRepServiceFolder.Parent = script.Parent
	end)
end

function KnitServer.OnStart()
	if startedComplete then
		return Promise.Resolve()
	else
		return Promise.FromEvent(onStartedComplete.Event)
	end
end

return KnitServer
