local function F(F)local w={canvases={};spots={},isLoaded={}}for F,v in ipairs(F)do w.canvases[v.id]={spots=Cryo.List.map(v.spots,function(F)return F.id end)}for F,v in ipairs(v.spots)do w.spots[v.id]=v end w.isLoaded[v.id]=true end return function(F)return{canvases=Cryo.Dictionary.join(F.canvases,w.canvases);spots=Cryo.Dictionary.join(F.spots,w.spots);isLoaded=Cryo.Dictionary.join(F.isLoaded,w.isLoaded)}end end modules.fetchAllArt=function(w)return function()local v={}for F,B in ipairs(w.getTagged())do local b=B:GetAttribute(constants.Attributes.SurfaceCanvasId)local x,M=w.props.GetCanvas:InvokeServer(b)if not x 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 "..M)continue end table.insert(v,M)end local B=F(v)w:setState(B)end end modules.fetchArt=function(w)return function(v)local B,b=w.props.GetCanvas:InvokeServer(v)if not B 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)return end local x=F({b})w:setState(x)end end modules.applyArt=function(F)return function(w)if F.isAnimating then return end local v=F.state.activeSpotId local B,b=F.props.ApplyArt:InvokeServer(v,w)if not B then F:setState({errorMessage=b})F.cleanup()return end task.spawn(function()F.isAnimating=true F:setState({activeSpotId=Roact.None,errorMessage=Roact.None})task.wait(1)F.showHumanoids()F.cameraModule:animateToHumanoidAsync()F.waitForHumanoidAnimations()F.userInputController:enable()F.guiController:enable()F.isAnimating=false end)end end modules.getRandomSpot=function(F)return function(w)if F.state.activeSpotId then return end if F.isAnimating then return end F.userInputController:disable()F.guiController:disable()task.spawn(function()F.isAnimating=true local v=F.canvasRefs[w]:getValue()F.cameraModule:animateToCanvasAsync(v)local B,b=F.props.GetRandomSpot:InvokeServer(w)if not B then F:setState({errorMessage=b})F.cleanup()F.isAnimating=false return end F.hideHumanoids()local x=F.spotRefs[b]:getValue()F.cameraModule:animateToSpotAsync(x)F:setState({activeSpotId=b})F.waitForHumanoidAnimations()F.isAnimating=false end)end end modules.clearErrorMessage=function(F)return function()F:setState({errorMessage=Roact.None})end end modules.onCancel=function(F)return function()if F.isAnimating then return end F:setState({activeSpotId=Roact.None})task.spawn(function()F.isAnimating=true F.showHumanoids()F.cameraModule:animateToHumanoidAsync()F.guiController:enable()F.userInputController:enable()F.waitForHumanoidAnimations()F.isAnimating=false end)end end modules.onSpotChanged=function(F,w)return function(v,B,b,x)local M=F.canvasRefs[v]local j=F.spotRefs[B]if M and j then local v=M:getValue()local B=j:getValue()if v and B then local M=F.cameraModule:getSpotPosition(v,B)w.artChanged:Fire(v.Adornee,B,M,b,x)end end F:setState({spots=Cryo.Dictionary.join(F.state.spots,{[B]={id=B,artId=b;ownerUserId=x}})})end end modules.onPromptShown=function(F)return function(w)F:setState({activeCanvasId=w})end end modules.onPromptHidden=function(F)return function()F:setState({activeCanvasId=Roact.None})end end modules.hideHumanoids=function(F)return function()for F,w in pairs(F.humanoidControllers)do w:hide()end end end modules.showHumanoids=function(F)return function()for F,w in pairs(F.humanoidControllers)do w:show()end end end

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
