local function n(n)local I={canvases={};spots={},isLoaded={}}for n,X in ipairs(n)do I.canvases[X.id]={spots=Cryo.List.map(X.spots,function(n)return n.id end)}for n,X in ipairs(X.spots)do I.spots[X.id]=X end I.isLoaded[X.id]=true end return function(n)return{canvases=Cryo.Dictionary.join(n.canvases,I.canvases),spots=Cryo.Dictionary.join(n.spots,I.spots);isLoaded=Cryo.Dictionary.join(n.isLoaded,I.isLoaded)}end end modules.fetchAllArt=function(I)return function()local X={}for n,r in ipairs(I.getTagged())do local t=r:GetAttribute(constants.Attributes.SurfaceCanvasId)local u,B=I.props.GetCanvas:InvokeServer(t)if not u then warn("\091\083\117\114\102\097\099\101\065\114\116\093 \073\110\118\097\108\105\100 \071\101\116\067\097\110\118\097\115 \099\097\108\108\058 "..B)continue end table.insert(X,B)end local r=n(X)I:setState(r)end end modules.fetchArt=function(I)return function(X)local r,t=I.props.GetCanvas:InvokeServer(X)if not r then warn("\091\083\117\114\102\097\099\101\065\114\116\093 \073\110\118\097\108\105\100 \071\101\116\067\097\110\118\097\115 \099\097\108\108\058 "..t)return end local u=n({t})I:setState(u)end end modules.applyArt=function(n)return function(I)if n.isAnimating then return end local X=n.state.activeSpotId local r,t=n.props.ApplyArt:InvokeServer(X,I)if not r then n:setState({errorMessage=t})n.cleanup()return end task.spawn(function()n.isAnimating=true n:setState({activeSpotId=Roact.None,errorMessage=Roact.None})task.wait(1)n.showHumanoids()n.cameraModule:animateToHumanoidAsync()n.waitForHumanoidAnimations()n.userInputController:enable()n.guiController:enable()n.isAnimating=false end)end end modules.getRandomSpot=function(n)return function(I)if n.state.activeSpotId then return end if n.isAnimating then return end n.userInputController:disable()n.guiController:disable()task.spawn(function()n.isAnimating=true local X=n.canvasRefs[I]:getValue()n.cameraModule:animateToCanvasAsync(X)local r,t=n.props.GetRandomSpot:InvokeServer(I)if not r then n:setState({errorMessage=t})n.cleanup()n.isAnimating=false return end n.hideHumanoids()local u=n.spotRefs[t]:getValue()n.cameraModule:animateToSpotAsync(u)n:setState({activeSpotId=t})n.waitForHumanoidAnimations()n.isAnimating=false end)end end modules.clearErrorMessage=function(n)return function()n:setState({errorMessage=Roact.None})end end modules.onCancel=function(n)return function()if n.isAnimating then return end n:setState({activeSpotId=Roact.None})task.spawn(function()n.isAnimating=true n.showHumanoids()n.cameraModule:animateToHumanoidAsync()n.guiController:enable()n.userInputController:enable()n.waitForHumanoidAnimations()n.isAnimating=false end)end end modules.onSpotChanged=function(n,I)return function(X,r,t,u)local B=n.canvasRefs[X]local m=n.spotRefs[r]if B and m then local X=B:getValue()local r=m:getValue()if X and r then local B=n.cameraModule:getSpotPosition(X,r)I.artChanged:Fire(X.Adornee,r,B,t,u)end end n:setState({spots=Cryo.Dictionary.join(n.state.spots,{[r]={id=r;artId=t;ownerUserId=u}})})end end modules.onPromptShown=function(n)return function(I)n:setState({activeCanvasId=I})end end modules.onPromptHidden=function(n)return function()n:setState({activeCanvasId=Roact.None})end end modules.hideHumanoids=function(n)return function()for n,I in pairs(n.humanoidControllers)do I:hide()end end end modules.showHumanoids=function(n)return function()for n,I in pairs(n.humanoidControllers)do I:show()end end end
--[[
	Returns the next canvas state given new canvas information.
]]
local function buildNextState(canvases)
	local nextState = {
		canvases = {},
		spots = {},
		isLoaded = {},
	}

	for _, canvas in ipairs(canvases) do
		nextState.canvases[canvas.id] = {
			spots = Cryo.List.map(canvas.spots, function(spot)
				return spot.id
			end),
		}

		for _, spot in ipairs(canvas.spots) do
			nextState.spots[spot.id] = spot
		end

		nextState.isLoaded[canvas.id] = true
	end

	return function(state)
		return {
			canvases = Cryo.Dictionary.join(state.canvases, nextState.canvases),
			spots = Cryo.Dictionary.join(state.spots, nextState.spots),
			isLoaded = Cryo.Dictionary.join(state.isLoaded, nextState.isLoaded),
		}
	end
