--[=[
	@return Promise
	Starts Knit. Should only be called once.

	Optionally, `KnitOptions` can be passed in order to set
	Knit's custom configurations.

	:::caution
	Be sure that all services have been created _before_
	calling `Start`. Services cannot be added later.
	:::

	```lua
	Knit.Start():andThen(function()
		print("Knit started!")
	end):catch(warn)
	```
	
	Example of Knit started with options:
	```lua
	Knit.Start({
		Middleware = {
			Inbound = {
				function(player, args)
					print("Player is giving following args to server:", args)
					return true
				end
			},
		},
	}):andThen(function()
		print("Knit started!")
	end):catch(warn)
	```
]=]

function KnitServer.Start(options: KnitOptions?)
	if started then
		return Promise.reject("Knit already started")
	end

	started = true

	if options == nil then
		selectedOptions = defaultOptions
	else
		assert(typeof(options) == "table", `KnitOptions should be a table or nil; got {typeof(options)}`)
		selectedOptions = options
		for k, v in defaultOptions do
			if selectedOptions[k] == nil then
				selectedOptions[k] = v
			end
		end
	end

	return Promise.new(function(resolve)
		local knitMiddleware = if selectedOptions.Middleware ~= nil then selectedOptions.Middleware else {}

		-- Bind remotes:
		for _, service in services do
			local middleware = if service.Middleware ~= nil then service.Middleware else {}
			local inbound = if middleware.Inbound ~= nil then middleware.Inbound else knitMiddleware.Inbound
			local outbound = if middleware.Outbound ~= nil then middleware.Outbound else knitMiddleware.Outbound
			service.Middleware = nil
			for k, v in service.Client do
				if type(v) == "function" then
					service.KnitComm:WrapMethod(service.Client, k, inbound, outbound)
				elseif v == SIGNAL_MARKER then
					service.Client[k] = service.KnitComm:CreateSignal(k, inbound, outbound)
				elseif type(v) == "table" and v[1] == PROPERTY_MARKER then
					service.Client[k] = service.KnitComm:CreateProperty(k, v[2], inbound, outbound)
				end
			end
		end

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

		resolve(Promise.all(promisesInitServices))
	end):andThen(function()
		-- Start:
		for _, service in services do
			if type(service.KnitStart) == "function" then
				task.spawn(function()
					debug.setmemorycategory(service.Name)
					service:KnitStart()
				end)
			end
		end

		startedComplete = true
		onStartedComplete:Fire()

		task.defer(function()
			onStartedComplete:Destroy()
		end)

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