--[[
	RoSession client half (copied into StarterPlayerScripts by RoSession.start).
	Reports what the server can't see: device, FPS, seconds since the last input,
	what the player is doing second by second, the open menu, and client errors.
	Sent to the server over ReplicatedStorage.RoSessionRemote every 30 seconds,
	and right away when the Roblox menu opens (people often leave from there).

	Your game can tell it more:
	  - the server's RoSession.activity(player, "Fishing") sets what they're doing
	  - tag a menu Frame "RoSessionMenu" (CollectionService) and it counts as open
	    while visible; its "RoSessionMenu" attribute (or its Name) is the label
]]

local CollectionService = game:GetService("CollectionService")
local GuiService = game:GetService("GuiService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local ScriptContext = game:GetService("ScriptContext")
local UserInputService = game:GetService("UserInputService")
local VRService = game:GetService("VRService")

local TICK_SECONDS = 30
local AFK_SECONDS = 60
local MENU_TAG = "RoSessionMenu"

local player = Players.LocalPlayer
local remote = ReplicatedStorage:WaitForChild("RoSessionRemote", 30)
if not remote then
	return -- RoSession isn't running on the server
end

local lastInput = os.clock()
local frames, frameTime = 0, 0
local act = {}
local lastSent = 0

local function visible(gui)
	local node = gui
	while node and node ~= game do
		if node:IsA("GuiObject") and not node.Visible then
			return false
		elseif node:IsA("LayerCollector") then
			return node.Enabled
		end
		node = node.Parent
	end
	return false
end

local function openMenu()
	if GuiService.MenuIsOpen then
		return "RobloxMenu"
	end
	local playerGui = player:FindFirstChildOfClass("PlayerGui")
	for _, gui in ipairs(CollectionService:GetTagged(MENU_TAG)) do
		if gui:IsA("GuiObject") and playerGui and gui:IsDescendantOf(playerGui) and visible(gui) then
			local name = gui:GetAttribute(MENU_TAG)
			return type(name) == "string" and name or gui.Name
		end
	end
	return nil
end

local function activity(idle)
	local set = player:GetAttribute("RoSessionActivity")
	if type(set) == "string" and set ~= "" then
		return set
	elseif idle >= AFK_SECONDS then
		return "AFK"
	end
	local character = player.Character
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	if humanoid and humanoid.MoveDirection.Magnitude > 0.1 then
		return "Moving"
	end
	return "Idle"
end

local function platform()
	if VRService.VREnabled then
		return "VR"
	elseif GuiService:IsTenFootInterface() then
		return "Console"
	elseif UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled then
		local size = workspace.CurrentCamera and workspace.CurrentCamera.ViewportSize or Vector2.new(1000, 1000)
		return math.min(size.X, size.Y) < 600 and "Phone" or "Tablet"
	end
	return "Desktop"
end

local function send(urgent)
	local idle = os.clock() - lastInput
	remote:FireServer("Tick", {
		fps = frameTime > 0 and frames / frameTime or nil,
		idle = idle,
		activity = activity(idle),
		menu = openMenu(),
		act = act,
		urgent = urgent or nil,
	})
	act = {}
	frames, frameTime = 0, 0
	lastSent = os.clock()
end

local size = workspace.CurrentCamera and workspace.CurrentCamera.ViewportSize
remote:FireServer("Info", {
	platform = platform(),
	device = size and string.format("%dx%d", size.X, size.Y) or nil,
})

local function touched()
	lastInput = os.clock()
end
UserInputService.InputBegan:Connect(touched)
UserInputService.InputChanged:Connect(function(input)
	if input.UserInputType ~= Enum.UserInputType.MouseMovement or input.Delta.Magnitude > 2 then
		touched()
	end
end)

RunService.RenderStepped:Connect(function(dt)
	frames += 1
	frameTime += dt
end)

-- client script errors (the server rate-limits them too)
ScriptContext.Error:Connect(function(message, trace)
	remote:FireServer("Error", string.sub(message, 1, 500), string.sub(trace or "", 1, 2000))
end)

-- people often leave from the Roblox menu: report it as soon as it opens
GuiService.MenuOpened:Connect(function()
	if os.clock() - lastSent > 3 then
		send(true)
	end
end)

while true do
	task.wait(1)
	local idle = os.clock() - lastInput
	local menu = openMenu()
	local key = menu and ("Menu: " .. menu) or activity(idle)
	act[key] = (act[key] or 0) + 1
	if os.clock() - lastSent >= TICK_SECONDS then
		send(false)
	end
end
