--!strict
--[[
	ModerationHandler (Script -- NOT a ModuleScript)
	Location: ServerScriptService.ModerationHandler

	The EXAMPLE that shows PermissionService doing its job in a real request
	flow. It demonstrates the trust boundary; it is not a complete moderation
	feature. Read it as: "a RemoteEvent is an untrusted RPC endpoint, and this is
	the server-side handler that authorizes before acting."

	  client LocalScript fires  ModerationRemote:FireServer("kick", targetPlayer)
	      |
	      v  the engine injects the REAL firing player as arg #1 (unspoofable)
	  this handler builds an AuthRequest and calls PermissionService.authorize()
	      |
	      v  authorize() validates shape (forgery check) + checks capability + logs
	  ONLY if it returns true do we perform the privileged action
--]]

local ReplicatedStorage   = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")

local PermissionService = require(ServerScriptService.PermissionService)
local Capability = PermissionService.Capability

-- The client fires this remote to REQUEST an action. Created here if absent so
-- the example is self-contained; in a larger project a dedicated bootstrap
-- script would own remote creation.
local function getOrCreateRemote(): RemoteEvent
	local existing = ReplicatedStorage:FindFirstChild("ModerationRemote")
	if existing and existing:IsA("RemoteEvent") then
		return existing
	end
	local created = Instance.new("RemoteEvent")
	created.Name = "ModerationRemote"
	created.Parent = ReplicatedStorage
	return created
end
local ModerationRemote = getOrCreateRemote()

-- Each action maps to the capability it requires and how to perform it. Driving
-- this from data keeps the handler flat and makes "what does this action need"
-- auditable at a glance.
type ActionSpec = {
	capability: string,
	requiresTarget: boolean,
	perform: (target: Player) -> (),
}

local ACTIONS: { [string]: ActionSpec } = {
	kick = {
		capability = Capability.Kick,
		requiresTarget = true,
		perform = function(target: Player)
			target:Kick("You have been removed by a moderator.")
		end,
	},
	-- ban / fly / softShutdown would slot in here the same way. Left out to keep
	-- the example focused -- the PATTERN is the point, not the action list. (A
	-- production "ban" would call Players:BanAsync for a real cross-server ban.)
}

-- IMPORTANT: actor is arg #1, injected by the engine -- the real sender, which
-- cannot be spoofed. We NEVER read identity from the rest of the payload;
-- `action` and `target` are attacker-controlled and typed `any` to say so.
ModerationRemote.OnServerEvent:Connect(function(actor: Player, action: any, target: any)
	-- An action string we don't recognise: could be our own client with a typo,
	-- could be probing. authorize() isn't involved yet, so just ignore it.
	local spec = type(action) == "string" and ACTIONS[action] or nil
	if not spec then
		return
	end

	-- Build the request from the TRUSTED identity (actor) and the UNTRUSTED
	-- payload (target). authorize() validates the shape, decides, and logs.
	local approved = PermissionService.authorize({
		actor = actor,
		capability = spec.capability,
		target = target,                 -- may be garbage; validateShape handles it
		requiresTarget = spec.requiresTarget,
	})

	if not approved then
		-- Denied or forged. authorize() already logged it at the right severity.
		-- We do nothing; the attack -- if it was one -- has already failed safely.
		return
	end

	-- Authorized. authorize() confirmed target is a real Player (requiresTarget),
	-- so the cast is safe.
	spec.perform(target :: Player)
end)
