--!strict
--[[
	Roles (ModuleScript)
	Location: ServerScriptService.PermissionService.Roles  (child of PermissionService)

	PURE DATA. No logic lives here on purpose. This module only DECLARES the
	role lattice; the PermissionService resolves it. Keeping the "what exists"
	separate from the "how we decide" means you can re-tune the whole game's
	access model by editing this one table, and never touch the resolver.

	------------------------------------------------------------------------
	DESIGN: there are TWO independent axes, not one ladder.
	------------------------------------------------------------------------
	  * PERK TIER   : user -> vip -> vip+ -> mvp -> mvp+
	                  Linear, paid, cosmetic/economic. Each tier INHERITS the
	                  one below it, so we only ever declare what's NEW.

	  * STAFF TRACK : mod  -> admin -> owner
	                  A SEPARATE line. Staff is not "a really high paid tier" --
	                  a moderator is not a superset of mvp+, and mvp+ is not a
	                  superset of mod. They grant different things. Modelling
	                  them as one integer would be wrong, which is exactly why
	                  capabilities are NAMED SETS, not a privilege number.

	A real player therefore carries a perk tier AND (optionally) a staff role.
	The resolver unions the capabilities of both.

	------------------------------------------------------------------------
	Two kinds of grant, deliberately separated:
	------------------------------------------------------------------------
	  * capabilities : a SET of named booleans -- "what you may DO"
	  * perks        : NUMBERS/values that vary by tier -- "what you GET"
	                   A perk is NOT a permission; "how big is your bonus" is
	                   not a yes/no question, so it is not in the capability set.
--]]

local Roles = {}

-- Capabilities are referenced by name everywhere else. Centralising the string
-- constants here means a typo is a nil reference you can catch, not a silent
-- "permission denied" you chase for an hour.
Roles.Capability = {
	-- staff / moderation
	Kick         = "kick",
	Ban          = "ban",
	SoftShutdown = "softShutdown", -- honest name for "force restart": kick/teleport everyone, let the place relaunch
	RunCommands  = "runCommands",  -- honest name for "console": run privileged admin commands (e.g. a Cmdr-style system)
	ViewLogs     = "viewLogs",
	-- movement / world
	Fly          = "fly",
	Interact     = "interact",     -- talk to / use NPCs and items (the floor every tier stands on)
	-- kits (one capability per kit; higher tiers accumulate them via inheritance)
	KitVip       = "kit:vip",
	KitVipPlus   = "kit:vipplus",
	KitMvp       = "kit:mvp",
	KitMvpPlus   = "kit:mvpplus",
}
local C = Roles.Capability

-- Perk keys, same rationale as capability names.
Roles.Perk = {
	RewardMultiplier = "rewardMultiplier",
}
local P = Roles.Perk

-- A single role definition. Typed explicitly so the resolver can index the
-- table by an arbitrary string without the analyzer complaining.
type RoleDef = {
	inheritsFrom: string?,
	capabilities: { [string]: boolean },
	perks: { [string]: any },
}

--[[
	The role definitions. Each is tiny because inheritance carries the rest:
	vip+ does NOT re-list "interact" or the vip kit -- it inherits them. That
	brevity is the model doing its job, and it's the part that reads as
	"structured" rather than copy-pasted.
--]]
local Definitions: { [string]: RoleDef } = {

	------------------------------------------------------------------ PERK TIER
	user = {
		capabilities = { [C.Interact] = true }, -- move/use items/talk to NPCs
		perks = { [P.RewardMultiplier] = 1 },   -- baseline; everything above multiplies up from here
	},

	vip = {
		inheritsFrom = "user",
		capabilities = { [C.KitVip] = true },
		perks = { [P.RewardMultiplier] = 2 },
	},

	["vip+"] = {
		inheritsFrom = "vip",
		capabilities = { [C.KitVipPlus] = true },
		perks = { [P.RewardMultiplier] = 2.5 },
	},

	mvp = {
		inheritsFrom = "vip+",
		capabilities = { [C.KitMvp] = true },
		perks = { [P.RewardMultiplier] = 3 },
	},

	["mvp+"] = {
		inheritsFrom = "mvp",
		capabilities = { [C.KitMvpPlus] = true },
		perks = { [P.RewardMultiplier] = 4 },
	},

	----------------------------------------------------------------- STAFF TRACK
	-- Separate line. Staff roles do NOT inherit the paid perk tiers: a mod gets
	-- moderation powers, not the MVP kit. If you ever want a staffer to ALSO
	-- carry a perk tier, you express that on the PLAYER (baseRole = "mvp",
	-- staffRole = "mod") -- never by tangling the two lattices together here.
	mod = {
		capabilities = {
			[C.Kick] = true,
			[C.Ban]  = true,
			[C.Fly]  = true,
		},
		perks = {},
	},

	admin = {
		inheritsFrom = "mod",
		capabilities = {
			[C.RunCommands]  = true,
			[C.SoftShutdown] = true,
			[C.ViewLogs]     = true,
		},
		perks = {},
	},

	owner = {
		inheritsFrom = "admin",
		capabilities = {}, -- adds nothing today; exists as a home for owner-only powers later
		perks = {},
	},
}
Roles.Definitions = Definitions

-- Perk tiers in ascending order. The resolver uses this to pick the HIGHEST
-- owned tier and to guarantee a purchase can never DOWNGRADE a player.
local TierOrder: { string } = { "user", "vip", "vip+", "mvp", "mvp+" }
Roles.TierOrder = TierOrder

--[[
	GamePass ID -> the perk tier it grants. REPLACE the placeholders with your
	real GamePass IDs from the Creator dashboard. This is data, not logic:
	PermissionService reads it after verifying ownership server-side.
	It grants PERK TIERS ONLY -- staff powers are never purchasable.
--]]
local GamePassToTier: { [number]: string } = {
	-- [123456] = "vip",
	-- [123457] = "vip+",
	-- [123458] = "mvp",
	-- [123459] = "mvp+",
}
Roles.GamePassToTier = GamePassToTier

return Roles
