--[[
	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