end

modules.fetchAllArt = function(self)
	return function()
		local canvases = {}
		for _, model in ipairs(self.getTagged()) do
			local canvasId = model:GetAttribute(constants.Attributes.SurfaceCanvasId)
			local isSuccess, canvas = self.props.GetCanvas:InvokeServer(canvasId)
			if not isSuccess then
				warn("[SurfaceArt] Invalid GetCanvas call: " .. canvas)
				continue
			end

			table.insert(canvases, canvas)
		end

		local nextState = buildNextState(canvases)
		self:setState(nextState)
	end
end

modules.fetchArt = function(self)
	return function(canvasId)
		local isSuccess, canvas = self.props.GetCanvas:InvokeServer(canvasId)
		if not isSuccess then
			warn("[SurfaceArt] Invalid GetCanvas call: " .. canvas)
			return
		end

		local nextState = buildNextState({ canvas })
		self:setState(nextState)
	end
end

modules.applyArt = function(self)
	return function(artId)
		if self.isAnimating then
			return
		end

		local spotId = self.state.activeSpotId
		local isSuccess, res = self.props.ApplyArt:InvokeServer(spotId, artId)
		if not isSuccess then
			self:setState({ errorMessage = res })
			self.cleanup()
			return
		end

		task.spawn(function()
			self.isAnimating = true
			self:setState({ activeSpotId = Roact.None, errorMessage = Roact.None })

			-- Wait for the surface spot animation to be applied before we animate the camera out
			-- again
			task.wait(1)

			-- Show humanoids before animating camera
			self.showHumanoids()

			self.cameraModule:animateToHumanoidAsync()

			-- Wait for humanoids to complete hiding
			self.waitForHumanoidAnimations()

			self.userInputController:enable()
			self.guiController:enable()

			self.isAnimating = false
		end)
	end
end

modules.getRandomSpot = function(self)
	return function(canvasId)
		if self.state.activeSpotId then
			return
		end

		if self.isAnimating then
			return
		end

		self.userInputController:disable()
		self.guiController:disable()

		task.spawn(function()
			self.isAnimating = true

			local surfaceGui = self.canvasRefs[canvasId]:getValue()
			self.cameraModule:animateToCanvasAsync(surfaceGui)

			local isSuccess, res = self.props.GetRandomSpot:InvokeServer(canvasId)
			if not isSuccess then
				self:setState({ errorMessage = res })
				self.cleanup()
				self.isAnimating = false

				return
			end

			self.hideHumanoids()

			local spotFrame = self.spotRefs[res]:getValue()

			self.cameraModule:animateToSpotAsync(spotFrame)

			self:setState({ activeSpotId = res })

			-- Wait for humanoids to complete hiding before setting flag
			self.waitForHumanoidAnimations()

			self.isAnimating = false
		end)
	end
end

modules.clearErrorMessage = function(self)
	return function()
		self:setState({ errorMessage = Roact.None })
	end
end

modules.onCancel = function(self)
	return function()
		if self.isAnimating then
			return
		end

		self:setState({ activeSpotId = Roact.None })
		task.spawn(function()
			self.isAnimating = true

			self.showHumanoids()
			self.cameraModule:animateToHumanoidAsync()

			self.guiController:enable()
			self.userInputController:enable()

			-- Wait for humanoids to complete hiding before setting flag
			self.waitForHumanoidAnimations()

			self.isAnimating = false
		end)
	end
end

modules.onSpotChanged = function(self, events)
	return function(canvasId, spotId, artId, ownerUserId)
		-- Fire an event for developers to hook to only if an art is placed
		local canvasRef = self.canvasRefs[canvasId]
		local spotRef = self.spotRefs[spotId]
		if canvasRef and spotRef then
			local surfaceGui = canvasRef:getValue()
			local spot = spotRef:getValue()

			if surfaceGui and spot then
				local spotPosition = self.cameraModule:getSpotPosition(surfaceGui, spot)
				events.artChanged:Fire(surfaceGui.Adornee, spot, spotPosition, artId, ownerUserId)
			end
		end

		self:setState({
			spots = Cryo.Dictionary.join(self.state.spots, {
				[spotId] = { id = spotId, artId = artId, ownerUserId = ownerUserId },
			}),
		})
	end
end

modules.onPromptShown = function(self)
	return function(canvasId)
		self:setState({ activeCanvasId = canvasId })
	end
end

modules.onPromptHidden = function(self)
	return function()
		self:setState({ activeCanvasId = Roact.None })
	end
end

modules.hideHumanoids = function(self)
	return function()
		for _, humanoidController in pairs(self.humanoidControllers) do
			humanoidController:hide()
		end
	end
end

modules.showHumanoids = function(self)
	return function()
		for _, humanoidController in pairs(self.humanoidControllers) do
			humanoidController:show()
		end
	end
end
