local a={}a.Version=script.Parent.Version.Value a.Player=(game:GetService("\080\108\097\121\101\114\115")).LocalPlayer a.Controllers={}a.Util=script.Parent.Util local V=require(a.Util.Promise)local D=require(a.Util.Thread)local W=require(a.Util.Loader)local Q=require(a.Util.Ser)local F=require(a.Util.Remote.ClientRemoteSignal)local O=require(a.Util.Remote.ClientRemoteProperty)local Z=require(a.Util.TableUtil)local B={}local X=script.Parent:WaitForChild("\083\101\114\118\105\099\101\115")local R=false local b=false local t=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local function l(a,D)local W={}if D:FindFirstChild("\082\070")then for a,D in ipairs(D.RF:GetChildren())do if D:IsA("\082\101\109\111\116\101\070\117\110\099\116\105\111\110")then W[D.Name]=function(a,...)return Q.DeserializeArgsAndUnpack(D:InvokeServer(Q.SerializeArgsAndUnpack(...)))end W[D.Name.."\080\114\111\109\105\115\101"]=function(a,...)local W=Q.SerializeArgs(...)return V.new(function(a)a(Q.DeserializeArgsAndUnpack(D:InvokeServer(table.unpack(W,1,W.n))))end)end end end end if D:FindFirstChild("\082\069")then for a,V in ipairs(D.RE:GetChildren())do if V:IsA("\082\101\109\111\116\101\069\118\101\110\116")then W[V.Name]=F.new(V)end end end if D:FindFirstChild("\082\080")then for a,V in ipairs(D.RP:GetChildren())do if V:IsA("\086\097\108\117\101\066\097\115\101")or V:IsA("\082\101\109\111\116\101\069\118\101\110\116")then W[V.Name]=O.new(V)end end end B[a]=W return W end function a.CreateController(V)assert(type(V)=="\116\097\098\108\101","\067\111\110\116\114\111\108\108\101\114 \109\117\115\116 \098\101 \097 \116\097\098\108\101\059 \103\111\116 "..type(V))assert(type(V.Name)=="\115\116\114\105\110\103","\067\111\110\116\114\111\108\108\101\114\046\078\097\109\101 \109\117\115\116 \098\101 \097 \115\116\114\105\110\103\059 \103\111\116 "..type(V.Name))assert(#V.Name>0,"\067\111\110\116\114\111\108\108\101\114\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(a.Controllers[V.Name]==nil,"\083\101\114\118\105\099\101 \""..(V.Name.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))Z.Extend(V,{_knit_is_controller=true})a.Controllers[V.Name]=V return V end function a.AddControllers(a)return W.LoadChildren(a)end function a.AddControllersDeep(a)return W.LoadDescendants(a)end function a.GetService(a)assert(type(a)=="\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(a))local V=X:FindFirstChild(a)assert(V~=nil,"\067\111\117\108\100 \110\111\116 \102\105\110\100 \115\101\114\118\105\099\101 \""..(a.."\""))return B[a]or l(a,V)end function a.GetController(V)return a.Controllers[V]end function a.Start()if R then return V.Reject("\075\110\105\116 \097\108\114\101\097\100\121 \115\116\097\114\116\101\100")end R=true local W=a.Controllers return(V.new(function(a)local D={}for a,W in pairs(W)do if type(W.KnitInit)=="\102\117\110\099\116\105\111\110"then table.insert(D,V.new(function(a)W:KnitInit()a()end))end end a(V.All(D))end)):Then(function()for a,V in pairs(W)do if type(V.KnitStart)=="\102\117\110\099\116\105\111\110"then D.SpawnNow(V.KnitStart,V)end end b=true t:Fire()D.Spawn(function()t:Destroy()end)end)end function a.OnStart()if b then return V.Resolve()else return V.new(function(a)if b then a()return end t.Event:Wait()a()end)end end return a
--[[

	Knit.CreateController(controller): Controller
	Knit.AddControllers(folder): Controller[]
	Knit.AddControllersDeep(folder): Controller[]
	Knit.GetService(serviceName): Service
	Knit.GetController(controllerName): Controller
	Knit.Start(): Promise<void>
	Knit.OnStart(): Promise<void>

--]]

local KnitClient = {}

KnitClient.Version = script.Parent.Version.Value
KnitClient.Player = game:GetService("Players").LocalPlayer
KnitClient.Controllers = {}
KnitClient.Util = script.Parent.Util

local Promise = require(KnitClient.Util.Promise)
local Thread = require(KnitClient.Util.Thread)
local Loader = require(KnitClient.Util.Loader)
local Ser = require(KnitClient.Util.Ser)
local ClientRemoteSignal = require(KnitClient.Util.Remote.ClientRemoteSignal)
local ClientRemoteProperty = require(KnitClient.Util.Remote.ClientRemoteProperty)
local TableUtil = require(KnitClient.Util.TableUtil)

local services = {}
local servicesFolder = script.Parent:WaitForChild("Services")

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

local function BuildService(serviceName, folder)
	local service = {}
	if folder:FindFirstChild("RF") then
		for _, rf in ipairs(folder.RF:GetChildren()) do
			if rf:IsA("RemoteFunction") then
				service[rf.Name] = function(self, ...)
					return Ser.DeserializeArgsAndUnpack(rf:InvokeServer(Ser.SerializeArgsAndUnpack(...)))
				end
				service[rf.Name .. "Promise"] = function(self, ...)
					local args = Ser.SerializeArgs(...)
					return Promise.new(function(resolve)
						resolve(Ser.DeserializeArgsAndUnpack(rf:InvokeServer(table.unpack(args, 1, args.n))))
					end)
				end
			end
		end
	end
	if folder:FindFirstChild("RE") then
		for _, re in ipairs(folder.RE:GetChildren()) do
			if re:IsA("RemoteEvent") then
				service[re.Name] = ClientRemoteSignal.new(re)
			end
		end
	end
	if folder:FindFirstChild("RP") then
		for _, rp in ipairs(folder.RP:GetChildren()) do
			if rp:IsA("ValueBase") or rp:IsA("RemoteEvent") then
				service[rp.Name] = ClientRemoteProperty.new(rp)
			end
		end
	end
	services[serviceName] = service
	return service
end

function KnitClient.CreateController(controller)
	assert(type(controller) == "table", "Controller must be a table; got " .. type(controller))
	assert(type(controller.Name) == "string", "Controller.Name must be a string; got " .. type(controller.Name))
	assert(#controller.Name > 0, "Controller.Name must be a non-empty string")
	assert(KnitClient.Controllers[controller.Name] == nil, 'Service "' .. controller.Name .. '" already exists')
	TableUtil.Extend(controller, {
		_knit_is_controller = true,
	})
	KnitClient.Controllers[controller.Name] = controller
	return controller
end

function KnitClient.AddControllers(folder)
	return Loader.LoadChildren(folder)
end

function KnitClient.AddControllersDeep(folder)
	return Loader.LoadDescendants(folder)
end

function KnitClient.GetService(serviceName)
	assert(type(serviceName) == "string", "ServiceName must be a string; got " .. type(serviceName))
	local folder = servicesFolder:FindFirstChild(serviceName)
	assert(folder ~= nil, 'Could not find service "' .. serviceName .. '"')
	return services[serviceName] or BuildService(serviceName, folder)
end

function KnitClient.GetController(controllerName)
	return KnitClient.Controllers[controllerName]
end

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

	started = true

	local controllers = KnitClient.Controllers

	return Promise.new(function(resolve)
		-- Init:
		local promisesStartControllers = {}
		for _, controller in pairs(controllers) do
			if type(controller.KnitInit) == "function" then
				table.insert(
					promisesStartControllers,
					Promise.new(function(r)
						controller:KnitInit()
						r()
					end)
				)
			end
		end

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

		startedComplete = true
		onStartedComplete:Fire()

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

function KnitClient.OnStart()
	if startedComplete then
		return Promise.Resolve()
	else
		return Promise.new(function(resolve)
			if startedComplete then
				resolve()
				return
			end
			onStartedComplete.Event:Wait()
			resolve()
		end)
	end
end

return KnitClient
