-- note: maybe use playfab to capture errors
ClientOrchestration.registerClientComponents = function(config)
	if config.orchestrationTime < ClientOrchestration.lastCalledOrchestrationTime then
		ClientOrchestration._warn("Attempted to orchestrate an older scene on the client: " .. config.name)
		return
	end

	if config.orchestrationTime == ClientOrchestration.lastCalledOrchestrationTime then
		ClientOrchestration._warn(
			"Attempted to orchestrate a scene with same tick time. Double check that you're not loading a scene 2x"
		)
		ClientOrchestration._warn(config.name)
		return
	end

	if config.schema.Client == nil then
		ClientOrchestration._warn(
			"No schema Client file provided on the client for scene: " .. config.name .. ". Is this intended?"
		)
		return
	end

	ClientOrchestration.isSeekable = false

	ClientOrchestration.lastCalledOrchestrationTime = config.orchestrationTime
	ClientOrchestration.cleanUp()

	local schema = ClientOrchestration._require(config.schema.Client)
	local SchemaProcessor = ClientOrchestration._require(config.schemaProcessor)
	ClientOrchestration.SchemaProcessorInstance = SchemaProcessor.new(schema, config.timePosition)

	-- Run setup before processing
	local setupSuccess = ClientOrchestration.setupSchema()

	if config.orchestrationTime < ClientOrchestration.lastCalledOrchestrationTime then
		ClientOrchestration._warn("No longer processing outdated scene: " .. config.name)
		return
	end

	if setupSuccess then
		ClientOrchestration.processSchema()
	end

	if config.orchestrationTime < ClientOrchestration.lastCalledOrchestrationTime then
		ClientOrchestration._warn("No longer processing outdated scene: " .. config.name)
		return
	end

	local name = config.name or "Default"
	-- If the player has seek permissions, then update the seek UI
	ClientOrchestration.ClientSceneMetadataEvent:Fire(
		"Pass",
		name,
		ClientOrchestration.SchemaProcessorInstance.timePosition,
		config.timeLength
	)

	if ClientOrchestration.ClientSceneMetadataRequestConnection then
		ClientOrchestration.ClientSceneMetadataRequestConnection:Disconnect()
	end

	ClientOrchestration.ClientSceneMetadataRequestConnection =
		ClientOrchestration.ClientSceneMetadataEvent.Event:Connect(
			function(Type)
				if Type == "Request" then
					ClientOrchestration.ClientSceneMetadataEvent:Fire(
						"Pass",
						name,
						ClientOrchestration.SchemaProcessorInstance.timePosition,
						config.timeLength
					)
				end
			end
		)
end

ClientOrchestration.seek = function(timeToPlay)
	local lastCalledOrchestrationTime = ClientOrchestration.lastCalledOrchestrationTime

	while not ClientOrchestration.isSeekable do
		local wasNewSceneCalled = lastCalledOrchestrationTime ~= ClientOrchestration.lastCalledOrchestrationTime
		if wasNewSceneCalled then
			ClientOrchestration._warn("New scene orchestrating, cancelling seek")
			return
		end
		ClientOrchestration.taskLib.wait()
	end

	if ClientOrchestration.SchemaProcessorInstance == nil then
		ClientOrchestration._warn("Schema Processor Instance hasn't been created on client")
		return
	end

	ClientOrchestration.cleanUp()

	-- This is in Process fires faster than setTime has replicated for the client
	ClientOrchestration.SchemaProcessorInstance.timePosition.Value = timeToPlay
	ClientOrchestration.processSchema(true)
end

ClientOrchestration.pause = function(isPaused: boolean)
	if ClientOrchestration.SchemaProcessorInstance == nil then
		ClientOrchestration._warn("Schema Processor Instance hasn't been created on client")
		return
	end

	ClientOrchestration.SchemaProcessorInstance:SetPaused(isPaused)
end

local hasBeenCalled = false
return function(stubs)
	if hasBeenCalled then
		error("Client Orchestration has already been called")
		return
	end

	-- Used for testing only
	if stubs then
		for i, v in pairs(stubs) do
			ClientOrchestration[i] = v
		end
	end

	ClientOrchestration.SendMediaControllerConfig.OnClientEvent:Connect(ClientOrchestration.registerClientComponents)
	ClientOrchestration.SeekPermissionsEvent.OnClientEvent:Connect(ClientOrchestration.handleSeekPermissionsEvent)
	ClientOrchestration.Seek.OnClientEvent:Connect(ClientOrchestration.seek)
	ClientOrchestration.Pause.OnClientEvent:Connect(ClientOrchestration.pause)
	ClientOrchestration.SceneEnded.OnClientEvent:Connect(ClientOrchestration.handleSceneEnded)

	hasBeenCalled = true

	return ClientOrchestration
end