--[[
	Cerebro - Command Registry
	Central registry for all addon commands with RBAC enforcement
]]--

local AddonName, PrivateTable = ...
local Cerebro = LibStub("AceAddon-3.0"):GetAddon(AddonName)

-- Command Registry Module
local CommandRegistry = {}

-- Shared helper: normalize a player key for case-insensitive, realm-normalized comparison
-- Used by delete_player, purge_player, and block_player handlers.
local function NormalizePlayerKey(key)
	local name, realm = strsplit("-", key)
	if realm then
		return (name or ""):lower() .. "-" .. Cerebro:NormalizeRealmString(realm):lower()
	end
	return key:lower()
end

-- Command list (authoritative source)
local commands = {
	-- UI Commands
	{
		id = "toggle_ui",
		displayName = "Toggle Main UI",
		description = "Opens or closes the main Cerebro window",
		officerOnly = false,
		slash = "/cb show or /cb",
		handler = function(ctx)
			if Cerebro.modules.UI then
				Cerebro.modules.UI:Toggle()
			else
				Cerebro:Print("UI module not loaded.")
			end
		end
	},
	{
		id = "show_standings",
		displayName = "Show Standings",
		description = "Opens the standings tab",
		officerOnly = false,
		slash = "/cb standings",
		handler = function(ctx)
			if Cerebro.modules.UI then
				Cerebro.modules.UI:ShowStandings()
			else
				Cerebro:Print("UI module not loaded.")
			end
		end
	},
	{
		id = "show_history",
		displayName = "Show History",
		description = "Opens the EPGP transaction history",
		officerOnly = false,
		slash = "/cb history",
		handler = function(ctx)
			if Cerebro.modules.UI then
				Cerebro.modules.UI:ShowHistory()
			else
				Cerebro:Print("UI module not loaded.")
			end
		end
	},
	
	-- Diagnostics Commands
	{
		id = "show_status",
		displayName = "Show Status",
		description = "Displays addon status, role, and diagnostics",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		slash = "/cb status [--json]",
		-- FIX #1: Use GetModule instead of Cerebro.modules.Diagnostics
		handler = function(ctx, argv)
			local diagnostics = Cerebro:GetModule("Diagnostics", true)
			if diagnostics then
				diagnostics:ShowStatus(argv)
			else
				Cerebro:Print("Diagnostics module not loaded.")
			end
		end
	},
	{
		id = "self_test",
		displayName = "Run Self-Test",
		description = "Runs registered health checks with PASS/FAIL summary",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		slash = "/cb selftest [--debug]",
		handler = function(ctx, argv)
			local diagnostics = Cerebro:GetModule("Diagnostics", true)
			if diagnostics and diagnostics.RunSelfTest then
				diagnostics:RunSelfTest(argv)
			else
				Cerebro:Print("Diagnostics module not loaded.")
			end
		end
	},
	{
		id = "audit_officers",
		displayName = "Audit Officer Detection",
		description = "Lists all players in standings with their guild rank and IsOfficer() status",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		aliases = {"auditofficers"},
		slash = "/cb auditofficers",
		handler = function(ctx)
			local players = Cerebro.db.global.players or {}
			local officers = {}
			local raiders = {}
			local unknown = {}
			
			for name, _ in pairs(players) do
				local rankName, _rankIndex = nil, nil
				
				-- Look up in guild roster
				for i = 1, GetNumGuildMembers() do
					local guildName, guildRank, _guildRankIndex = GetGuildRosterInfo(i)
				if guildName and Cerebro:GetFullPlayerName(guildName) == name then
					rankName = guildRank
					break
				end
				end
				
				local shortName = Cerebro:GetShortPlayerName(name) or name
				local isOfficer = Cerebro:IsOfficer(name)
				
				if not rankName then
					table.insert(unknown, shortName)
				elseif isOfficer then
					table.insert(officers, string.format("%s(%s)", shortName, rankName))
				else
					table.insert(raiders, string.format("%s(%s)", shortName, rankName))
				end
			end
			
			-- Compact output
			Cerebro:Print("=== Officer Audit ===")
			Cerebro:Print(string.format("|cff00ff00Officers (%d):|r %s", 
				#officers, #officers > 0 and table.concat(officers, ", ") or "None"))
			Cerebro:Print(string.format("|cff888888Raiders (%d):|r %s", 
				#raiders, #raiders > 0 and table.concat(raiders, ", ") or "None"))
			if #unknown > 0 then
				Cerebro:Print(string.format("|cffff8800Unknown (%d):|r %s", 
					#unknown, table.concat(unknown, ", ")))
			end
			Cerebro:Print("=====================")
		end
	},
	
	-- Session Commands (Officer)

	{
		id = "start_session",
		displayName = "Start Raid Session",
		description = "Begins a new raid session with attendance tracking",
		officerOnly = true,
		slash = "/cb start",
		handler = function(ctx)
			if Cerebro.modules.Session then
				Cerebro.modules.Session:StartSession()
			else
				Cerebro:Print("Session module not loaded.")
			end
		end
	},
	{
		id = "end_session",
		displayName = "End Raid Session",
		description = "Ends the current raid session",
		officerOnly = true,
		slash = "/cb end",
		handler = function(ctx)
			if Cerebro.modules.Session then
				Cerebro.modules.Session:EndSession()
			else
				Cerebro:Print("Session module not loaded.")
			end
		end
	},
	{
		id = "force_end_session",
		displayName = "Force End Session",
		description = "Nuclear cleanup: wipes current session with NO EP awards. Use when a session is stuck or corrupted.",
		officerOnly = true,
		slash = "/cb forceend",
		handler = function(ctx)
			if Cerebro.modules.Session then
				Cerebro.modules.Session:ForceEndSession()
			else
				Cerebro:Print("Session module not loaded.")
			end
		end
	},
	
	-- Preparation Commands (Officer)
	{
		id = "prep_check",
		displayName = "Run Prep Check",
		description = "Inspects raid members for gems, enchants, and consumables",
		officerOnly = true,
		slash = "/cb prep",
		handler = function(ctx)
			if Cerebro.modules.Preparation then
				Cerebro.modules.Preparation:CheckRaid()
			else
				Cerebro:Print("Preparation module not loaded.")
			end
		end
	},
	
	-- Communication Commands
	{
		id = "sync_data",
		displayName = "Force Sync",
		description = "Syncs EPGP data to/from raid (Officers push, Raiders request)",
		officerOnly = false,
		slash = "/cb sync",
		handler = function(ctx)
			-- Quarantine check with user-friendly message
			if Cerebro.modules.Communication and Cerebro.modules.Communication:IsQuarantined() then
				Cerebro:Print("|cffff8800Sync blocked:|r Quarantine active (recent reset).")
				Cerebro:Print("Use /cb quarantine to check status or /cb quarantine clear to override.")
				return
			end
			
			if Cerebro:IsOfficer() then
				Cerebro:RepairDatabase()
				if Cerebro.modules.Communication then
					local sent = Cerebro.modules.CommunicationV2:SyncToRaid()
					if sent then
						Cerebro:Print("Data synced to raid (Officer push).")
					else
						Cerebro:Print("|cffff8800Sync deferred until combat ends.|r")
					end
				end
			else
				if Cerebro.modules.Communication then
					Cerebro.modules.CommunicationV2:RequestSync()
				end
				Cerebro:Print("Sync requested from officers...")
			end
		end
	},
	{
		id = "version_check",
		displayName = "Version Check",
		description = "Scans guild for Cerebro versions and reports outdated clients",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"versioncheck", "vercheck"},
		slash = "/cb versioncheck",
		handler = function(ctx)
			if Cerebro.modules.CommunicationV2 then
				Cerebro.modules.CommunicationV2:VersionCheck()
			else
				Cerebro:Print("Communication module not loaded.")
			end
		end
	},
	{
		id = "sync_reset",
		displayName = "Force Full Sync Reset",
		description = "Clears all delta sync state and forces immediate full sync to all clients",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"syncreset"},
		slash = "/cb syncreset",
		handler = function(ctx)
			-- Clear delta sync state
			local s = Cerebro.db.global.syncState
			if s then
				s.pendingSyncs = {}
				s.dirtyPlayers = {}
				s.lastFullSyncAt = 0  -- Force full sync on next SyncToRaid
				s.dirtyRetainUntil = nil
			end
			
			-- Force immediate full sync
			if Cerebro.modules.Communication then
				Cerebro:RecalculateAllPR()
				local sent = Cerebro.modules.CommunicationV2:SyncToRaid()
				if sent then
					Cerebro:Print("|cff00ff00Sync state reset.|r Full sync sent to all clients.")
				else
					Cerebro:Print("|cff00ff00Sync state reset.|r |cffff8800Full sync deferred until combat ends.|r")
				end
			end
			
			-- Audit log
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("SYNC_RESET", "Forced full sync by " .. (ctx.sender or "officer"))
			end
		end
	},
	
	-- Sync Status (Officer)
	{
		id = "sync_status",
		displayName = "Sync Status",
		description = "Shows sync health for all raid members (Officer only)",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"syncstatus"},
		slash = "/cb syncstatus",
		handler = function(ctx, argv)
			if not IsInRaid() then
				Cerebro:Print("Not in a raid group. Sync status requires an active raid.")
				return
			end
			
			local numMembers = GetNumGroupMembers()
			local freshCount, staleCount, unknownSyncCount = 0, 0, 0
			local lines = {}
			local s = Cerebro.db.global.syncState or {}
			local peerStatus = s.peerStatus or {}
			
			for i = 1, numMembers do
				local name, _, _, _, class = GetRaidRosterInfo(i)
				if name then
					local fullName = Cerebro:GetFullPlayerName(name)
					local pData = Cerebro.db.global.players and Cerebro.db.global.players[fullName]
					local peer = peerStatus[fullName]
					
					-- DB status column
					local dbStatus
					if pData then
						if pData.status == "Core" then
							dbStatus = "|cff00ff00Core|r"
						else
							dbStatus = "|cffff8800" .. (pData.status or "?") .. "|r"
						end
					else
						dbStatus = "|cff888888no DB|r"
					end
					
					-- Freshness column (from peer tracking)
					local freshIcon, freshLabel, latencyStr
					if peer and peer.freshness then
						if peer.freshness == "FRESH" then
							freshIcon = "|cff00ff00\226\156\147|r"
							freshLabel = "|cff00ff00FRESH|r"
							freshCount = freshCount + 1
						elseif peer.freshness == "STALE" then
							freshIcon = "|cffff4444\226\156\151|r"
							freshLabel = "|cffff4444STALE|r"
							staleCount = staleCount + 1
						else
							freshIcon = "|cff888888?|r"
							freshLabel = "|cff888888UNKNOWN|r"
							unknownSyncCount = unknownSyncCount + 1
						end
						-- Show latency if available
						if peer.avgAckLatencyMs and peer.avgAckLatencyMs > 0 then
							latencyStr = string.format(" |cffaaaaaa(~%dms)|r", peer.avgAckLatencyMs)
						else
							latencyStr = ""
						end
					else
						freshIcon = "|cff888888?|r"
						freshLabel = "|cff888888no ACK|r"
						latencyStr = ""
						unknownSyncCount = unknownSyncCount + 1
					end
					
					local coloredName = Cerebro:ColorPlayerName(fullName, class)
					table.insert(lines, string.format("  %s %s  %s  %s%s",
						freshIcon, coloredName, dbStatus, freshLabel, latencyStr))
				end
			end
			
			-- Header
			Cerebro:Print(string.format("=== Raid Sync Status (%d members) ===", numMembers))
			
			-- Last broadcast info
			if (s.lastBroadcastAt or 0) > 0 then
				local age = time() - s.lastBroadcastAt
				local ageStr
				if age < 60 then
					ageStr = age .. "s ago"
				elseif age < 3600 then
					ageStr = math.floor(age / 60) .. "m ago"
				else
					ageStr = math.floor(age / 3600) .. "h ago"
				end
				Cerebro:Print("  Last broadcast: seq " .. (s.lastBroadcastSeq or "?") .. " (" .. ageStr .. ")")
			end
			
			-- Sort and print lines
			table.sort(lines)
			for _, line in ipairs(lines) do
				Cerebro:Print(line)
			end
			
			-- Summary
			Cerebro:Print(string.format("  Sync: |cff00ff00%d FRESH|r, |cffff4444%d STALE|r, |cff888888%d UNKNOWN|r",
				freshCount, staleCount, unknownSyncCount))
		end
	},
	
	-- Sync Audit (Diagnostic)
	{
		id = "sync_audit",
		displayName = "Sync Audit",
		description = "Compares per-player data across all online guild members to find sync discrepancies (Officer only)",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"syncaudit"},
		slash = "/cb syncaudit",
		handler = function(ctx)
			if Cerebro.modules.CommunicationV2 then
				Cerebro.modules.CommunicationV2:RequestSyncAudit()
			else
				Cerebro:Print("Communication module not loaded.")
			end
		end
	},
	
	-- Sync History (Officer) - retroactive force-push of full transaction log
	{
		id = "sync_history",
		displayName = "Sync History",
		description = "Force-pushes your full transaction log to all online officers (retroactive catch-up)",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"synchistory"},
		slash = "/cb synchistory",
		handler = function(ctx)
			if not Cerebro:IsOfficer() then
				Cerebro:Print("Officer permission required.")
				return
			end
			if not Cerebro.modules.CommunicationV2 then
				Cerebro:Print("Communication module not loaded.")
				return
			end
			local count = #(Cerebro.db.global.transactions or {})
			Cerebro:Print(string.format("Pushing %d history entries to officers...", count))
			Cerebro.modules.CommunicationV2:BroadcastAllTransactions()
		end
	},

	-- Sync Check (V1 Transport — uses same proven guild broadcast as VersionCheck)
	{
		id = "sync_check",
		displayName = "Sync Check",
		description = "Broadcasts sync check via V1 channel, collects peer hash responses for 5 seconds, reports convergence. Auto-syncs divergent peers.",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"synccheck"},
		slash = "/cb synccheck",
		handler = function(ctx)
			local comm = Cerebro.modules.Communication
			if not comm then
				Cerebro:Print("|cffff0000Error:|r Communication module not loaded.")
				return
			end

			if not IsInGuild() then
				Cerebro:Print("|cffff8800Not in guild.|r Sync check requires online peers.")
				return
			end

			comm:SyncCheckRequest()
		end
	},
	{
		id = "debug_hash",
		displayName = "Debug Hash",
		description = "Prints V2 roster/config hash inputs for cross-client debugging",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		aliases = {"debughash"},
		slash = "/cb debughash",
		handler = function(ctx)
			if Cerebro.modules.CommunicationV2 then
				Cerebro.modules.CommunicationV2:DebugHash()
			else
				Cerebro:Print("Communication module not loaded.")
			end
		end
	},
	
	-- UI Development
	{
		id = "show_test_ui",
		displayName = "UI Sandbox",
		description = "Opens the visual development sandbox for testing UI components",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		slash = "/cb testui",
		handler = function(ctx)
			if Cerebro.modules.UISandbox then
				Cerebro.modules.UISandbox:Toggle()
			else
				Cerebro:Print("UISandbox module not loaded.")
			end
		end
	},
	
	-- Testing Commands
	{
		id = "test_ui",
		displayName = "Run UI Tests",
		description = "Executes UI module unit tests",
		officerOnly = false,
		slash = "/testui",
		handler = function(ctx)
			if SlashCmdList["TESTUI"] then
				SlashCmdList["TESTUI"]()
			else
				Cerebro:Print("UI tests not loaded.")
			end
		end
	},
	{
		id = "test_prep",
		displayName = "Run Prep Tests",
		description = "Executes Preparation module unit tests",
		officerOnly = false,
		slash = "/testprep",
		handler = function(ctx)
			if SlashCmdList["TESTPREP"] then
				SlashCmdList["TESTPREP"]()
			else
				Cerebro:Print("Prep tests not loaded.")
			end
		end
	},
	{
		id = "test_rclc",
		displayName = "Run RCLC Tests",
		description = "Executes RCLC Bridge unit tests",
		officerOnly = false,
		slash = "/testrclc",
		handler = function(ctx)
			if SlashCmdList["TESTRCLC"] then
				SlashCmdList["TESTRCLC"]()
			else
				Cerebro:Print("RCLC tests not loaded.")
			end
		end
	},
	{
		id = "test_calc",
		displayName = "Run Calc Tests",
		description = "Executes Player calculation unit tests",
		officerOnly = false,
		slash = "/testcalc",
		handler = function(ctx)
			if SlashCmdList["TESTCALC"] then
				SlashCmdList["TESTCALC"]()
			else
				Cerebro:Print("Calc tests not loaded.")
			end
		end
	},
	{
		id = "test_decay",
		displayName = "Run Decay Tests",
		description = "Executes Decay unit tests",
		officerOnly = false,
		slash = "/testdecay",
		handler = function(ctx)
			if SlashCmdList["TESTDECAY"] then
				SlashCmdList["TESTDECAY"]()
			else
				Cerebro:Print("Decay tests not loaded.")
			end
		end
	},
	{
		id = "test_ledger",
		displayName = "Run Ledger Tests",
		description = "Executes Ledger unit tests",
		officerOnly = false,
		slash = "/testledger",
		handler = function(ctx)
			if SlashCmdList["TESTLEDGER"] then
				SlashCmdList["TESTLEDGER"]()
			else
				Cerebro:Print("Ledger tests not loaded.")
			end
		end
	},
	{
		id = "test_session",
		displayName = "Run Session Tests",
		description = "Executes Session unit tests",
		officerOnly = false,
		slash = "/testsession",
		handler = function(ctx)
			if SlashCmdList["TESTSESSION"] then
				SlashCmdList["TESTSESSION"]()
			else
				Cerebro:Print("Session tests not loaded.")
			end
		end
	},
	{
		id = "test_mplus",
		displayName = "Run M+ Tests",
		description = "Generate test M+ runs. Use --live to award EP, or 'target' to test sync.",
		officerOnly = false,
		slash = "/cb testmplus [count] [--live] OR /cb testmplus target Player-Realm [--live]",
		handler = function(ctx, argv)
			local subCmd = argv[1] and argv[1]:lower()
			
			-- Target mode: /cb testmplus target Player-Realm [--live]
			if subCmd == "target" then
				-- Dev gate: only in dev mode
				if not (SlashCmdList["TESTMPLUS"] or Cerebro._TEST_HARNESS_LOADED) then
					Cerebro:Print("|cffff0000Error:|r target mode requires dev deploy (--with-tests).")
					return
				end
				
				-- RBAC: Officer only
				if not Cerebro:IsOfficer() then
					Cerebro:Print("|cffff0000Error:|r target mode is officer-only.")
					return
				end
				
				local targetPlayer = argv[2]
				local isLive = false
				for i = 3, #argv do
					if argv[i] == "--live" then isLive = true end
				end
				
				local mplus = Cerebro:GetModule("MythicPlus", true)
				if mplus and mplus.AwardTargetEP then
					mplus:AwardTargetEP(targetPlayer, isLive)
				else
					Cerebro:Print("M+ module not loaded.")
				end
				return
			end
			
			-- Standard mode: /cb testmplus [count] [--live]
			local count = 1
			local isLive = false
			
			for i = 1, #argv do
				if argv[i] == "--live" then
					isLive = true
				elseif tonumber(argv[i]) then
					count = tonumber(argv[i])
				end
			end
			
			local mplus = Cerebro:GetModule("MythicPlus", true)
			if mplus and mplus.GenerateTestRuns then
				mplus:GenerateTestRuns(count, isLive)
			else
				Cerebro:Print("M+ module not loaded.")
			end
		end
	},
	{
		id = "test_audit",
		displayName = "Run Audit Tests",
		description = "Executes Audit logging unit tests",
		officerOnly = false,
		slash = "/testaudit",
		handler = function(ctx)
			if SlashCmdList["TESTAUDIT"] then
				SlashCmdList["TESTAUDIT"]()
			else
				Cerebro:Print("Audit tests not loaded.")
			end
		end
	},
	{
		id = "test_diag",
		displayName = "Run Diagnostics Tests",
		description = "Executes Diagnostics unit tests",
		officerOnly = false,
		slash = "/testdiag",
		handler = function(ctx)
			if SlashCmdList["TESTDIAG"] then
				SlashCmdList["TESTDIAG"]()
			else
				Cerebro:Print("Diagnostics tests not loaded.")
			end
		end
	},
	{
		id = "test_env",
		displayName = "Run Environment Tests",
		description = "Tests season tracking and game version utilities",
		officerOnly = false,
		slash = "/cb testenv",
		handler = function(_)
			Cerebro:Print("=== Season & Environment Tests ===")
			
			-- Raw Build Info (for debugging PTR detection)
			local version, build, _, toc = GetBuildInfo()
			Cerebro:Print(string.format("Raw Build: version=%s, build=%s, toc=%s", 
				tostring(version), tostring(build), tostring(toc)))
			Cerebro:Print(string.format("IsTestBuild() = %s", 
				IsTestBuild and tostring(IsTestBuild()) or "nil (function not found)"))
			Cerebro:Print(string.format("Realm: %s", GetRealmName() or "?"))
			
			-- Test 1: GetGameVersion
			local ver = Cerebro:GetGameVersion()
			local pass = type(ver) == "table" and type(ver.version) == "string"
			Cerebro:Print(string.format("%s GetGameVersion returns table (v%s)", 
				pass and "|cff00ff00PASS|r" or "|cffff0000FAIL|r", ver.version or "?"))
			
			-- Test 2: GetSeasonInfo
			local info = Cerebro:GetSeasonInfo()
			pass = type(info) == "table" and type(info.seasonID) == "number"
			Cerebro:Print(string.format("%s GetSeasonInfo returns table (Season %d)", 
				pass and "|cff00ff00PASS|r" or "|cffff0000FAIL|r", info.seasonID or -1))
			
			-- Test 3: GetCurrentSeasonWeek
			local week = Cerebro:GetCurrentSeasonWeek()
			pass = week == nil or type(week) == "number"
			Cerebro:Print(string.format("%s GetCurrentSeasonWeek returns %s", 
				pass and "|cff00ff00PASS|r" or "|cffff0000FAIL|r", week and ("Week " .. week) or "nil"))
			
			-- Test 4: IsReleaseBuild
			local isRelease = Cerebro:IsReleaseBuild()
			pass = type(isRelease) == "boolean"
			Cerebro:Print(string.format("%s IsReleaseBuild returns %s", 
				pass and "|cff00ff00PASS|r" or "|cffff0000FAIL|r", tostring(isRelease)))
			
			-- Test 5: SeasonStartDates
			pass = type(Cerebro.SeasonStartDates) == "table" and Cerebro.SeasonStartDates[13] ~= nil
			Cerebro:Print(string.format("%s SeasonStartDates has Season 13", 
				pass and "|cff00ff00PASS|r" or "|cffff0000FAIL|r"))
			
			Cerebro:Print("=== Tests Complete ===")
		end
	},
	
	-- Diagnostics
	{
		id = "version",
		displayName = "Show Version",
		description = "Displays addon version information",
		officerOnly = false,
		slash = "/cb version",
		handler = function(ctx)
			Cerebro:Print("Cerebro v" .. (Cerebro.version or "unknown"))
		end
	},
	{
		id = "help",
		displayName = "Show Help",
		description = "Displays available commands",
		officerOnly = false,
		slash = "/cb help",
		handler = function(ctx)
			Cerebro:ShowHelp()
		end
	},
	
	-- RCLC Loot History (formerly Wishlist)
	{
		id = "show_loothistory",
		displayName = "Show Loot History",
		description = "Opens the RCLC Loot History tab",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		aliases = {"loothistory", "lh"},
		slash = "/cb loothistory or /cb lh",
		handler = function(ctx)
			if Cerebro.modules.UI then
				Cerebro.modules.UI:ShowWishlist()  -- Reuses existing tab show method
			else
				Cerebro:Print("UI module not loaded.")
			end
		end
	},
	
	-- RCLC Import Operations (parent command with subcommands)
	{
		id = "rclc_command",
		displayName = "RCLC Operations",
		description = "RCLC import, refresh, and status commands",
		officerOnly = false,  -- Subcommands have their own gates
		hidden = false,
		dangerous = false,
		aliases = {"rclc"},
		slash = "/cb rclc [import|refresh|status|paste]",
		handler = function(ctx, argv)
			local subCmd = argv[1] and argv[1]:lower() or "help"
			
			local importModule = Cerebro:GetModule("RCLC_Import", true)
			if not importModule then
				Cerebro:Print("RCLC Import module not loaded. Is RCLootCouncil installed?")
				return
			end
			
			if subCmd == "import" then
				-- Officer-only import
				if not Cerebro:IsOfficer() then
					Cerebro:Print("RCLC import requires officer permissions.")
					return
				end
				
				local dateArg = argv[2]
				if not dateArg then
					Cerebro:Print("Usage: /cb rclc import MM-DD-YYYY")
					Cerebro:Print("  Imports RCLC loot data from the specified date forward.")
					return
				end
				
				local sinceDate = importModule:ParseDateArg(dateArg)
				if not sinceDate then
					Cerebro:Print("Invalid date format. Use: MM-DD-YYYY (e.g., 03-19-2026)")
					return
				end
				
				Cerebro:Print(string.format("Starting RCLC import from %s...", dateArg))
				importModule:ImportFromRCLC(sinceDate)
				
			elseif subCmd == "refresh" then
				-- All players can refresh the tab
				importModule._tabStale = true
				importModule:DetectNewRCLCDates()
				Cerebro:RefreshAllUI()
				Cerebro:Print("RCLC Loot History tab refreshed.")
				
			elseif subCmd == "status" then
				importModule:PrintStatus()
				
			elseif subCmd == "dryrun" or subCmd == "dry-run" or subCmd == "simulate" then
				-- Dry-run: safe simulation, no officer check needed
				local dateArg = argv[2]
				if not dateArg then
					Cerebro:Print("Usage: /cb rclc dryrun MM-DD-YYYY")
					Cerebro:Print("  Shows what an import WOULD do without modifying data.")
					return
				end
				
				local sinceDate = importModule:ParseDateArg(dateArg)
				if not sinceDate then
					Cerebro:Print("Invalid date format. Use: MM-DD-YYYY (e.g., 02-19-2026)")
					return
				end
				
				importModule:SimulateImport(sinceDate)
				
			else
				Cerebro:Print("=== RCLC Commands ===")
				Cerebro:Print("/cb rclc dryrun MM-DD-YYYY - Simulate import (safe, read-only)")
				Cerebro:Print("/cb rclc import MM-DD-YYYY - Import RCLC loot data (Officer)")
				Cerebro:Print("/cb rclc refresh - Refresh Loot History tab")
				Cerebro:Print("/cb rclc status - Show last import status")
			end
		end
	},
	{
		id = "show_intro",
		displayName = "Show Onboarding",
		description = "Re-shows the onboarding wizard",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		slash = "/cb intro",
		handler = function(ctx)
			local Onboarding = Cerebro:GetModule("Onboarding", true)
			if Onboarding then
				Onboarding:Show(true)  -- force=true to show even if completed
			else
				Cerebro:Print("Onboarding module not loaded.")
			end
		end
	},
	
	-- Configuration
	{
		id = "show_config",
		displayName = "Show Configuration",
		description = "Opens the Blizzard Interface Options panel for Cerebro",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		aliases = {"config", "options"},
		slash = "/cb config or /cb options",
		handler = function(ctx)
			if Cerebro.modules.UI and Cerebro.modules.UI.ShowConfig then
				Cerebro.modules.UI:ShowConfig()
			else
				-- Fallback to Blizzard panel
				InterfaceOptionsFrame_OpenToCategory(Cerebro.optionsFrame)
				InterfaceOptionsFrame_OpenToCategory(Cerebro.optionsFrame) -- Called twice for Blizz UI bug
			end
		end
	},
	
	-- Debug Toggle
	{
		id = "toggle_debug",
		displayName = "Toggle Debug Mode",
		description = "Controls debug output. Usage: /cb debug [simple|technical|both|on|off]",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		slash = "/cb debug [simple|technical|both|on|off]",
		handler = function(ctx)
			local arg = ctx.args and ctx.args:lower() or ""
			local validLevels = { simple = true, technical = true, both = true, off = true }
			local newLevel

			if arg == "on" or arg == "1" or arg == "true" then
				newLevel = "both"
			elseif arg == "off" or arg == "0" or arg == "false" then
				newLevel = "off"
			elseif validLevels[arg] then
				newLevel = arg
			elseif arg == "" then
				-- No argument: cycle off → simple → technical → both → off
				local current = Cerebro:GetDebugLevel()
				local cycle = { off = "simple", simple = "technical", technical = "both", both = "off" }
				newLevel = cycle[current] or "simple"
			else
				Cerebro:Print("Usage: /cb debug [simple|technical|both|on|off]")
				Cerebro:Print("  simple    — User-friendly sync/session messages")
				Cerebro:Print("  technical — Implementation detail (hashes, internal state)")
				Cerebro:Print("  both      — All debug messages")
				Cerebro:Print("  off       — No debug output")
				return
			end

			-- Update both fields for backward compatibility
			Cerebro.db.global.config.debugLevel = newLevel
			Cerebro.db.global.config.debug = (newLevel ~= "off")

			local labels = {
				off = "|cff888888OFF|r",
				simple = "|cff66bbffSIMPLE|r",
				technical = "|cff888888TECHNICAL|r",
				both = "|cff00ff00BOTH|r",
			}
			Cerebro:Print("Debug mode: " .. (labels[newLevel] or newLevel))
		end
	},

	-- Discord Export
	{
		id = "discord_export",
		displayName = "Discord Export",
		description = "Opens the Discord webhook export window",
		officerOnly = false,
		hidden = false,
		dangerous = false,
		slash = "/cb discord",
		handler = function(ctx)
			if Cerebro.modules.DiscordExport then
				Cerebro.modules.DiscordExport:ShowExportWindow()
			else
				Cerebro:Print("DiscordExport module not loaded.")
			end
		end
	},
	
	-- Verification
	{
		id = "run_verification",
		displayName = "Run Logic Verification",
		description = "Executes internal EPGP logic tests",
		officerOnly = false,
		hidden = true, -- Diagnostic command
		dangerous = false,
		slash = "/cb verify",
		handler = function(ctx)
			Cerebro:RunVerification()
		end
	},
	
	-- Award Points (EP/GP)
	{
		id = "award_points",
		displayName = "Award Points",
		description = "Adds EP or GP to a player",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		slash = "/cb add [player] [amount] (ep/gp) [reason...]",
		usage = "/cb add [player] [amount] (ep/gp) [reason...]",
		examples = {
			"/cb add Shezzfoo 100 Bonus",
			"/cb add Shezzfoo 50 gp Loot award",
		},
		argsSchema = {
			{name = "player", type = "string", required = true},
			{name = "amount", type = "number", required = true},
			{name = "type", type = "string", default = "ep", values = {"ep", "gp"}},
			{name = "reason", type = "string", variadic = true, default = "Officer Adjust"},
		},
		handler = function(ctx, argv)
			local player = argv[1]
			local amount = tonumber(argv[2])
			local typeArg = (argv[3] and argv[3]:lower()) or "ep"
			local reasonStart = 4
			
			-- Check if arg3 is type or reason
			if typeArg ~= "ep" and typeArg ~= "gp" then
				typeArg = "ep"
				reasonStart = 3
			end
			
			local reason = table.concat(argv, " ", reasonStart)
			if reason == "" then reason = "Officer Adjust" end
			
			if not player or not amount then
				Cerebro:Print("Usage: /cb add [player] [amount] (ep/gp) [reason]")
				return
			end
			
			local fullName = Cerebro:GetFullPlayerName(player)
			
			if typeArg == "gp" then
				Cerebro.modules.EPGP:ChargeGP(fullName, amount, "Manual", reason)
				Cerebro:Print(string.format("Added %d GP to %s.", amount, player))
			else
				Cerebro.modules.EPGP:AwardEP(fullName, amount, "OFFICER_ADJUST", reason)
				Cerebro:Print(string.format("Added %d EP to %s.", amount, player))
			end
		end
	},
	
	-- Subtract Points
	{
		id = "subtract_points",
		displayName = "Subtract Points",
		description = "Subtracts EP or GP from a player",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"sub", "subtract"},
		slash = "/cb sub [player] [amount] (ep/gp) [reason...]",
		usage = "/cb sub [player] [amount] (ep/gp) [reason...]",
		argsSchema = {
			{name = "player", type = "string", required = true},
			{name = "amount", type = "number", required = true},
			{name = "type", type = "string", default = "ep", values = {"ep", "gp"}},
			{name = "reason", type = "string", variadic = true, default = "Officer Adjust"},
		},
		handler = function(ctx, argv)
			local player = argv[1]
			local amount = tonumber(argv[2])
			local typeArg = (argv[3] and argv[3]:lower()) or "ep"
			local reasonStart = 4
			
			if typeArg ~= "ep" and typeArg ~= "gp" then
				typeArg = "ep"
				reasonStart = 3
			end
			
			local reason = table.concat(argv, " ", reasonStart)
			if reason == "" then reason = "Officer Adjust" end
			
			if not player or not amount then
				Cerebro:Print("Usage: /cb sub [player] [amount] (ep/gp) [reason]")
				return
			end
			
			local fullName = Cerebro:GetFullPlayerName(player)
			
			if typeArg == "gp" then
				Cerebro.modules.EPGP:ChargeGP(fullName, -amount, "Manual", reason)
				Cerebro:Print(string.format("Subtracted %d GP from %s.", amount, player))
			else
				Cerebro.modules.EPGP:AwardEP(fullName, -amount, "OFFICER_ADJUST", reason)
				Cerebro:Print(string.format("Subtracted %d EP from %s.", amount, player))
			end
		end
	},
	
	-- Readiness Award
	{
		id = "award_readiness",
		displayName = "Award Readiness EP",
		description = "Awards readiness EP (weekly capped) to a player",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		slash = "/cb readiness award [player] [amount]",
		usage = "/cb readiness award [player] [amount]",
		argsSchema = {
			{name = "subcommand", type = "string", required = true, values = {"award"}},
			{name = "player", type = "string", required = true},
			{name = "amount", type = "number", required = true},
		},
		handler = function(ctx, argv)
			local subCmd = argv[1] and argv[1]:lower()
			if subCmd ~= "award" then
				Cerebro:Print("Usage: /cb readiness award [player] [amount]")
				return
			end
			
			local player = argv[2]
			local amount = tonumber(argv[3])
			
			if not player or not amount then
				Cerebro:Print("Usage: /cb readiness award [player] [amount]")
				return
			end
			
			local fullName = Cerebro:GetFullPlayerName(player)
			local awarded = Cerebro.modules.EPGP:AwardReadinessEP(fullName, amount, "Manual Readiness")
			Cerebro:Print(string.format("Awarded %d Readiness EP to %s.", awarded, player))
		end
	},
	
	-- Admit Player to Core
	{
		id = "admit_player",
		displayName = "Admit Player to Core",
		description = "Promotes a Pending player to Core status",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"admit"},
		slash = "/cb admit [player]",
		usage = "/cb admit [player]",
		argsSchema = {
			{name = "player", type = "string", required = true},
		},
		handler = function(ctx, argv)
			local player = argv[1]
			
			if not player then
				Cerebro:Print("Usage: /cb admit [player]")
				return
			end
			
			local fullName = Cerebro:GetFullPlayerName(player)
			local data = Cerebro:GetPlayerData(fullName)
			
			if data.status == "Core" then
				Cerebro:Print(Cerebro:ColorPlayerName(fullName) .. " is already Core.")
				-- Still refresh UI and repair database in case they're not visible
				Cerebro:RepairDatabase()
				if Cerebro.modules.Communication then
					Cerebro.modules.CommunicationV2:SyncToRaid()
				end
				Cerebro:RefreshAllUI()
				return
			end
			
			data.status = "Core"
			data.coreAdmittedAt = time()  -- Track admission time for fair attendance %
			data.lastUpdate = time()  -- FIX: Critical for LWW sync - ensures Core status wins future merges
			Cerebro:MarkPlayerDirty(fullName, {"status", "coreAdmittedAt"})  -- Delta Sync v2
			
			-- Try to populate class from guild roster if missing
			if not data.class then
				for i = 1, GetNumGuildMembers() do
					local name, _, _, _, _, _, _, _, _, _, classFileName = GetGuildRosterInfo(i)
					if name and Cerebro:GetFullPlayerName(name) == fullName then
						data.class = classFileName
						Cerebro:Debug("Fetched class for " .. fullName .. ": " .. classFileName)
						break
					end
				end
			end
			
			Cerebro:Print(string.format("Admitted %s to Core.", Cerebro:ColorPlayerName(fullName)))
			
			-- Audit log
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("ADMIT_PLAYER", "Promoted " .. fullName .. " to Core status.")
			end
			
			-- Broadcast update (Real-time sync) - include class, coreAdmittedAt, and lastUpdate for LWW
			if Cerebro.modules.CommunicationV2 and Cerebro.modules.CommunicationV2.BroadcastPlayerUpdate then
				Cerebro.modules.CommunicationV2:BroadcastPlayerUpdate(fullName, {
					status = "Core", 
					class = data.class,
					coreAdmittedAt = data.coreAdmittedAt,
					lastUpdate = data.lastUpdate
				})
			end
			
			-- Refresh UI if open
			Cerebro:RefreshAllUI()
		end
	},
	
	-- Fix Database (DANGEROUS)
	{
		id = "fix_database",
		displayName = "Fix Database",
		description = "Removes invalid player entries from the database",
		officerOnly = true,
		hidden = true,
		dangerous = true,
		slash = "/cb fixdb YES",
		usage = "/cb fixdb YES",
		handler = function(ctx, argv)
			-- Dangerous command confirmation
			if ctx.source == "slash" and (not argv[1] or argv[1]:upper() ~= "YES") then
				Cerebro:Print("|cffff0000DANGEROUS:|r This command removes invalid player entries.")
				Cerebro:Print("Re-run with YES to confirm: |cff00ff00/cb fixdb YES|r")
				return
			end
			
			Cerebro:Print("Running database cleanup...")
			local count = 0
			local toDelete = {}
			
			for name, data in pairs(Cerebro.db.global.players) do
				local n = name:lower()
				local shortName = Cerebro:GetShortPlayerName(name):lower()
				
				if n == "player" or shortName == "player" or n == "unknown" or n:match("^unknown") or (not n:find("-")) then
					table.insert(toDelete, name)
				end
			end
			
			for _, name in ipairs(toDelete) do
				Cerebro.db.global.players[name] = nil
				count = count + 1
				Cerebro:Print("Removed invalid entry: " .. name)
			end
			
			if count > 0 then
				Cerebro:Print(string.format("Cleanup complete. Removed %d entries. Please /reload.", count))
				if Cerebro.Ledger then
					Cerebro.Ledger:AddAuditLog("DB_FIX", string.format("Cleaned up %d invalid entries: %s", count, table.concat(toDelete, ", ")))
				end
			else
				Cerebro:Print("Database seems clean (no invalid entries found).")
			end
		end
	},
	
	-- Purge All Pending Players (DANGEROUS)
	{
		id = "purge_pending",
		displayName = "Purge Pending Players",
		description = "Removes ALL Pending status players from the database",
		officerOnly = true,
		hidden = false,
		dangerous = true,
		slash = "/cb purgepending YES",
		usage = "/cb purgepending YES",
		argsSchema = {
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			-- Dangerous command confirmation
			if ctx.source == "slash" and (not argv[1] or argv[1]:upper() ~= "YES") then
				-- Count pending players first
				local count = 0
				for name, data in pairs(Cerebro.db.global.players) do
					if data.status == "Pending" then
						count = count + 1
					end
				end
				Cerebro:Print(string.format("|cffff0000DANGEROUS:|r This will remove %d Pending players from the database.", count))
				Cerebro:Print("Re-run with YES to confirm: |cff00ff00/cb purgepending YES|r")
				return
			end
			
			local count = 0
			local toDelete = {}
			
			for name, data in pairs(Cerebro.db.global.players) do
				if data.status == "Pending" then
					table.insert(toDelete, name)
				end
			end
			
			for _, name in ipairs(toDelete) do
				Cerebro.db.global.players[name] = nil
				count = count + 1
			end
			
			if count > 0 then
				Cerebro:Print(string.format("|cff00ff00Purged %d Pending players.|r Database cleaned.", count))
				if Cerebro.Ledger then
					Cerebro.Ledger:AddAuditLog("PURGE_PENDING", string.format("Removed %d Pending players from database.", count))
				end
				-- Refresh UI
				Cerebro:RefreshAllUI()
			else
				Cerebro:Print("No Pending players found in database.")
			end
		end
	},
	
	-- Delete Player (DANGEROUS)
	{
		id = "delete_player",
		displayName = "Delete Player",
		description = "Permanently removes a player from the database",
		officerOnly = true,
		hidden = true,
		dangerous = true,
		slash = "/cb delete [player] YES  (use quotes for realms with spaces)",
		usage = "/cb delete [player] YES",
		argsSchema = {
			{name = "player", type = "string", required = true},
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			local player = argv[1]
			
			if not player then
				Cerebro:Print("Usage: /cb delete [player] YES")
				Cerebro:Print("  Tip: Use quotes for realms with spaces: /cb delete \"Name-Area 52\" YES")
				Cerebro:Print("  Or use normalized name (no spaces): /cb delete Name-Area52 YES")
				return
			end
			
			-- Dangerous command confirmation
			if ctx.source == "slash" and (not argv[2] or argv[2]:upper() ~= "YES") then
				Cerebro:Print("|cffff0000DANGEROUS:|r This will permanently delete " .. player)
				Cerebro:Print("Re-run with YES to confirm: |cff00ff00/cb delete " .. player .. " YES|r")
				return
			end
			
			local inputNormalized = NormalizePlayerKey(player)
			
			-- Search case-insensitive (Pass 1: exact match)
			local targetKey = nil
			for k, v in pairs(Cerebro.db.global.players) do
				if k:lower() == player:lower() then
					targetKey = k
					break
				end
			end
			
			-- Pass 2: Try appending current realm
			if not targetKey then
				local withRealm = player .. "-" .. GetRealmName():gsub("%s", "")
				for k, v in pairs(Cerebro.db.global.players) do
					if k:lower() == withRealm:lower() then
						targetKey = k
						break
					end
				end
			end
			
			-- Pass 3: Normalized realm matching (handles "Area52" matching "Area 52")
			if not targetKey then
				for k, v in pairs(Cerebro.db.global.players) do
					if NormalizePlayerKey(k) == inputNormalized then
						targetKey = k
						break
					end
				end
			end
			
			if targetKey then
				-- Instead of deleting, set status to Pending so they can re-announce
				local data = Cerebro.db.global.players[targetKey]
				data.status = "Pending"
				-- Reset EP/GP to defaults
				data.ep = 0
				data.gp = Cerebro.db.global.config.epgp.baseGP or 200
				data.pr = 0
				data.lastUpdate = time()  -- FIX: Critical for LWW sync - ensures Pending status propagates
				Cerebro:MarkPlayerDirty(targetKey, {"status", "ep", "gp", "pr"})  -- Delta Sync v2
				
				Cerebro:Print("Reset " .. targetKey .. " to Pending status (can be re-admitted).")
				
				-- Audit log
				if Cerebro.Ledger then
					Cerebro.Ledger:AddAuditLog("PLAYER_RESET", "Reset to Pending: " .. targetKey)
				end
				
				-- Broadcast update and refresh
				if Cerebro.modules.CommunicationV2 and Cerebro.modules.CommunicationV2.BroadcastPlayerUpdate then
					Cerebro.modules.CommunicationV2:BroadcastPlayerUpdate(targetKey, {
						status = "Pending", 
						ep = 0, 
						gp = data.gp, 
						pr = 0,
						lastUpdate = data.lastUpdate
					})
				end
				Cerebro:RefreshAllUI()
			else
				Cerebro:Print("Player not found in database: " .. player)
			end
		end
	},
	
	-- Manual Epoch Set (Dangerous - Advanced)
	{
		id = "set_epoch",
		displayName = "Set Epoch",
		description = "Manually sets the sync epoch to a specific value. DANGEROUS.",
		officerOnly = true,
		hidden = true,
		dangerous = true,
		slash = "/cb setepoch [value] YES",
		usage = "/cb setepoch [value] YES",
		argsSchema = {
			{name = "value", type = "number", required = true},
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			local val = tonumber(argv[1])
			
			if ctx.source == "slash" and (not val or not argv[2] or argv[2]:upper() ~= "YES") then
				Cerebro:Print("|cffff0000DANGEROUS:|r Manually setting epoch can invalidate valid sync packets.")
				Cerebro:Print("Current Epoch: " .. (Cerebro.db.global.resetEpoch or 0))
				Cerebro:Print("Usage: |cff00ff00/cb setepoch [new_value] YES|r")
				return
			end
			
			local oldEpoch = Cerebro.db.global.resetEpoch or 0
			Cerebro.db.global.resetEpoch = val
			
			Cerebro:Print(string.format("|cff00ff00Epoch Updated:|r %d -> %d", oldEpoch, val))
			Cerebro:Print("Run /cb sync to broadcast this new epoch to the raid.")
			
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("MANUAL_EPOCH_SET", string.format("Changed from %d to %d by %s", oldEpoch, val, ctx.sender))
			end
		end
	},

	-- Season Reset (Manual)
	{
		id = "season_reset",
		displayName = "Season Reset",
		description = "Resets EP/GP/M+ for a new season (preserves history)",
		officerOnly = true,
		hidden = false,
		dangerous = true,
		aliases = {"seasonreset"},
		slash = "/cb seasonreset YES",
		usage = "/cb seasonreset YES",
		argsSchema = {
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			-- Dangerous command confirmation
			if ctx.source == "slash" and (not argv[1] or argv[1]:upper() ~= "YES") then
				Cerebro:Print("|cffff8800SEASON RESET:|r This will wipe EP/GP and M+ history for everyone.")
				Cerebro:Print("  • All player EP → 0")
				Cerebro:Print("  • All player GP → Base GP")
				Cerebro:Print("  • All M+ runs cleared (personal + guild)")
				Cerebro:Print("  • Session history + attendance cleared")
				Cerebro:Print("  • Epoch incremented (forces sync)")
				Cerebro:Print("Re-run with YES to confirm: |cff00ff00/cb seasonreset YES|r")
				return
			end
			
			-- Shared core: zero EP/GP, clear attendance/history, increment epoch, sync
			local playerCount = Cerebro:ResetStandings("SEASON_RESET")
			
			-- Season-specific: Clear M+ history
			local mplusCount = Cerebro.db.global.mplusRuns and #Cerebro.db.global.mplusRuns or 0
			Cerebro.db.global.mplusRuns = {}
			local guildMplusCount = Cerebro.db.global.guildMplusRuns and #Cerebro.db.global.guildMplusRuns or 0
			Cerebro.db.global.guildMplusRuns = {}
			
			-- Season-specific: Clear readiness tracking
			if Cerebro.db.global.readiness then
				Cerebro.db.global.readiness.players = {}
				Cerebro.db.global.readiness.week = nil
			end
			
			-- Season-specific: Update season ID and TOC tracking
			local seasonInfo = Cerebro:GetSeasonInfo()
			Cerebro.db.global.seasonID = seasonInfo and seasonInfo.seasonID or 0
			local _, _, _, toc = GetBuildInfo()
			if toc then
				Cerebro.db.global.lastKnownTOC = toc
			end
			
			-- Print summary
			Cerebro:Print("|cff00ff00[Season Reset Complete]|r")
			Cerebro:Print(string.format("  - Reset %d players", playerCount))
			Cerebro:Print(string.format("  - Cleared %d personal + %d guild M+ runs", mplusCount, guildMplusCount))
			Cerebro:Print(string.format("  - Epoch = %d", Cerebro.db.global.resetEpoch))
			
			-- Refresh UI if open
			Cerebro:RefreshAllUI()
		end
	},
	
	-- Standings Reset (Zeroes EP/GP, preserves M+ and readiness)
	{
		id = "standings_reset",
		displayName = "Standings Reset",
		description = "Zeroes EP/GP and attendance for all players. Preserves M+ and readiness data.",
		officerOnly = true,
		hidden = false,
		dangerous = true,
		aliases = {"standingsreset"},
		slash = "/cb standingsreset YES",
		usage = "/cb standingsreset YES",
		argsSchema = {
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			-- Dangerous command confirmation
			if ctx.source == "slash" and (not argv[1] or argv[1]:upper() ~= "YES") then
				Cerebro:Print("|cffff8800STANDINGS RESET:|r This will zero all EP/GP and attendance for everyone.")
				Cerebro:Print("  - All player EP -> 0")
				Cerebro:Print("  - All player GP -> Base GP")
				Cerebro:Print("  - All attendance -> 0")
				Cerebro:Print("  - Epoch incremented (forces sync)")
				Cerebro:Print("  - M+ and readiness data preserved")
				Cerebro:Print("Re-run with YES to confirm: |cff00ff00/cb standingsreset YES|r")
				return
			end
			
			-- Shared core: zero EP/GP, clear attendance/history, increment epoch, sync
			local playerCount = Cerebro:ResetStandings("STANDINGS_RESET")
			
			-- Print summary
			Cerebro:Print("|cff00ff00[Standings Reset Complete]|r")
			Cerebro:Print(string.format("  - Reset %d players", playerCount))
			Cerebro:Print(string.format("  - Epoch = %d", Cerebro.db.global.resetEpoch))
			Cerebro:Print("  - M+ and readiness data preserved")
			
			-- Refresh UI if open
			Cerebro:RefreshAllUI()
		end
	},
	
	-- Factory Reset (EXTREMELY DANGEROUS - GM Only by default)
	{
		id = "factory_reset",
		displayName = "Factory Reset",
		description = "Wipes all EPGP data (creates backup first). GM-only by default.",
		officerOnly = true, -- Base RBAC check; handler does additional GM check
		hidden = true,
		dangerous = true,
		slash = "/cb factoryreset YES [--force]",
		usage = "/cb factoryreset YES [--force]",
		argsSchema = {
			{name = "confirm", type = "string", required = true, values = {"YES"}},
			{name = "force", type = "string", required = false},
		},
		handler = function(ctx, argv)
			-- TODO: Restore to 72 before v1.0.0 release
			local COOLDOWN_HOURS = 0  -- Temporarily disabled for testing
			local COOLDOWN_SECONDS = COOLDOWN_HOURS * 60 * 60
			
			-- Check for --force flag
			local forceOverride = false
			for _, arg in ipairs(argv) do
				if arg == "--force" then
					forceOverride = true
					break
				end
			end
			
			-- Permission Logic:
			-- 1. GM can always reset (with --force to bypass 72h cooldown)
			-- 2. Officers can reset ONLY if allowOfficerFactoryReset=true AND no fresh backup
			local isGM = Cerebro:IsGuildMasterSafe()
			local allowOfficerReset = Cerebro.db.global.config.admin and Cerebro.db.global.config.admin.allowOfficerFactoryReset
			local backup = Cerebro.db.global.backup
			local backupAge = backup and (time() - backup.timestamp) or nil
			local hasFreshBackup = backupAge and (backupAge < COOLDOWN_SECONDS)
			
			-- GM check first (strictest)
			if not isGM then
				-- Not GM - check if officer reset is allowed
				if not allowOfficerReset then
					Cerebro:Print("|cffff0000ACCESS DENIED:|r Factory reset is restricted to Guild Master only.")
					Cerebro:Print("Contact your GM or enable 'Allow Officer Factory Reset' in Admin settings.")
					return
				end
				
				-- Officer trying to reset with fresh backup - BLOCKED
				if hasFreshBackup then
					local ageHours = math.floor(backupAge / 3600)
					Cerebro:Print("|cffff0000ACCESS DENIED:|r Recent backup exists (" .. ageHours .. "h old, <72h).")
					Cerebro:Print("Officers cannot factory reset while a recent backup is protected.")
					Cerebro:Print("Wait " .. (COOLDOWN_HOURS - ageHours) .. " more hours or contact the Guild Master.")
					return
				end
			else
				-- GM with fresh backup and no --force
				if hasFreshBackup and not forceOverride then
					local ageHours = math.floor(backupAge / 3600)
					Cerebro:Print("|cffff8800WARNING:|r A backup from " .. ageHours .. " hours ago exists.")
					Cerebro:Print("Use |cff00ff00/cb factoryreset YES --force|r to override the 72h protection.")
					return
				end
			end
			
			-- Confirmation check
			if ctx.source == "slash" and (not argv[1] or argv[1]:upper() ~= "YES") then
				Cerebro:Print("|cffff0000EXTREMELY DANGEROUS:|r This will wipe ALL EPGP data.")
				Cerebro:Print("Re-run with YES to confirm: |cff00ff00/cb factoryreset YES|r")
				return
			end
			
			-- == BACKUP PHASE ==
			local previousBackupAge = backupAge and math.floor(backupAge / 3600) or nil
			
			Cerebro.db.global.backup = {
				timestamp = time(),
				version = Cerebro.version,
				players = Cerebro:CopyTable(Cerebro.db.global.players),
				transactions = Cerebro:CopyTable(Cerebro.db.global.transactions),
				audit = Cerebro:CopyTable(Cerebro.db.global.audit),
				epgpConfig = Cerebro:CopyTable(Cerebro.db.global.config.epgp),
				resetEpoch = Cerebro.db.global.resetEpoch or 0, -- Store current epoch for restore
			}
			
			-- Audit: Backup created (before wipe clears audit)
			if Cerebro.Ledger then
				local backupDetails = "Backup created before factory reset"
				if previousBackupAge then
					backupDetails = backupDetails .. " (previous backup was " .. previousBackupAge .. "h old)"
				end
				Cerebro.Ledger:AddAuditLog("BACKUP_CREATED", backupDetails)
			end
			
			Cerebro:Print("|cff00ff00Backup created successfully.|r")
			
			-- == QUARANTINE PHASE ==
			-- Increment epoch to invalidate all older sync payloads
			Cerebro.db.global.resetEpoch = (Cerebro.db.global.resetEpoch or 0) + 1
			-- Set quarantine to block incoming/outgoing sync for 15 min
			Cerebro.db.global.resetQuarantine = { at = time() }
			
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("QUARANTINE_START", 
					"Epoch: " .. Cerebro.db.global.resetEpoch .. ", 15 min quarantine active")
			end
			
			-- == WIPE PHASE ==
			Cerebro.db.global.players = {}
			Cerebro.db.global.transactions = {}
			Cerebro.db.global.awards = {}
			Cerebro.db.global.guildMplusRuns = {}
			Cerebro.db.global.history = {}
			Cerebro.db.global.totalBossKills = 0
			Cerebro.db.global.seasonBossKills = 0
			if Cerebro.db.global.readiness then
				Cerebro.db.global.readiness.players = {}
				Cerebro.db.global.readiness.week = nil
			end
			-- NOTE: Audit is intentionally NOT wiped - preserved for accountability
			-- Old entries will show [Epoch N] markers in UI
			Cerebro.db.global.currentSession = nil
			
			-- Reset delta sync state
			if Cerebro.db.global.syncState then
				Cerebro.db.global.syncState.dirtyPlayers = {}
				Cerebro.db.global.syncState.nextRev = 0
			end
			
			-- Reset onboarding flag so wizard appears on fresh install
			if Cerebro.db.global.config then
				Cerebro.db.global.config.onboardingCompleted = false
			end
			
			-- Add Audit Log (this entry will have the new epoch)
			if Cerebro.Ledger then
				local resetDetails = "Database wiped by " .. UnitName("player")
				if forceOverride then
					resetDetails = resetDetails .. " (--force override used)"
				end
				Cerebro.Ledger:AddAuditLog("FACTORY_RESET", resetDetails)
			end
			
			-- Broadcast
			if Cerebro.modules.CommunicationV2 then
				Cerebro.modules.CommunicationV2:BroadcastReset()
			end
			
			-- Refresh UI
			Cerebro:RefreshAllUI()
			
			Cerebro:Print("|cffff0000Factory Reset Complete.|r Database cleared. Use /cb restore YES to recover.")
		end
	},
	
	-- Fresh Start (Local client recovery - available to ALL players)
	{
		id = "fresh_start",
		displayName = "Fresh Start",
		description = "Resets YOUR local addon data and re-syncs from officers. Does not affect guild epoch or other players.",
		officerOnly = false, -- Available to everyone
		hidden = false,
		dangerous = true,
		slash = "/cb freshstart YES",
		usage = "/cb freshstart YES",
		argsSchema = {
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			-- Cooldown tracking (module-level)
			Cerebro._freshStartCooldown = Cerebro._freshStartCooldown or 0
			local COOLDOWN_SECONDS = 60
			
			-- Cooldown check
			local now = time()
			if (now - Cerebro._freshStartCooldown) < COOLDOWN_SECONDS then
				local remaining = COOLDOWN_SECONDS - (now - Cerebro._freshStartCooldown)
				Cerebro:Print(string.format("|cffff8800Please wait %d seconds|r before running freshstart again.", remaining))
				return
			end
			
			-- Confirmation check
			if ctx.source == "slash" and (not argv[1] or argv[1]:upper() ~= "YES") then
				Cerebro:Print("|cffff8800Fresh Start|r will wipe YOUR local data and re-sync from officers.")
				Cerebro:Print("Your EP/GP standings, raid attendance, and status will be restored via sync.")
				Cerebro:Print("Only local transaction history and UI preferences are lost.")
				Cerebro:Print("")
				Cerebro:Print("Run |cff00ff00/cb freshstart YES|r to confirm.")
				return
			end
			
			-- Officer warning: if caller is the only officer online, warn them
			if Cerebro:IsOfficer() then
				-- Count online officers in guild
				local officerCount = 0
				if IsInGuild() then
					for i = 1, GetNumGuildMembers() do
						local name, _, _, _, _, _, _, _, online = GetGuildRosterInfo(i)
						if online and name then
							local fullName = Cerebro:GetFullPlayerName(name)
							if Cerebro:IsOfficerSafe(fullName) then
								officerCount = officerCount + 1
							end
						end
					end
				end
				
				-- IsOfficerSafe may not work for other players via roster, so check if we're likely alone
				if officerCount <= 1 then
					Cerebro:Print("|cffff8800Warning:|r You appear to be the only officer online.")
					Cerebro:Print("After fresh start, you won't have data to sync until another officer logs in.")
					Cerebro:Print("Proceeding anyway...")
				end
			end
			
			-- Record cooldown timestamp
			Cerebro._freshStartCooldown = now
			
			-- == WIPE PHASE ==
			-- Preserve purge ledger (prevents synthetic character resurrection)
			local purgedPlayers = Cerebro.db.global.purgedPlayers
			
			-- Wipe local data
			Cerebro.db.global.players = {}
			Cerebro.db.global.transactions = {}
			Cerebro.db.global.audit = {}
			Cerebro.db.global.syncStatus = {}
			Cerebro.db.global.mplusRuns = {}         -- Personal M+ run history
			Cerebro.db.global.guildMplusRuns = {}    -- Guild M+ run history (officer)
			Cerebro.db.global.scope = nil  -- Triggers fresh guild lock prompt
			
			-- Restore config to defaults while preserving structure
			local defaultConfig = {
				epgp = {
					baseGP = 200,
					minGP = 100,
					decayRate = 10,
				},
				ui = {},
				debug = false,
				onboardingCompleted = false,  -- Reset so wizard shows on fresh start
			}
			Cerebro.db.global.config = defaultConfig
			
			-- Restore preserved data
			Cerebro.db.global.purgedPlayers = purgedPlayers
			
			-- Clear quarantine states (so sync is not blocked)
			Cerebro.db.global.epochQuarantine = nil
			Cerebro.db.global.resetQuarantine = nil
			
			-- == BOOTSTRAP PHASE ==
			-- Re-announce to get added back to officers' rosters
			if Cerebro.modules.CommunicationV2 and Cerebro.modules.CommunicationV2.AnnounceSelf then
				Cerebro:ScheduleTimer(function()
					Cerebro.modules.CommunicationV2:AnnounceSelf()
				end, 1)
			end
			
			-- Request sync from officers
			if Cerebro.modules.CommunicationV2 and Cerebro.modules.CommunicationV2.RequestSync then
				Cerebro:ScheduleTimer(function()
					Cerebro.modules.CommunicationV2:RequestSync()
				end, 2)
			end
			
			-- Close UI if open (will reopen with fresh state)
			if Cerebro.modules.UI and Cerebro.modules.UI.Hide then
				Cerebro.modules.UI:Hide()
			end
			
			Cerebro:Print("|cff00ff00Fresh start complete.|r Waiting for officer sync...")
			Cerebro:Print("Open /cb to lock to your guild and view data once synced.")
		end
	},
	
	-- Restore Backup (GM Only)
	{
		id = "restore_backup",
		displayName = "Restore Backup",
		description = "Restores database from last factory reset backup. Officer-only.",
		officerOnly = true, -- Base check; handler enforces GM-only
		hidden = true,
		dangerous = true,
		slash = "/cb restore YES",
		usage = "/cb restore YES",
		argsSchema = {
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			-- Officers can restore (was GM-only before v0.8.14)
			if not Cerebro:IsOfficer() then
				Cerebro:Print("|cffff0000ACCESS DENIED:|r Restore is restricted to officers only.")
				return
			end
			
			-- Check backup exists
			local backup = Cerebro.db.global.backup
			if not backup or not backup.timestamp then
				Cerebro:Print("|cffff0000ERROR:|r No backup found. Nothing to restore.")
				return
			end
			
			-- Confirmation check
			if ctx.source == "slash" and (not argv[1] or argv[1]:upper() ~= "YES") then
				local ageHours = math.floor((time() - backup.timestamp) / 3600)
				local playerCount = 0
				for _ in pairs(backup.players or {}) do playerCount = playerCount + 1 end
				local txCount = backup.transactions and #backup.transactions or 0
				
				Cerebro:Print("|cffff8800Backup Available:|r")
				Cerebro:Print("  Created: " .. ageHours .. " hours ago")
				Cerebro:Print("  Version: " .. (backup.version or "unknown"))
				Cerebro:Print("  Players: " .. playerCount)
				Cerebro:Print("  Transactions: " .. txCount)
				Cerebro:Print("")
				Cerebro:Print("Run |cff00ff00/cb restore YES|r to restore this backup.")
				return
			end
			
			-- == RESTORE PHASE ==
			-- Restore tables
			Cerebro.db.global.players = Cerebro:CopyTable(backup.players) or {}
			Cerebro.db.global.transactions = Cerebro:CopyTable(backup.transactions) or {}
			Cerebro.db.global.audit = Cerebro:CopyTable(backup.audit) or {}
			
			-- Restore EPGP config if present
			if backup.epgpConfig then
				Cerebro.db.global.config.epgp = Cerebro:CopyTable(backup.epgpConfig)
			end
			
			-- Replay history to recalculate PR
			if Cerebro.Ledger and Cerebro.Ledger.ReplayHistory then
				Cerebro.Ledger:ReplayHistory()
			end
			
			-- Audit: Restore performed
			if Cerebro.Ledger then
				local ageHours = math.floor((time() - backup.timestamp) / 3600)
				Cerebro.Ledger:AddAuditLog("RESTORE_PERFORMED", "Database restored from backup (" .. ageHours .. "h old) by " .. UnitName("player"))
			end
			
			-- Refresh UI
			Cerebro:RefreshAllUI()
			
			-- Restore epoch with fallback (avoid stale sync rejection)
			Cerebro.db.global.resetEpoch = backup.resetEpoch or Cerebro.db.global.resetEpoch or 0
			
			-- Stamp restored records with current epoch to prevent stale marking (v0.8.18)
			-- (Option 2: Restoration treats data as authoritative for current epoch)
			local currentEpoch = Cerebro.db.global.resetEpoch
			for name, data in pairs(Cerebro.db.global.players) do
				data.createdEpoch = currentEpoch
			end
			Cerebro:Debug("Stamped restored records with current epoch: " .. currentEpoch)
			
			-- Clear quarantine (user explicitly wants data back)
			Cerebro.db.global.resetQuarantine = nil
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("QUARANTINE_CLEAR", "Cleared via restore by " .. UnitName("player"))
			end
			
			-- Sync to raid
			if Cerebro.modules.Communication then
				Cerebro.modules.CommunicationV2:SyncToRaid()
			end
			
			Cerebro:Print("|cff00ff00Restore Complete.|r Database recovered from backup.")
		end
	},
	
	-- Backup Status (Officer)
	{
		id = "backup_status",
		displayName = "Backup Status",
		description = "Shows the status of the last factory reset backup.",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		slash = "/cb backup",
		usage = "/cb backup",
		handler = function(ctx, argv)
			local backup = Cerebro.db.global.backup
			
			if not backup or not backup.timestamp then
				Cerebro:Print("|cff888888No backup exists.|r")
				Cerebro:Print("A backup is created automatically before any factory reset.")
				return
			end
			
			local ageSeconds = time() - backup.timestamp
			local ageHours = math.floor(ageSeconds / 3600)
			local ageDays = math.floor(ageHours / 24)
			
			local playerCount = 0
			for _ in pairs(backup.players or {}) do playerCount = playerCount + 1 end
			local txCount = backup.transactions and #backup.transactions or 0
			local auditCount = backup.audit and #backup.audit or 0
			
			local isProtected = ageSeconds < (72 * 60 * 60)
			local canRestore = Cerebro:IsOfficer()
			
			Cerebro:Print("=== Backup Status ===")
			if ageDays > 0 then
				Cerebro:Print("  Age: " .. ageDays .. " days, " .. (ageHours % 24) .. " hours")
			else
				Cerebro:Print("  Age: " .. ageHours .. " hours")
			end
			Cerebro:Print("  Version: " .. (backup.version or "unknown"))
			Cerebro:Print("  Players: " .. playerCount)
			Cerebro:Print("  Transactions: " .. txCount)
			Cerebro:Print("  Audit Entries: " .. auditCount)
			Cerebro:Print("  Protected: " .. (isProtected and "|cff00ff00Yes (72h window)|r" or "|cff888888No|r"))
			Cerebro:Print("  Can Restore: " .. (canRestore and "|cff00ff00Yes (Officer)|r" or "|cffff0000No (Officer Only)|r"))
		end
	},
	
	-- Quarantine Status/Clear
	{
		id = "quarantine_status",
		displayName = "Quarantine Status",
		description = "Shows quarantine status or clears it. Use '/cb quarantine clear' to clear.",
		officerOnly = false, -- Non-officers need to check/clear their quarantine
		hidden = false,
		dangerous = false,
		aliases = {"quarantine"},
		slash = "/cb quarantine [clear]",
		usage = "/cb quarantine [clear]",
		handler = function(ctx, argv)
			local subCmd = argv[1] and argv[1]:lower() or ""
			
			if subCmd == "clear" then
				-- Clear quarantine
				Cerebro.db.global.resetQuarantine = nil
				if Cerebro.Ledger then
					Cerebro.Ledger:AddAuditLog("QUARANTINE_CLEAR", "Manual clear by " .. UnitName("player"))
				end
				Cerebro:Print("|cff00ff00Quarantine cleared.|r Sync is now enabled.")
				return
			end
			
			-- Show status
			local q = Cerebro.db.global.resetQuarantine
			local epoch = Cerebro.db.global.resetEpoch or 0
			
			if not q then
				Cerebro:Print("|cff00ff00No quarantine active.|r Sync is enabled.")
				Cerebro:Print("  Current epoch: " .. epoch)
			else
				local age = math.floor((time() - q.at) / 60)
				local remaining = math.max(0, 15 - age)
				local isActive = remaining > 0
				
				if isActive then
					Cerebro:Print("|cffff8800Quarantine active:|r " .. age .. " min elapsed, " .. remaining .. " min remaining")
				else
					Cerebro:Print("|cff888888Quarantine expired|r (was set " .. age .. " min ago)")
				end
				Cerebro:Print("  Current epoch: " .. epoch)
				Cerebro:Print("Use |cff00ff00/cb quarantine clear|r to enable sync.")
			end
		end
	},
	
	-- Purge Player (DANGEROUS - Permanent removal)
	{
		id = "purge_player",
		displayName = "Purge Player",
		description = "Removes a player from the roster and adds to purge ledger. They can be rediscovered via guild/group scan.",
		officerOnly = true,
		hidden = true,
		dangerous = true,
		aliases = {"purge"},
		slash = "/cb purge [player] YES",
		usage = "/cb purge [player] YES",
		argsSchema = {
			{name = "player", type = "string", required = true},
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			-- Session lock: prevent purges during active raid sessions
			if Cerebro.modules.Session and Cerebro.modules.Session:IsActive() then
				Cerebro:Print("|cffff8800Purge is locked during an active session.|r End the session first.")
				return
			end

			local player = argv[1]
			if not player then
				Cerebro:Print("Usage: /cb purge [player] YES")
				Cerebro:Print("  Removes player from roster and adds to purge ledger. Rediscoverable via guild/group.")
				return
			end
			
			-- Dangerous command confirmation
			if ctx.source == "slash" and (not argv[2] or argv[2]:upper() ~= "YES") then
				Cerebro:Print("|cffff8800WARNING:|r This resets " .. player .. "'s EP/GP/attendance to zero.")
				Cerebro:Print("Re-run with YES to confirm: |cff00ff00/cb purge " .. player .. " YES|r")
				return
			end
			
			local fullName = Cerebro:GetFullPlayerName(player)
			local inputNormalized = NormalizePlayerKey(fullName)
			
			-- Search case-insensitive
			local targetKey = nil
			for k in pairs(Cerebro.db.global.players) do
				if k:lower() == fullName:lower() then
					targetKey = k
					break
				end
			end
			
			-- Pass 2: Normalized realm matching
			if not targetKey then
				for k in pairs(Cerebro.db.global.players) do
					if NormalizePlayerKey(k) == inputNormalized then
						targetKey = k
						break
					end
				end
			end
			
			if not targetKey then
				Cerebro:Print("Player not found in database: " .. player)
				return
			end
			
			-- Delete from roster + add to purge ledger
			Cerebro:PurgePlayer(targetKey)
		end
	},
	{
		id = "block_player",
		displayName = "Block Player",
		description = "Permanently removes a player and blocks re-sync (for corrupted/unwanted entries)",
		officerOnly = true,
		hidden = true,
		dangerous = true,
		aliases = {"block"},
		slash = "/cb block [player] YES",
		usage = "/cb block [player] YES",
		argsSchema = {
			{name = "player", type = "string", required = true},
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			local player = argv[1]
			if not player then
				Cerebro:Print("Usage: /cb block [player] YES")
				Cerebro:Print("  Permanently removes player and blocks re-sync. Use /cb unblock to reverse.")
				return
			end
			
			-- Dangerous command confirmation
			if ctx.source == "slash" and (not argv[2] or argv[2]:upper() ~= "YES") then
				Cerebro:Print("|cffff0000DANGEROUS:|r This permanently blocks " .. player .. " from all databases.")
				Cerebro:Print("Re-run with YES to confirm: |cff00ff00/cb block " .. player .. " YES|r")
				return
			end
			
			local fullName = Cerebro:GetFullPlayerName(player)
			local inputNormalized = NormalizePlayerKey(fullName)
			
			-- Search case-insensitive in players
			local targetKey = nil
			for k in pairs(Cerebro.db.global.players) do
				if k:lower() == fullName:lower() then
					targetKey = k
					break
				end
			end
			
			-- Pass 2: Normalized realm matching
			if not targetKey then
				for k in pairs(Cerebro.db.global.players) do
					if NormalizePlayerKey(k) == inputNormalized then
						targetKey = k
						break
					end
				end
			end
			
			-- If not in players, still allow blocking by name (for pre-emptive blocks)
			if not targetKey then
				targetKey = fullName
			end
			
			-- Add to blocked list
			Cerebro.db.global.blockedPlayers = Cerebro.db.global.blockedPlayers or {}
			Cerebro.db.global.blockedPlayers[targetKey] = time()
			
			-- Delete from players (if exists)
			if Cerebro.db.global.players[targetKey] then
				Cerebro.db.global.players[targetKey] = nil
			end
			
			-- Audit log
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("PLAYER_BLOCK", "Blocked: " .. targetKey)
			end
			
			-- Refresh UI
			Cerebro:RefreshAllUI()
			
			Cerebro:Print("|cffff0000Blocked|r " .. targetKey .. " — removed from database, sync will reject. Use /cb unblock to reverse.")
		end
	},
	{
		id = "unblock_player",
		displayName = "Unblock Player",
		description = "Removes a player from the blocked list, allowing re-sync",
		officerOnly = true,
		hidden = true,
		aliases = {"unblock"},
		slash = "/cb unblock [player]",
		usage = "/cb unblock [player]",
		argsSchema = {
			{name = "player", type = "string", required = true},
		},
		handler = function(ctx, argv)
			local player = argv[1]
			if not player then
				Cerebro:Print("Usage: /cb unblock [player]")
				return
			end
			
			local fullName = Cerebro:GetFullPlayerName(player)
			Cerebro.db.global.blockedPlayers = Cerebro.db.global.blockedPlayers or {}
			
			-- Search case-insensitive
			local targetKey = nil
			for k in pairs(Cerebro.db.global.blockedPlayers) do
				if k:lower() == fullName:lower() then
					targetKey = k
					break
				end
			end
			
			-- Also try exact match on the raw input (for entries without realm)
			if not targetKey and Cerebro.db.global.blockedPlayers[player] then
				targetKey = player
			end
			
			if not targetKey then
				Cerebro:Print("Player not found in blocked list: " .. player)
				return
			end
			
			Cerebro.db.global.blockedPlayers[targetKey] = nil
			
			-- Audit log
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("PLAYER_UNBLOCK", "Unblocked: " .. targetKey)
			end
			
			Cerebro:Print("|cff00ff00Unblocked|r " .. targetKey .. " — player can be re-synced.")
		end
	},
	{
		id = "blocked_list",
		displayName = "Blocked List",
		description = "Shows all blocked player entries",
		officerOnly = true,
		hidden = true,
		aliases = {"blockedlist"},
		slash = "/cb blockedlist",
		usage = "/cb blockedlist",
		handler = function()
			Cerebro.db.global.blockedPlayers = Cerebro.db.global.blockedPlayers or {}
			local count = 0
			Cerebro:Print("=== Blocked Players ===")
			for name, blockTime in pairs(Cerebro.db.global.blockedPlayers) do
				local when = date("%Y-%m-%d %H:%M", blockTime)
				Cerebro:Print("  |cffff4444" .. name .. "|r  (blocked " .. when .. ")")
				count = count + 1
			end
			if count == 0 then
				Cerebro:Print("  (none)")
			else
				Cerebro:Print("Total: " .. count .. " blocked. Use /cb unblock [name] to remove.")
			end
		end
	},
	
	-- Purge Test Players (DANGEROUS - Batch removal)
	{
		id = "purge_test_players",
		displayName = "Purge Test Players",
		description = "Mass-purge all synthetic test players (Test*-Realm)",
		officerOnly = true,
		hidden = true,
		dangerous = true,
		aliases = {"purgetest"},
		slash = "/cb purgetest YES",
		usage = "/cb purgetest YES",
		argsSchema = {
			{name = "confirm", type = "string", required = true, values = {"YES"}},
		},
		handler = function(ctx, argv)
			-- Dangerous command confirmation
			if ctx.source == "slash" and (not argv[1] or argv[1]:upper() ~= "YES") then
				Cerebro:Print("|cffff0000DANGEROUS:|r This purges ALL test players (Test*-Realm).")
				Cerebro:Print("Re-run with YES: |cff00ff00/cb purgetest YES|r")
				return
			end
			
			local purged = 0
			local names = {}
			
			Cerebro.db.global.purgedPlayers = Cerebro.db.global.purgedPlayers or {}
			
			for name in pairs(Cerebro.db.global.players) do
				if Cerebro:IsSyntheticTestPlayer(name) then
					Cerebro.db.global.purgedPlayers[name] = time()
					Cerebro.db.global.players[name] = nil
					table.insert(names, name)
					purged = purged + 1
				end
			end
			
			if purged > 0 then
				-- Audit log
				if Cerebro.Ledger then
					Cerebro.Ledger:AddAuditLog("PLAYER_PURGE", "Batch purged " .. purged .. " test players")
				end
				-- Sync to propagate purge ledger
				if Cerebro.modules.Communication then
					Cerebro.modules.CommunicationV2:SyncToRaid()
				end
			end
			
			-- Refresh UI
			Cerebro:RefreshAllUI()
			
			Cerebro:Print(string.format("|cffff0000Purged %d test players.|r", purged))
		end
	},
	
	-- Purge Ghost Players (non-guild members that accumulated via tooltip/sync pollution)
	{
		id = "purge_ghosted",
		displayName = "Purge Ghost Players",
		description = "Mass-purge all players not in the current guild roster (tooltip pollution cleanup)",
		officerOnly = true,
		hidden = false,
		dangerous = true,
		aliases = {"purgeghosted", "purgeghosts"},
		slash = "/cb purgeghosted [YES]",
		usage = "/cb purgeghosted [YES]  (without YES = dry run)",
		argsSchema = {
			{name = "confirm", type = "string", required = false, values = {"YES"}},
		},
		handler = function(ctx, argv)
			local dryRun = not (argv[1] and argv[1]:upper() == "YES")
			
			-- Build guild roster lookup table
			local guildMembers = {}
			local guildCount = 0
			if IsInGuild() then
				for i = 1, GetNumGuildMembers() do
					local gName = GetGuildRosterInfo(i)
					if gName then
						local fullName = Cerebro:GetFullPlayerName(gName)
						guildMembers[fullName] = true
						guildCount = guildCount + 1
					end
				end
			end
			
			if guildCount == 0 then
				Cerebro:Print("|cffff0000ERROR:|r Could not load guild roster. Try opening the guild panel first (J key).")
				return
			end
			
			-- Find non-guild players
			local ghosts = {}
			for name, data in pairs(Cerebro.db.global.players) do
				if not guildMembers[name] then
					table.insert(ghosts, {name = name, status = data.status or "Unknown", ep = data.ep or 0})
				end
			end
			
			if #ghosts == 0 then
				Cerebro:Print("|cff00ff00No ghost players found.|r All tracked players are in the guild roster.")
				return
			end
			
			-- Sort by name for readability
			table.sort(ghosts, function(a, b) return a.name < b.name end)
			
			if dryRun then
				Cerebro:Print(string.format("|cffff8800[DRY RUN]|r Found %d ghost players (not in guild roster of %d):", #ghosts, guildCount))
				local shown = 0
				for _, g in ipairs(ghosts) do
					if shown < 20 then
						Cerebro:Print(string.format("  %s (%s, EP: %d)", g.name, g.status, g.ep))
						shown = shown + 1
					end
				end
				if #ghosts > 20 then
					Cerebro:Print(string.format("  ... and %d more", #ghosts - 20))
				end
				Cerebro:Print("")
				Cerebro:Print("Run |cff00ff00/cb purgeghosted YES|r to permanently remove these players.")
				Cerebro:Print("|cffff8800WARNING:|r This removes them from ALL officer databases via purge ledger sync.")
				return
			end
			
			-- Execute purge
			Cerebro.db.global.purgedPlayers = Cerebro.db.global.purgedPlayers or {}
			local purged = 0
			
			for _, g in ipairs(ghosts) do
				Cerebro.db.global.purgedPlayers[g.name] = time()
				Cerebro.db.global.players[g.name] = nil
				purged = purged + 1
			end
			
			-- Audit log
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("PLAYER_PURGE", string.format("Batch purged %d ghost (non-guild) players by %s", purged, UnitName("player")))
			end
			
			-- Sync purge ledger to other officers
			if Cerebro.modules.Communication then
				Cerebro.modules.CommunicationV2:SyncToRaid()
			end
			
			-- Refresh UI
			Cerebro:RefreshAllUI()
			
			Cerebro:Print(string.format("|cffff0000Purged %d ghost players.|r Purge ledger will sync to other officers.", purged))
		end
	},
	
	-- Attendance Commands (Officer)
	{
		id = "attendance_summary",
		displayName = "Attendance Summary",
		description = "Show current session attendance summary",
		officerOnly = true,
		hidden = false,
		dangerous = false,
		aliases = {"attendance"},
		slash = "/cb attendance",
		usage = "/cb attendance [live|late|missing|report [session-id]]",
		handler = function(ctx, argv)
			local subCmd = argv[1] and argv[1]:lower()
			
			-- Sub-commands
			if subCmd == "live" then
				-- Real-time roster
				if not IsInRaid() then
					Cerebro:Print("Not in a raid group.")
					return
				end
				local roster = Cerebro:GetRaidRoster()
				Cerebro:Print(string.format("=== Live Roster (%d players) ===", #roster))
				for _, member in ipairs(roster) do
					-- Read-only lookup: do NOT use GetPlayerData() here as it auto-creates
					-- records for cross-realm PUGs who aren't in our guild
					local data = Cerebro.db.global.players[member.name]
					local status = data and data.status or "Unknown"
					Cerebro:Print(string.format("  %s (%s)", Cerebro:ColorPlayerName(member.name, member.class), status))
				end
				return
			end
			
			if subCmd == "late" then
				-- Late arrivals
				local session = Cerebro.db.global.currentSession
				if not session then
					Cerebro:Print("No active session.")
					return
				end
				local lateCount = 0
				Cerebro:Print("=== Late Arrivals ===")
				for player, entry in pairs(session.roster) do
					if entry.onTime == false then
						Cerebro:Print("  " .. Cerebro:ColorPlayerName(player))
						lateCount = lateCount + 1
					end
				end
				if lateCount == 0 then
					Cerebro:Print("  No late arrivals recorded.")
				end
				return
			end
			
			if subCmd == "missing" then
				-- Core players not in raid
				if not IsInRaid() then
					Cerebro:Print("Not in a raid group.")
					return
				end
				local roster = Cerebro:GetRaidRoster()
				local inRaid = {}
				for _, member in ipairs(roster) do
					inRaid[member.name] = true
				end
				
				local missingCount = 0
				Cerebro:Print("=== Missing Core Players ===")
				for name, data in pairs(Cerebro.db.global.players) do
					if data.status == "Core" and not inRaid[name] then
						Cerebro:Print("  " .. Cerebro:ColorPlayerName(name, data.class))
						missingCount = missingCount + 1
					end
				end
				if missingCount == 0 then
					Cerebro:Print("  All Core players are in raid.")
				end
				return
			end
			
			if subCmd == "report" then
				local sessionID = argv[2]
				if not sessionID then
					Cerebro:Print("Usage: /cb attendance report [session-id]")
					Cerebro:Print("Available sessions:")
					local count = 0
					for id, session in pairs(Cerebro.db.global.history or {}) do
						count = count + 1
						if count <= 5 then
							Cerebro:Print("  " .. id)
						end
					end
					if count > 5 then
						Cerebro:Print("  ... and " .. (count - 5) .. " more")
					end
					return
				end
				
				local session = Cerebro.db.global.history[sessionID]
				if not session then
					Cerebro:Print("Session not found: " .. sessionID)
					return
				end
				
				Cerebro:Print(string.format("=== Session Report: %s ===", sessionID))
				Cerebro:Print(string.format("Raid: %s", session.raidName or "Unknown"))
				Cerebro:Print(string.format("Bosses Killed: %d", #(session.bosses or {})))
				local onTimeCount, lateCount = 0, 0
				for player, entry in pairs(session.roster) do
					if entry.onTime then onTimeCount = onTimeCount + 1 end
					if entry.onTime == false then lateCount = lateCount + 1 end
				end
				Cerebro:Print(string.format("On-Time: %d, Late: %d", onTimeCount, lateCount))
				return
			end
			
			-- Default: summary
			local session = Cerebro.db.global.currentSession
			if not session then
				Cerebro:Print("No active session. Use /cb attendance report [session-id] for history.")
				return
			end
			
			local bossCount = #(session.bosses or {})
			local rosterCount = 0
			local onTimeCount, lateCount = 0, 0
			for player, entry in pairs(session.roster) do
				rosterCount = rosterCount + 1
				if entry.onTime then onTimeCount = onTimeCount + 1 end
				if entry.onTime == false then lateCount = lateCount + 1 end
			end
			
			Cerebro:Print("=== Current Session Attendance ===")
			Cerebro:Print(string.format("Raid: %s", session.raidName or "Unknown"))
			Cerebro:Print(string.format("Started: %s", Cerebro:FormatRelativeTime(session.startTime)))
			Cerebro:Print(string.format("Bosses Killed: %d", bossCount))
			Cerebro:Print(string.format("Players: %d (On-Time: %d, Late: %d)", rosterCount, onTimeCount, lateCount))
		end
	},
	
	-- Test Season Reset (Dev-Only)
	{
		id = "test_season_reset",
		displayName = "Test Season Reset",
		description = "Force season reset for testing (dev builds only)",
		officerOnly = true,
		hidden = true,
		dangerous = true,
		slash = "/cb testseasonreset",
		usage = "/cb testseasonreset",
		handler = function(ctx, argv)
			-- Dev gate
			if Cerebro:IsReleaseBuild() then
				Cerebro:Print("|cffff0000Error:|r This command is only available in dev builds.")
				return
			end
			
			if not Cerebro:IsOfficer() then
				Cerebro:Print("|cffff0000Error:|r Officer permission required.")
				return
			end
			
			-- Force reset attendance counters
			local playerCount = 0
			for name, data in pairs(Cerebro.db.global.players or {}) do
				data.bossKillsPresent = 0
				playerCount = playerCount + 1
			end
			
			Cerebro.db.global.seasonBossKills = 0
			
			if Cerebro.Ledger then
				Cerebro.Ledger:AddAuditLog("ATTENDANCE_RESET", "Manual test reset by " .. UnitName("player"))
			end
			
			Cerebro:Print(string.format("|cffff8800[Test]|r Reset attendance for %d players. seasonBossKills = 0", playerCount))
			
			-- Refresh UI
			Cerebro:RefreshAllUI()
		end
	},
}


-- Get all commands
function CommandRegistry:GetAll()
	return commands
end

-- Get filtered commands (by search text)
function CommandRegistry:GetFiltered(searchText)
	if not searchText or searchText == "" then
		return commands
	end
	
	local filtered = {}
	local search = searchText:lower()
	
	for _, cmd in ipairs(commands) do
		local match = false
		
		if cmd.displayName and cmd.displayName:lower():find(search, 1, true) then
			match = true
		elseif cmd.description and cmd.description:lower():find(search, 1, true) then
			match = true
		elseif cmd.slash and cmd.slash:lower():find(search, 1, true) then
			match = true
		end
		
		if match then
			table.insert(filtered, cmd)
		end
	end
	
	return filtered
end

-- Execute command with RBAC enforcement and context
function CommandRegistry:Execute(cmdId, argv, ctx)
	argv = argv or {}
	ctx = ctx or {}
	
	-- Resolve alias to ID
	local resolvedId = cmdId
	for _, cmd in ipairs(commands) do
		if cmd.id == cmdId then
			resolvedId = cmdId
			break
		end
		if cmd.aliases then
			for _, alias in ipairs(cmd.aliases) do
				if alias == cmdId then
					resolvedId = cmd.id
					break
				end
			end
		end
	end
	
	-- Find command
	local cmd = nil
	for _, c in ipairs(commands) do
		if c.id == resolvedId then
			cmd = c
			break
		end
	end
	
	if not cmd then
		return false, "Command not found: " .. tostring(cmdId)
	end
	
	-- RBAC check (click-time for UI, always for slash)
	if cmd.officerOnly and not Cerebro:IsOfficerSafe() then
		Cerebro:Print("This command requires officer permissions.")
		return false, "Permission denied"
	end
	
	-- Execute via pcall
	local success, result = pcall(function()
		cmd.handler(ctx, argv)
	end)
	
	if not success then
		Cerebro:Debug("CommandRegistry: Execute failed for " .. resolvedId .. ": " .. tostring(result))
		return false, tostring(result)
	end
	
	return true, nil
end

-- Make globally accessible
_G.UI_CommandRegistry = CommandRegistry

-- Register test slash commands (proxies to test files if loaded)
-- These ensure /testcalc etc. exist even if test files fail to load
SLASH_TESTCALC1 = "/testcalc"
SlashCmdList["TESTCALC"] = function()
	if _G.RunPlayerCalcTests then
		_G.RunPlayerCalcTests()
	else
		Cerebro:Print("Calc tests not loaded. Ensure tests are included in TOC.")
	end
end

SLASH_TESTDECAY1 = "/testdecay"
SlashCmdList["TESTDECAY"] = function()
	if _G.RunDecayTests then
		_G.RunDecayTests()
	else
		Cerebro:Print("Decay tests not loaded.")
	end
end

SLASH_TESTLEDGER1 = "/testledger"
SlashCmdList["TESTLEDGER"] = function()
	if _G.RunLedgerTests then
		_G.RunLedgerTests()
	else
		Cerebro:Print("Ledger tests not loaded.")
	end
end

SLASH_TESTSESSION1 = "/testsession"
SlashCmdList["TESTSESSION"] = function()
	if _G.RunSessionTests then
		_G.RunSessionTests()
	else
		Cerebro:Print("Session tests not loaded.")
	end
end

SLASH_TESTMPLUS1 = "/testmplus"
SlashCmdList["TESTMPLUS"] = function()
	if _G.RunMPlusTests then
		_G.RunMPlusTests()
	else
		Cerebro:Print("M+ tests not loaded.")
	end
end

SLASH_TESTDIAG1 = "/testdiag"
SlashCmdList["TESTDIAG"] = function()
	if _G.RunDiagnosticsTests then
		_G.RunDiagnosticsTests()
	else
		Cerebro:Print("Diagnostics tests not loaded.")
	end
end
