local _, GF = ...

GF.Debug = GF.Debug or {}
local Debug = GF.Debug

local function printChat(message, opts)
	if GF.ShowStatusMessage then
		GF.ShowStatusMessage(message, opts)
	elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage and message and message ~= "" then
		DEFAULT_CHAT_FRAME:AddMessage(tostring(message))
	end
end

local function normalizeSlashCommand(msg)
	msg = tostring(msg or "")
	msg = msg:gsub("^%s+", ""):gsub("%s+$", "")
	return msg, string.lower(msg)
end

local function diagnosticValue(value)
	if value == nil then
		return "nil"
	end
	if value == "" then
		return '""'
	end
	local normalized = tostring(value):gsub("[\r\n]+", " ")
	return normalized
end

local function diagnosticList(values, limit)
	if type(values) ~= "table" then
		return "not-table"
	end
	local out = {}
	limit = tonumber(limit) or 20
	for index = 1, math.min(#values, limit) do
		out[#out + 1] = diagnosticValue(values[index])
	end
	if #values > limit then
		out[#out + 1] = "+" .. tostring(#values - limit)
	end
	return table.concat(out, ",")
end

local function diagnosticActivityInfo(activityID)
	activityID = tonumber(activityID)
	if not (activityID and C_LFGList and C_LFGList.GetActivityInfoTable) then
		return nil
	end
	local ok, info = pcall(C_LFGList.GetActivityInfoTable, activityID)
	return ok and type(info) == "table" and info or nil
end

local function diagnosticAvailableActivities(categoryID, groupID, filterFlags, searchText)
	local getter = C_LFGList and C_LFGList.GetAvailableActivities
	if type(getter) ~= "function" then
		return nil, "missing"
	end
	local ok, activities
	if searchText ~= nil then
		ok, activities = pcall(getter, categoryID, groupID, filterFlags, searchText)
	else
		ok, activities = pcall(getter, categoryID, groupID, filterFlags)
	end
	if not ok or type(activities) ~= "table" then
		return nil, ok and "not-table" or "error"
	end
	return activities
end

local function diagnosticAvailableGroups(categoryID, filterFlags)
	local getter = C_LFGList and C_LFGList.GetAvailableActivityGroups
	if type(getter) ~= "function" then
		return nil, "missing"
	end
	local ok, groups = pcall(getter, categoryID, filterFlags)
	if not ok or type(groups) ~= "table" then
		return nil, ok and "not-table" or "error"
	end
	return groups
end

local function diagnosticContains(values, target)
	target = tonumber(target)
	for _, value in ipairs(values or {}) do
		if tonumber(value) == target then
			return true
		end
	end
	return false
end

local function diagnosticHit(values, reason, target)
	if type(values) == "table" then
		return diagnosticContains(values, target)
	end
	return reason
end

local function diagnosticEditBoxText(editBox)
	if not (editBox and type(editBox.GetText) == "function") then
		return nil
	end
	local ok, text = pcall(editBox.GetText, editBox)
	return ok and text or nil
end

local function refreshDebugPanel()
	if GF.DebugPanel and GF.DebugPanel.Refresh then
		GF.DebugPanel:Refresh()
	end
end

local function selectDebugTab()
	if GF.MainFrame and GF.MainFrame.OpenDebugTab then
		GF.MainFrame:OpenDebugTab()
		return
	end
	if GF.MainFrame and GF.MainFrame.ShowFrame then
		GF.MainFrame:ShowFrame()
	end
	if GF.TabBar and GF.TabBar.SelectTab then
		GF.TabBar:SelectTab(GF.TAB_DEBUG)
	end
end

local function clearSavedLocaleOverride()
	if type(GroupFinderDB) == "table" then
		GroupFinderDB.debugForceLocale = nil
	end
	local db = GF.GetDB and GF.GetDB()
	if db then
		db.debugForceLocale = nil
	end
	if GF.db and type(GF.db) == "table" then
		GF.db.debugForceLocale = nil
	end
end

local function refreshLocaleSurfaces()
	if GF.MythicPlusDebugService
		and GF.MythicPlusDebugService.IsEnabled
		and GF.MythicPlusDebugService:IsEnabled()
		and GF.MythicPlusDebugService.Rebuild
	then
		GF.MythicPlusDebugService:Rebuild("debug-locale")
	end
	if GF.MainFrame and GF.MainFrame.RefreshLocale then
		GF.MainFrame:RefreshLocale()
	end
end

local function selectFallbackTab()
	if not (GF.TabBar and GF.TabBar.GetCurrent and GF.TabBar.SelectTab) then
		return
	end
	local current = GF.TabBar:GetCurrent()
	if current == GF.TAB_DEBUG then
		GF.TabBar:SelectTab(GF.TAB_SETTINGS)
	end
end

local function closeCensoredDebugEditor()
	local createPanel = GF.CreatePanel
	if not (createPanel and createPanel.debugCensoredPreviewMode == true) then
		return false
	end
	local inline = GF.MythicPlusCreateManagerPanel
	local sidebarMode = createPanel.IsMythicPlusSidebarMode
		and createPanel:IsMythicPlusSidebarMode()
	if sidebarMode and inline and inline.ClearCensoredDebugPreview then
		inline:ClearCensoredDebugPreview()
	end
	if GF.CreateDrawer and GF.CreateDrawer.Close then
		GF.CreateDrawer:Close(true)
	end
	return true
end

local function closeCensoredBrowsePreview(opts)
	local panel = GF.BrowsePanel
	if panel and panel.ClearCensoredDebugPreview then
		return panel:ClearCensoredDebugPreview(opts)
	end
	return false
end

function Debug:IsDebugModeEnabled()
	local db = GF.GetDB and GF.GetDB()
	return db and db.debugModeEnabled == true
end

function Debug:SetDebugModeEnabled(enabled, opts)
	opts = opts or {}
	local db = GF.GetDB and GF.GetDB()
	if db then
		db.debugModeEnabled = enabled == true
	end
	if GF.TabBar and GF.TabBar.SetDebugTabVisible then
		GF.TabBar:SetDebugTabVisible(enabled == true)
	end
	if enabled then
		if opts.openTab then
			selectDebugTab()
		end
		printChat((GF.L and GF.L.DEBUG_MODE_ON) or "GroupFinder: debug mode enabled.", { semantic = true })
	else
		self._activeCensoredDemoState = nil
		if GF.VersionDiscoveryService
			and GF.VersionDiscoveryService.ClearDebugPreview
		then
			GF.VersionDiscoveryService:ClearDebugPreview()
		end
		closeCensoredDebugEditor()
		closeCensoredBrowsePreview()
		selectFallbackTab()
		if GF.TabBar and GF.TabBar.SetDebugTabVisible then
			GF.TabBar:SetDebugTabVisible(false)
		end
		self:RestoreSystemLocale({ silent = true })
		printChat((GF.L and GF.L.DEBUG_MODE_OFF) or "GroupFinder: debug mode disabled.", { semantic = true })
	end
	refreshDebugPanel()
end

function Debug:GetActiveCensoredDemoState()
	return self._activeCensoredDemoState
end

function Debug:SetActiveCensoredDemoState(state, opts)
	opts = opts or {}
	if not self:IsDebugModeEnabled() then
		return false
	end
	local allowed = {
		hidden = true,
		revealed = true,
		unknown = true,
		off = true,
	}
	if allowed[state] ~= true then
		return false
	end
	if state ~= "off" and GF.RecruitmentSession
		and GF.RecruitmentSession.HasActive
		and GF.RecruitmentSession:HasActive() == true
	then
		local L = GF.L or {}
		printChat(L.DEBUG_CENSORED_DEMO_ACTIVE_BLOCKED
			or "GroupFinder: end the real recruitment before using this UI-only demo.",
			{ semantic = true })
		return false
	end
	self._activeCensoredDemoState = state ~= "off" and state or nil
	closeCensoredDebugEditor()
	if state == "off" then
		closeCensoredBrowsePreview()
	end
	if opts.openCreateTab ~= false then
		self:RefreshApplicantTestDataView()
	end
	if GF.CensoredActiveEntryDialog and GF.CensoredActiveEntryDialog.Refresh then
		GF.CensoredActiveEntryDialog:Refresh("debug-demo")
	end
	if not opts.silent then
		local L = GF.L or {}
		printChat(L.DEBUG_CENSORED_DEMO_SAFE_NOTICE
			or "GroupFinder: active-entry demo is UI-only and does not call Blizzard recruitment APIs.",
			{ semantic = true })
	end
	refreshDebugPanel()
	return true
end

function Debug:ShowCensoredBrowsePreview()
	if not self:IsDebugModeEnabled() then
		return false
	end
	local panel = GF.BrowsePanel
	if not (panel and panel.ShowCensoredDebugPreview) then
		return false
	end
	if GF.MainFrame and GF.MainFrame.OpenBrowseTab then
		GF.MainFrame:OpenBrowseTab()
	end
	local shown = panel:ShowCensoredDebugPreview()
	if shown then
		local L = GF.L or {}
		printChat(L.DEBUG_CENSORED_BROWSE_SAFE_NOTICE
			or "GroupFinder: the hidden-group preview is UI-only and cannot interact with real groups.",
			{ semantic = true })
	end
	return shown == true
end

function Debug:RefreshApplicantTestDataView()
	if GF.MainFrame and GF.MainFrame.OpenCreateTab then
		GF.MainFrame:OpenCreateTab()
	elseif GF.MainFrame and GF.MainFrame.OpenFrame then
		GF.MainFrame:OpenFrame()
	elseif GF.MainFrame and GF.MainFrame.Show then
		GF.MainFrame:Show()
	end
	if GF.MainFrame and GF.MainFrame.UpdateCreateTab then
		GF.MainFrame:UpdateCreateTab()
	end
	if GF.ApplicantsPanel and GF.ApplicantsPanel.Refresh then
		GF.ApplicantsPanel:Refresh()
	end
end

function Debug:SetApplicantTestDataEnabled(enabled, opts)
	opts = opts or {}
	local L = GF.L or {}
	if not (GF.ApplicantTestData and GF.ApplicantTestData.SetEnabled) then
		printChat(L.DEBUG_APPLICANT_TEST_UNAVAILABLE or "GroupFinder: applicant test data is unavailable.", { semantic = true })
		return false
	end
	if opts.scenario and GF.ApplicantTestData.SetScenario then
		GF.ApplicantTestData:SetScenario(opts.scenario)
	end
	if opts.extraRows ~= nil and GF.ApplicantTestData.SetExtraRowCount then
		GF.ApplicantTestData:SetExtraRowCount(opts.extraRows)
	end
	local nextEnabled = enabled == true
	GF.ApplicantTestData:SetEnabled(nextEnabled)
	if not nextEnabled
		and GF.ApplicantsPanel
		and GF.ApplicantsPanel.ClearApplicantTestProjection
	then
		GF.ApplicantsPanel:ClearApplicantTestProjection()
	end
	self:RefreshApplicantTestDataView()
	printChat(enabled
		and (L.DEBUG_APPLICANT_TEST_ON or "GroupFinder: applicant test data enabled.")
		or (L.DEBUG_APPLICANT_TEST_OFF or "GroupFinder: applicant test data disabled."), { semantic = true })
	refreshDebugPanel()
	return true
end

function Debug:ApplyApplicantScenario(scenario)
	local L = GF.L or {}
	local scenarioConfig = {
		basic = { extraRows = 0, label = L.DEBUG_SCENARIO_BASIC or "Basic" },
		full = { extraRows = 0, label = L.DEBUG_SCENARIO_FULL or "Full" },
		scroll = { extraRows = 40, label = L.DEBUG_SCENARIO_SCROLL or "Scroll stress" },
	}
	local config = scenarioConfig[scenario] or scenarioConfig.full
	self:SetApplicantTestDataEnabled(true, {
		scenario = scenario,
		extraRows = config.extraRows,
	})
	printChat(string.format(L.DEBUG_SCENARIO_APPLIED_FMT or "GroupFinder: switched to %s test data.", config.label))
end

function Debug:GetApplicantTestDataStatus()
	local atd = GF.ApplicantTestData
	local enabled = atd and atd.IsEnabled and atd:IsEnabled() == true
	local scenario = atd and atd.GetScenario and atd:GetScenario() or "full"
	local extraRows = atd and atd.GetExtraRowCount and atd:GetExtraRowCount() or 0
	return {
		enabled = enabled,
		scenario = scenario,
		extraRows = extraRows,
	}
end

function Debug:PrintBlackwingDiagnostic()
	local function emit(message)
		printChat("[GF-BWL] " .. tostring(message), { semantic = true })
	end
	local entryCreation = LFGListFrame and LFGListFrame.EntryCreation
	local finder = entryCreation and entryCreation.ActivityFinder
	local selectedActivity = tonumber(entryCreation and entryCreation.selectedActivity)
	local selectedCategory = tonumber(entryCreation and entryCreation.selectedCategory)
	local selectedGroup = tonumber(entryCreation and entryCreation.selectedGroup)
	local baseFilters = tonumber(entryCreation and entryCreation.baseFilters) or 0
	local selectedFilters = tonumber(entryCreation and entryCreation.selectedFilters) or 0
	local effectiveFilters = bit and bit.bor
		and bit.bor(baseFilters, selectedFilters)
		or (baseFilters + selectedFilters)
	local selectedInfo = diagnosticActivityInfo(selectedActivity)
	local legacyInfo = diagnosticActivityInfo(293)
	local function readJournal(mapID)
		mapID = tonumber(mapID)
		local apiInstanceID
		if mapID and C_EncounterJournal and C_EncounterJournal.GetInstanceForGameMap then
			local ok, value = pcall(C_EncounterJournal.GetInstanceForGameMap, mapID)
			apiInstanceID = ok and value or nil
		end
		local row
		if mapID and GF.NavCatalog and GF.NavCatalog.GetJournalInstance then
			local ok, value = pcall(GF.NavCatalog.GetJournalInstance, "raid", mapID, mapID, nil)
			row = ok and value or nil
		end
		return apiInstanceID, row
	end
	local selectedMapID = tonumber(selectedInfo and selectedInfo.mapID)
	local selectedJournalInstanceID, selectedJournal = readJournal(selectedMapID)
	local legacyJournalInstanceID, legacyJournal
	if selectedMapID == 469 then
		legacyJournalInstanceID, legacyJournal = selectedJournalInstanceID, selectedJournal
	else
		legacyJournalInstanceID, legacyJournal = readJournal(469)
	end

	emit("v1 read-only; select Blackwing Lair in Blizzard EntryCreation before running /gf bwl")
	emit(string.format(
		"native cat=%s group=%s activity=%s base=%s selectedFilter=%s effective=%s",
		diagnosticValue(selectedCategory),
		diagnosticValue(selectedGroup),
		diagnosticValue(selectedActivity),
		diagnosticValue(baseFilters),
		diagnosticValue(selectedFilters),
		diagnosticValue(effectiveFilters)
	))
	if selectedInfo then
		emit(string.format(
			"selected id=%s cat=%s group=%s map=%s filters=%s diff=%s order=%s",
			diagnosticValue(selectedActivity),
			diagnosticValue(selectedInfo.categoryID),
			diagnosticValue(selectedInfo.groupFinderActivityGroupID),
			diagnosticValue(selectedInfo.mapID),
			diagnosticValue(selectedInfo.filters),
			diagnosticValue(selectedInfo.difficultyID),
			diagnosticValue(selectedInfo.orderIndex)
		))
		emit(string.format(
			"selected names full=%s | short=%s",
			diagnosticValue(selectedInfo.fullName),
			diagnosticValue(selectedInfo.shortName)
		))
	else
		emit("selected info=nil (native Blackwing Lair selection is required)")
	end
	if legacyInfo then
		emit(string.format(
			"legacy293 cat=%s group=%s map=%s filters=%s diff=%s full=%s | short=%s",
			diagnosticValue(legacyInfo.categoryID),
			diagnosticValue(legacyInfo.groupFinderActivityGroupID),
			diagnosticValue(legacyInfo.mapID),
			diagnosticValue(legacyInfo.filters),
			diagnosticValue(legacyInfo.difficultyID),
			diagnosticValue(legacyInfo.fullName),
			diagnosticValue(legacyInfo.shortName)
		))
	else
		emit("legacy293 info=nil")
	end
	emit(string.format(
		"journal selectedMap=%s apiInstance=%s rowID=%s rowMap=%s rowInstanceMap=%s label=%s",
		diagnosticValue(selectedMapID),
		diagnosticValue(selectedJournalInstanceID),
		diagnosticValue(selectedJournal and selectedJournal.journalInstanceID),
		diagnosticValue(selectedJournal and selectedJournal.mapID),
		diagnosticValue(selectedJournal and selectedJournal.instanceMapID),
		diagnosticValue(selectedJournal and selectedJournal.label)
	))
	emit(string.format(
		"journal legacyMap469 apiInstance=%s rowID=%s rowMap=%s rowInstanceMap=%s label=%s",
		diagnosticValue(legacyJournalInstanceID),
		diagnosticValue(legacyJournal and legacyJournal.journalInstanceID),
		diagnosticValue(legacyJournal and legacyJournal.mapID),
		diagnosticValue(legacyJournal and legacyJournal.instanceMapID),
		diagnosticValue(legacyJournal and legacyJournal.label)
	))

	local navMatch
	if GF.NavCatalog and GF.NavCatalog.GetInstances then
		local ok, instances = pcall(GF.NavCatalog.GetInstances, "raid", 0)
		if ok and type(instances) == "table" then
			for _, instance in ipairs(instances) do
				if selectedMapID and (
						tonumber(instance and instance.mapID) == selectedMapID
						or tonumber(instance and instance.instanceMapID) == selectedMapID
					)
					or selectedActivity and tonumber(instance and instance.activityID) == selectedActivity
				then
					navMatch = instance
					break
				end
			end
		end
	end
	emit(string.format(
		"nav activity=%s group=%s map=%s unavailable=%s label=%s",
		diagnosticValue(navMatch and navMatch.activityID),
		diagnosticValue(navMatch and navMatch.groupID),
		diagnosticValue(navMatch and navMatch.mapID),
		diagnosticValue(navMatch and navMatch.catalogUnavailable),
		diagnosticValue(navMatch and navMatch.label)
	))

	local finderText = diagnosticEditBoxText(finder and finder.Dialog and finder.Dialog.EntryBox)
	emit(string.format(
		"finder cat=%s group=%s filters=%s selected=%s text=%s matches=%s",
		diagnosticValue(finder and finder.categoryID),
		diagnosticValue(finder and finder.groupID),
		diagnosticValue(finder and finder.filters),
		diagnosticValue(finder and finder.selectedActivity),
		diagnosticValue(finderText),
		diagnosticList(finder and finder.matchingActivities, 20)
	))

	local categoryID = selectedCategory or tonumber(finder and finder.categoryID) or 3
	local targetID = selectedActivity or tonumber(finder and finder.selectedActivity) or 293
	local queryGroupID = selectedGroup or tonumber(finder and finder.groupID)
	local searchText = finderText and finderText ~= "" and finderText
		or selectedInfo and selectedInfo.fullName
		or selectedJournal and selectedJournal.label
		or legacyJournal and legacyJournal.label
		or legacyInfo and legacyInfo.fullName
		or "Blackwing Lair"
	local filters, seenFilters = {}, {}
	local function addFilter(value)
		value = tonumber(value)
		if value ~= nil and not seenFilters[value] then
			seenFilters[value] = true
			filters[#filters + 1] = value
		end
	end
	addFilter(effectiveFilters)
	addFilter(selectedInfo and selectedInfo.filters)
	addFilter(legacyInfo and legacyInfo.filters)
	addFilter(finder and finder.filters)
	addFilter(0)
	addFilter(Enum and Enum.LFGListFilter and Enum.LFGListFilter.PvE)
	if Enum and Enum.LFGListFilter then
		local pve = tonumber(Enum.LFGListFilter.PvE) or 0
		local recommended = tonumber(Enum.LFGListFilter.Recommended) or 0
		local notRecommended = tonumber(Enum.LFGListFilter.NotRecommended) or 0
		addFilter(bit and bit.bor and bit.bor(recommended, pve) or recommended + pve)
		addFilter(bit and bit.bor and bit.bor(notRecommended, pve) or notRecommended + pve)
	end

	for _, filterFlags in ipairs(filters) do
		local top, topError = diagnosticAvailableActivities(categoryID, 0, filterFlags)
		local scope, scopeError = diagnosticAvailableActivities(categoryID, nil, filterFlags)
		local empty, emptyError = diagnosticAvailableActivities(categoryID, nil, filterFlags, "")
		local named, namedError = diagnosticAvailableActivities(categoryID, nil, filterFlags, searchText)
		local grouped, groupedError
		if queryGroupID ~= nil then
			grouped, groupedError = diagnosticAvailableActivities(
				categoryID, queryGroupID, filterFlags)
		else
			groupedError = "no-group"
		end
		emit(string.format(
			"q f=%s g=%s text=%s target=%s hit=%s/%s/%s/%s/%s n=%s/%s/%s/%s/%s namedIDs=%s",
			diagnosticValue(filterFlags),
			diagnosticValue(queryGroupID),
			diagnosticValue(searchText),
			diagnosticValue(targetID),
			diagnosticValue(diagnosticHit(top, topError, targetID)),
			diagnosticValue(diagnosticHit(scope, scopeError, targetID)),
			diagnosticValue(diagnosticHit(empty, emptyError, targetID)),
			diagnosticValue(diagnosticHit(named, namedError, targetID)),
			diagnosticValue(diagnosticHit(grouped, groupedError, targetID)),
			diagnosticValue(top and #top or topError),
			diagnosticValue(scope and #scope or scopeError),
			diagnosticValue(empty and #empty or emptyError),
			diagnosticValue(named and #named or namedError),
			diagnosticValue(grouped and #grouped or groupedError),
			diagnosticList(named, 20)
		))
	end
	emit("done; capture every [GF-BWL] line")
	return true
end

function Debug:PrintClassicDungeonDiagnostic()
	local function emit(message)
		printChat("[GF-CLASSIC] " .. tostring(message), { semantic = true })
	end
	local entryCreation = LFGListFrame and LFGListFrame.EntryCreation
	local finder = entryCreation and entryCreation.ActivityFinder
	local selectedActivity = tonumber(entryCreation and entryCreation.selectedActivity)
		or tonumber(finder and finder.selectedActivity)
	local selectedCategory = tonumber(entryCreation and entryCreation.selectedCategory)
		or tonumber(finder and finder.categoryID)
	local selectedGroup = tonumber(entryCreation and entryCreation.selectedGroup)
	local finderGroup = tonumber(finder and finder.groupID)
	local baseFilters = tonumber(entryCreation and entryCreation.baseFilters) or 0
	local selectedFilters = tonumber(entryCreation and entryCreation.selectedFilters) or 0
	local effectiveFilters = bit and bit.bor
		and bit.bor(baseFilters, selectedFilters)
		or (baseFilters + selectedFilters)
	local selectedInfo = diagnosticActivityInfo(selectedActivity)
	local infoGroup = tonumber(selectedInfo and selectedInfo.groupFinderActivityGroupID)
	local selectedMapID = tonumber(selectedInfo and selectedInfo.mapID)
	local legacyGroupByMap = {
		[48] = 6,
		[230] = 7,
		[229] = 8,
	}
	local legacyGroup = legacyGroupByMap[selectedMapID]
	local function groupName(groupID)
		if not (groupID and C_LFGList and C_LFGList.GetActivityGroupInfo) then
			return nil
		end
		local ok, name = pcall(C_LFGList.GetActivityGroupInfo, groupID)
		return ok and name or nil
	end
	local function activityBaseName(info)
		if not info then
			return nil
		end
		local getter = GF.ActivityInfo and GF.ActivityInfo.GetActivityBaseName
		if getter then
			local ok, value = pcall(getter, info)
			if ok and value and value ~= "" then
				return value
			end
		end
		return info.shortName or info.fullName
	end
	local finderText = diagnosticEditBoxText(finder and finder.Dialog and finder.Dialog.EntryBox)
	local searchText = finderText and finderText ~= "" and finderText
		or activityBaseName(selectedInfo)
		or selectedInfo and selectedInfo.shortName
		or selectedInfo and selectedInfo.fullName
	local activityListReady = "missing"
	if C_LFGList and type(C_LFGList.HasActivityList) == "function" then
		local ok, value = pcall(C_LFGList.HasActivityList)
		activityListReady = ok and diagnosticValue(value) or "error"
	end

	emit("v2 read-only; select one of the three missing Classic dungeons in Blizzard EntryCreation before running /gf classic")
	emit(string.format(
		"native ready=%s cat=%s group=%s activity=%s base=%s selectedFilter=%s effective=%s",
		diagnosticValue(activityListReady),
		diagnosticValue(selectedCategory),
		diagnosticValue(selectedGroup),
		diagnosticValue(selectedActivity),
		diagnosticValue(baseFilters),
		diagnosticValue(selectedFilters),
		diagnosticValue(effectiveFilters)
	))
	if selectedInfo then
		emit(string.format(
			"selected id=%s cat=%s group=%s map=%s filters=%s diff=%s order=%s",
			diagnosticValue(selectedActivity),
			diagnosticValue(selectedInfo.categoryID),
			diagnosticValue(selectedInfo.groupFinderActivityGroupID),
			diagnosticValue(selectedInfo.mapID),
			diagnosticValue(selectedInfo.filters),
			diagnosticValue(selectedInfo.difficultyID),
			diagnosticValue(selectedInfo.orderIndex)
		))
		emit(string.format(
			"selected names base=%s | full=%s | short=%s",
			diagnosticValue(activityBaseName(selectedInfo)),
			diagnosticValue(selectedInfo.fullName),
			diagnosticValue(selectedInfo.shortName)
		))
	else
		emit("selected info=nil (native Blackfathom Deeps, Blackrock Depths, or Lower Blackrock Spire selection is required)")
	end
	emit(string.format(
		"groups native=%s/%s finder=%s/%s info=%s/%s legacy=%s/%s",
		diagnosticValue(selectedGroup), diagnosticValue(groupName(selectedGroup)),
		diagnosticValue(finderGroup), diagnosticValue(groupName(finderGroup)),
		diagnosticValue(infoGroup), diagnosticValue(groupName(infoGroup)),
		diagnosticValue(legacyGroup), diagnosticValue(groupName(legacyGroup))
	))

	local apiJournalInstanceID
	if selectedMapID and C_EncounterJournal and C_EncounterJournal.GetInstanceForGameMap then
		local ok, value = pcall(C_EncounterJournal.GetInstanceForGameMap, selectedMapID)
		apiJournalInstanceID = ok and value or nil
	end
	local journalRow
	if selectedMapID and GF.NavCatalog and GF.NavCatalog.GetJournalInstance then
		local ok, value = pcall(
			GF.NavCatalog.GetJournalInstance, "dungeon", selectedMapID, selectedMapID, nil)
		journalRow = ok and value or nil
	end
	emit(string.format(
		"journal map=%s apiInstance=%s rowID=%s rowMap=%s rowInstanceMap=%s label=%s",
		diagnosticValue(selectedMapID),
		diagnosticValue(apiJournalInstanceID),
		diagnosticValue(journalRow and journalRow.journalInstanceID),
		diagnosticValue(journalRow and journalRow.mapID),
		diagnosticValue(journalRow and journalRow.instanceMapID),
		diagnosticValue(journalRow and journalRow.label)
	))

	local classicCount, navMatches = 0, {}
	if GF.NavCatalog and GF.NavCatalog.GetInstances then
		local ok, instances = pcall(GF.NavCatalog.GetInstances, "dungeon", 0)
		if ok and type(instances) == "table" then
			classicCount = #instances
			for _, instance in ipairs(instances) do
				local matches = selectedMapID and (
					tonumber(instance and instance.mapID) == selectedMapID
					or tonumber(instance and instance.instanceMapID) == selectedMapID)
				if not matches and selectedActivity then
					matches = tonumber(instance and instance.activityID) == selectedActivity
					for _, activityID in ipairs(instance and instance.activityIDs or {}) do
						matches = matches or tonumber(activityID) == selectedActivity
					end
				end
				if matches then
					navMatches[#navMatches + 1] = string.format(
						"%s:id=%s:g=%s:m=%s:u=%s:snap=%s",
						diagnosticValue(instance.label),
						diagnosticValue(instance.activityID or (instance.activityIDs and instance.activityIDs[1])),
						diagnosticValue(instance.groupID),
						diagnosticValue(instance.mapID or instance.instanceMapID),
						diagnosticValue(instance.catalogUnavailable),
						diagnosticValue(type(instance.activityInfo) == "table")
					)
				end
			end
		end
	end
	emit(string.format(
		"nav classicCount=%s targetMatches=%s rows=%s",
		diagnosticValue(classicCount),
		diagnosticValue(#navMatches),
		#navMatches > 0 and table.concat(navMatches, " || ") or "none"
	))

	local dungeonRoot, classicNode
	for _, root in ipairs(GF.navTree or {}) do
		if root and root.key == "dungeon" then
			dungeonRoot = root
			break
		end
	end
	for _, child in ipairs(dungeonRoot and dungeonRoot.children or {}) do
		if tonumber(child and child.expansionIndex) == 0 then
			classicNode = child
			break
		end
	end
	local treeMatches = {}
	local function visitTree(node, path)
		if not node then
			return
		end
		local label = diagnosticValue(node.label)
		local nextPath = path ~= "" and (path .. ">" .. label) or label
		local matches = selectedActivity and tonumber(node.activityID) == selectedActivity
		for _, activityID in ipairs(node.activityIDsFilter or {}) do
			matches = matches or selectedActivity and tonumber(activityID) == selectedActivity
		end
		if matches then
			treeMatches[#treeMatches + 1] = string.format(
				"%s:key=%s:id=%s:g=%s",
				nextPath,
				diagnosticValue(node.key),
				diagnosticValue(node.activityID),
				diagnosticValue(node.groupID)
			)
		end
		for _, child in ipairs(node.children or {}) do
			visitTree(child, nextPath)
		end
	end
	if classicNode then
		visitTree(classicNode, "")
	end
	emit(string.format(
		"tree dungeonLoaded=%s classicLoaded=%s classicChildren=%s targetMatches=%s rows=%s",
		diagnosticValue(dungeonRoot and dungeonRoot.childrenLoaded),
		diagnosticValue(classicNode and classicNode.childrenLoaded),
		diagnosticValue(classicNode and classicNode.children and #classicNode.children or 0),
		diagnosticValue(#treeMatches),
		#treeMatches > 0 and table.concat(treeMatches, " || ") or "none"
	))
	emit(string.format(
		"finder cat=%s group=%s filters=%s selected=%s text=%s matches=%s",
		diagnosticValue(finder and finder.categoryID),
		diagnosticValue(finderGroup),
		diagnosticValue(finder and finder.filters),
		diagnosticValue(finder and finder.selectedActivity),
		diagnosticValue(finderText),
		diagnosticList(finder and finder.matchingActivities, 20)
	))

	local categoryID = selectedCategory or tonumber(finder and finder.categoryID) or 2
	local filters, seenFilters = {}, {}
	local function addFilter(value)
		value = tonumber(value)
		if value ~= nil and not seenFilters[value] then
			seenFilters[value] = true
			filters[#filters + 1] = value
		end
	end
	addFilter(effectiveFilters)
	addFilter(selectedInfo and selectedInfo.filters)
	addFilter(finder and finder.filters)
	addFilter(0)
	local lfgFilters = Enum and Enum.LFGListFilter or {}
	local recommended = tonumber(lfgFilters.Recommended) or 0
	local notRecommended = tonumber(lfgFilters.NotRecommended) or 0
	local pve = tonumber(lfgFilters.PvE) or 0
	addFilter(recommended)
	addFilter(notRecommended)
	addFilter(pve)
	addFilter(bit and bit.bor and bit.bor(recommended, pve) or recommended + pve)
	addFilter(bit and bit.bor and bit.bor(notRecommended, pve) or notRecommended + pve)
	local unresolvedCount = #filters
	if bit and bit.band and bit.bnot and bit.bor then
		for index = 1, unresolvedCount do
			addFilter(bit.band(bit.bnot(notRecommended), bit.bor(filters[index], recommended)))
		end
	end

	for _, filterFlags in ipairs(filters) do
		local top, topError = diagnosticAvailableActivities(categoryID, 0, filterFlags)
		local scope, scopeError = diagnosticAvailableActivities(categoryID, nil, filterFlags)
		local empty, emptyError = diagnosticAvailableActivities(categoryID, nil, filterFlags, "")
		local named, namedError = diagnosticAvailableActivities(categoryID, nil, filterFlags, searchText)
		local native, nativeError = diagnosticAvailableActivities(
			categoryID, finderGroup, filterFlags, searchText)
		local current, currentError = diagnosticAvailableActivities(
			categoryID, infoGroup, filterFlags, searchText)
		local legacy, legacyError = diagnosticAvailableActivities(
			categoryID, legacyGroup, filterFlags, searchText)
		emit(string.format(
			"q f=%s text=%s target=%s hit=%s/%s/%s/%s/%s/%s/%s n=%s/%s/%s/%s/%s/%s/%s named=%s native=%s current=%s legacy=%s",
			diagnosticValue(filterFlags),
			diagnosticValue(searchText),
			diagnosticValue(selectedActivity),
			diagnosticValue(diagnosticHit(top, topError, selectedActivity)),
			diagnosticValue(diagnosticHit(scope, scopeError, selectedActivity)),
			diagnosticValue(diagnosticHit(empty, emptyError, selectedActivity)),
			diagnosticValue(diagnosticHit(named, namedError, selectedActivity)),
			diagnosticValue(diagnosticHit(native, nativeError, selectedActivity)),
			diagnosticValue(diagnosticHit(current, currentError, selectedActivity)),
			diagnosticValue(diagnosticHit(legacy, legacyError, selectedActivity)),
			diagnosticValue(top and #top or topError),
			diagnosticValue(scope and #scope or scopeError),
			diagnosticValue(empty and #empty or emptyError),
			diagnosticValue(named and #named or namedError),
			diagnosticValue(native and #native or nativeError),
			diagnosticValue(current and #current or currentError),
			diagnosticValue(legacy and #legacy or legacyError),
			diagnosticList(named, 20),
			diagnosticList(native, 20),
			diagnosticList(current, 20),
			diagnosticList(legacy, 20)
		))
	end
	emit("done; capture every [GF-CLASSIC] line")
	return true
end

function Debug:PrintSeasonRaidDiagnostic()
	local function emit(message)
		printChat("[GF-RAID] " .. tostring(message), { semantic = true })
	end
	local function resultList(values, reason, limit)
		if type(values) == "table" then
			return diagnosticList(values, limit)
		end
		return diagnosticValue(reason)
	end
	local function groupName(groupID)
		local getter = C_LFGList and C_LFGList.GetActivityGroupInfo
		if type(getter) ~= "function" then
			return nil
		end
		local ok, name = pcall(getter, groupID)
		return ok and name or nil
	end
	local function combine(...)
		if bit and bit.bor then
			return bit.bor(...)
		end
		local value = 0
		for index = 1, select("#", ...) do
			value = value + (tonumber(select(index, ...)) or 0)
		end
		return value
	end

	local categoryID = tonumber(GF.CAT_RAID) or 3
	local entryCreation = LFGListFrame and LFGListFrame.EntryCreation
	local baseFilters = tonumber(entryCreation and entryCreation.baseFilters) or 0
	local selectedFilters = tonumber(entryCreation and entryCreation.selectedFilters) or 0
	local effectiveFilters = combine(baseFilters, selectedFilters)
	local ready = "missing"
	if C_LFGList and type(C_LFGList.HasActivityList) == "function" then
		local ok, value = pcall(C_LFGList.HasActivityList)
		ready = ok and diagnosticValue(value) or "error"
	end

	local lfgFilters = Enum and Enum.LFGListFilter or {}
	local recommended = tonumber(lfgFilters.Recommended) or 0
	local notRecommended = tonumber(lfgFilters.NotRecommended) or 0
	local pve = tonumber(lfgFilters.PvE) or 0
	local currentExpansion = tonumber(lfgFilters.CurrentExpansion) or 0
	local notCurrentSeason = tonumber(lfgFilters.NotCurrentSeason) or 0
	local filters, seenFilters = {}, {}
	local function addFilter(value)
		value = tonumber(value)
		if value ~= nil and not seenFilters[value] then
			seenFilters[value] = true
			filters[#filters + 1] = value
		end
	end
	addFilter(0)
	addFilter(recommended)
	addFilter(notRecommended)
	addFilter(pve)
	addFilter(combine(recommended, pve))
	addFilter(combine(notRecommended, pve))
	for _, scope in ipairs({
		currentExpansion,
		notCurrentSeason,
		combine(currentExpansion, notCurrentSeason),
	}) do
		addFilter(combine(scope, pve))
		addFilter(combine(recommended, scope, pve))
		addFilter(combine(notRecommended, scope, pve))
	end
	addFilter(effectiveFilters)

	local targetGroups = { 429, 424 }
	local targetNames = {
		[429] = groupName(429),
		[424] = groupName(424),
	}
	local collected = {}
	local availableByTargetGroup = {
		[429] = {},
		[424] = {},
	}
	local function collect(values)
		for _, activityID in ipairs(values or {}) do
			activityID = tonumber(activityID)
			if activityID then
				collected[activityID] = true
			end
		end
	end
	local function collectTarget(groupID, values)
		local target = availableByTargetGroup[groupID]
		for _, activityID in ipairs(values or {}) do
			activityID = tonumber(activityID)
			if target and activityID then
				target[activityID] = true
			end
		end
	end

	emit("v2 read-only; no Blizzard selection required; this command does not search or change listings")
	emit(string.format(
		"native ready=%s cat=%s base=%s selectedFilter=%s effective=%s",
		diagnosticValue(ready),
		diagnosticValue(categoryID),
		diagnosticValue(baseFilters),
		diagnosticValue(selectedFilters),
		diagnosticValue(effectiveFilters)
	))
	emit(string.format(
		"targets g429=%s g424=%s",
		diagnosticValue(targetNames[429]),
		diagnosticValue(targetNames[424])
	))

	for _, filterFlags in ipairs(filters) do
		local groups, groupsError = diagnosticAvailableGroups(categoryID, filterFlags)
		local top, topError = diagnosticAvailableActivities(categoryID, 0, filterFlags)
		local scope, scopeError = diagnosticAvailableActivities(categoryID, nil, filterFlags)
		emit(string.format(
			"q f=%s groups=%s top=%s scope=%s",
			diagnosticValue(filterFlags),
			resultList(groups, groupsError, 30),
			resultList(top, topError, 30),
			resultList(scope, scopeError, 30)
		))

		local groupedResults, namedResults = {}, {}
		for _, groupID in ipairs(targetGroups) do
			local grouped, groupedError = diagnosticAvailableActivities(
				categoryID, groupID, filterFlags)
			local named, namedError
			if targetNames[groupID] and targetNames[groupID] ~= "" then
				named, namedError = diagnosticAvailableActivities(
					categoryID, nil, filterFlags, targetNames[groupID])
			else
				namedError = "no-name"
			end
			collect(grouped)
			collect(named)
			collectTarget(groupID, grouped)
			collectTarget(groupID, named)
			groupedResults[groupID] = resultList(grouped, groupedError, 20)
			namedResults[groupID] = resultList(named, namedError, 20)
		end
		emit(string.format(
			"q f=%s g429=%s g424=%s text429=%s text424=%s",
			diagnosticValue(filterFlags),
			groupedResults[429],
			groupedResults[424],
			namedResults[429],
			namedResults[424]
		))
	end
	local function sortedTargetIDs(groupID)
		local values = {}
		for activityID in pairs(availableByTargetGroup[groupID] or {}) do
			values[#values + 1] = activityID
		end
		table.sort(values)
		return values
	end
	local available429 = sortedTargetIDs(429)
	local available424 = sortedTargetIDs(424)
	emit(string.format(
		"available-targets g429=%s g424=%s",
		diagnosticList(available429, 30),
		diagnosticList(available424, 30)
	))

	for activityID = 1954, 1958 do
		collected[activityID] = true
	end
	for activityID = 2001, 2006 do
		collected[activityID] = true
	end
	local activityIDs = {}
	for activityID in pairs(collected) do
		activityIDs[#activityIDs + 1] = activityID
	end
	table.sort(activityIDs)
	for _, activityID in ipairs(activityIDs) do
		local info = diagnosticActivityInfo(activityID)
		if info then
			emit(string.format(
				"info id=%s cat=%s group=%s map=%s filters=%s diff=%s current=%s normal=%s heroic=%s mythic=%s key=%s full=%s | short=%s",
				diagnosticValue(activityID),
				diagnosticValue(info.categoryID),
				diagnosticValue(info.groupFinderActivityGroupID),
				diagnosticValue(info.mapID),
				diagnosticValue(info.filters),
				diagnosticValue(info.difficultyID),
				diagnosticValue(info.isCurrentRaidActivity),
				diagnosticValue(info.isNormalActivity),
				diagnosticValue(info.isHeroicActivity),
				diagnosticValue(info.isMythicActivity),
				diagnosticValue(info.isMythicPlusActivity),
				diagnosticValue(info.fullName),
				diagnosticValue(info.shortName)
			))
		else
			emit(string.format("info id=%s nil", diagnosticValue(activityID)))
		end
	end

	local seasonInstances
	if GF.NavCatalog and type(GF.NavCatalog.GetSeasonInstances) == "function" then
		local ok, value = pcall(GF.NavCatalog.GetSeasonInstances, "raid")
		seasonInstances = ok and type(value) == "table" and value or nil
	end
	if seasonInstances then
		emit(string.format("catalog count=%s", diagnosticValue(#seasonInstances)))
		for index, instance in ipairs(seasonInstances) do
			emit(string.format(
				"catalog i=%s label=%s group=%s map=%s activity=%s ids=%s unavailable=%s",
				diagnosticValue(index),
				diagnosticValue(instance and instance.label),
				diagnosticValue(instance and instance.groupID),
				diagnosticValue(instance and (instance.mapID or instance.instanceMapID)),
				diagnosticValue(instance and instance.activityID),
				diagnosticList(instance and instance.activityIDs, 20),
				diagnosticValue(instance and instance.catalogUnavailable)
			))
		end
	else
		emit("catalog unavailable")
	end

	local pending = {}
	for index = #(GF.navTree or {}), 1, -1 do
		pending[#pending + 1] = GF.navTree[index]
	end
	local treeHits = 0
	while #pending > 0 do
		local current = table.remove(pending)
		if current and current.navKind == "season_raid" and (current.level or 0) == 0 then
			emit(string.format(
				"tree root key=%s loaded=%s childCount=%s",
				diagnosticValue(current.key),
				diagnosticValue(current.childrenLoaded),
				diagnosticValue(type(current.children) == "table" and #current.children or nil)
			))
		end
		local groupID = tonumber(current and current.groupID)
		if groupID == 429 or groupID == 424 then
			treeHits = treeHits + 1
			emit(string.format(
				"tree hit key=%s level=%s label=%s group=%s activity=%s ids=%s loaded=%s childCount=%s",
				diagnosticValue(current.key),
				diagnosticValue(current.level),
				diagnosticValue(current.label),
				diagnosticValue(groupID),
				diagnosticValue(current.activityID),
				diagnosticList(current.activityIDs or current.activityIDsFilter, 20),
				diagnosticValue(current.childrenLoaded),
				diagnosticValue(type(current.children) == "table" and #current.children or nil)
			))
		end
		for index = #(current and current.children or {}), 1, -1 do
			pending[#pending + 1] = current.children[index]
		end
	end
	if treeHits == 0 then
		emit("tree targetHits=0")
	end
	emit("done; capture every [GF-RAID] line")
	return true
end

function Debug:PrintHelp()
	printChat((GF.L and GF.L.DEBUG_HELP) or "GroupFinder: debug commands: /gf 开启调试模式, /gf 关闭调试模式, /gf testdata.")
end

function Debug:RunLFGCoverageAudit()
	if not self:IsDebugModeEnabled() then
		return false
	end
	local audit = GF.LFGCoverageAudit
	if not (audit and type(audit.Run) == "function") then
		printChat((GF.L and GF.L.DEBUG_LFG_COVERAGE_UNAVAILABLE)
			or "[GroupFinder] LFG coverage audit is unavailable.")
		return false
	end
	if audit.IsRunning and audit:IsRunning() then
		printChat((GF.L and GF.L.DEBUG_LFG_COVERAGE_BUSY)
			or "[GroupFinder] LFG coverage audit is already running.")
		return false
	end
	printChat((GF.L and GF.L.DEBUG_LFG_COVERAGE_START)
		or "[GroupFinder] checking current-character LFG activity coverage with read-only APIs.")
	local started = audit:Run(function(report)
		local lines = audit.FormatReport and audit:FormatReport(report) or {}
		for _, line in ipairs(lines) do
			printChat("[GF-LFG-AUDIT] " .. tostring(line), { semantic = true })
		end
	end)
	if not started then
		printChat((GF.L and GF.L.DEBUG_LFG_COVERAGE_BUSY)
			or "[GroupFinder] LFG coverage audit is already running.")
	end
	return started == true
end

function Debug:ForceLocale(locale)
	if locale ~= "enUS" and locale ~= "zhCN" and locale ~= "zhTW" then
		return false
	end
	clearSavedLocaleOverride()
	if not (GF.Locale and GF.Locale.SetDebugLocale and GF.Locale:SetDebugLocale(locale)) then
		return false
	end
	refreshLocaleSurfaces()
	local L = GF.L or {}
	local label
	if locale == "enUS" then
		label = L.DEBUG_FORCE_LOCALE_ENUS_LABEL or "English"
	elseif locale == "zhTW" then
		label = L.DEBUG_FORCE_LOCALE_ZHTW_LABEL or "Traditional Chinese"
	else
		label = L.DEBUG_FORCE_LOCALE_ZHCN_LABEL or "Simplified Chinese"
	end
	printChat(string.format(L.DEBUG_FORCE_LOCALE_RELOAD_FMT or "GroupFinder: switched to %s for debug mode.", label))
	return true
end

function Debug:RestoreSystemLocale(opts)
	opts = opts or {}
	clearSavedLocaleOverride()
	if not (GF.Locale and GF.Locale.ClearDebugLocale) then
		return false
	end
	local wasDebugLocale = GF.Locale.IsDebugLocaleActive and GF.Locale:IsDebugLocaleActive()
	GF.Locale:ClearDebugLocale()
	if wasDebugLocale then
		refreshLocaleSurfaces()
		if not opts.silent then
			printChat((GF.L and GF.L.DEBUG_LOCALE_RESTORED) or "GroupFinder: system locale restored.")
		end
	end
	return wasDebugLocale == true
end

function Debug:RunAction(actionID, payload)
	if actionID == "showTab" then
		self:SetDebugModeEnabled(true, { openTab = true })
		return true
	end
	if actionID == "hideTab" then
		self:SetDebugModeEnabled(false)
		return true
	end
	if actionID == "applicantsToggle" then
		local enabled = not (GF.ApplicantTestData and GF.ApplicantTestData.IsEnabled and GF.ApplicantTestData:IsEnabled())
		return self:SetApplicantTestDataEnabled(enabled)
	end
	if actionID == "applicantsOn" then
		return self:SetApplicantTestDataEnabled(true)
	end
	if actionID == "applicantsOff" then
		return self:SetApplicantTestDataEnabled(false)
	end
	if actionID == "refreshApplicants" then
		self:RefreshApplicantTestDataView()
		refreshDebugPanel()
		return true
	end
	if actionID == "scenarioBasic" then
		self:ApplyApplicantScenario("basic")
		return true
	end
	if actionID == "scenarioFull" then
		self:ApplyApplicantScenario("full")
		return true
	end
	if actionID == "scenarioScroll" then
		self:ApplyApplicantScenario("scroll")
		return true
	end
	if actionID == "censoredActiveUnknown" then
		local changed = self:SetActiveCensoredDemoState("unknown", {
			silent = true,
		})
		if not changed then
			return false
		end
		return GF.CensoredActiveEntryDialog
			and GF.CensoredActiveEntryDialog:Show({ debugPreview = true })
	end
	if actionID == "censoredActiveOff" then
		return self:SetActiveCensoredDemoState("off")
	end
	if actionID == "censoredActiveDialog" then
		local current = self:GetActiveCensoredDemoState()
		if current == nil or current == "off" then
			self:SetActiveCensoredDemoState("hidden", { silent = true })
		end
		return GF.CensoredActiveEntryDialog
			and GF.CensoredActiveEntryDialog:Show({ debugPreview = true })
	end
	if actionID == "censoredBrowsePreview" then
		return self:ShowCensoredBrowsePreview()
	end
	if actionID == "mplusTestData" then
		return GF.MythicPlusDebugService
			and GF.MythicPlusDebugService:Enable(0)
	end
	if actionID == "mplusTestData1" then
		return GF.MythicPlusDebugService
			and GF.MythicPlusDebugService:Enable(1)
	end
	if actionID == "mplusTestData2" then
		return GF.MythicPlusDebugService
			and GF.MythicPlusDebugService:Enable(2)
	end
	if actionID == "mplusTestData3" then
		return GF.MythicPlusDebugService
			and GF.MythicPlusDebugService:Enable(3)
	end
	if actionID == "mplusTestDataOff" then
		return GF.MythicPlusDebugService
			and GF.MythicPlusDebugService:Disable()
	end
	if actionID == "mplusTeleportDump" then
		return GF.MythicPlusDebugService
			and GF.MythicPlusDebugService:DumpTeleportDiagnostics()
	end
	if actionID == "mplusTeleportDialog" then
		return GF.MythicPlusDebugService
			and GF.MythicPlusDebugService:ShowTeleportDialogPreview(payload)
	end
	if actionID == "help" then
		self:PrintHelp()
		return true
	end
	if actionID == "testNewVersion" then
		if not self:IsDebugModeEnabled() then
			return false
		end
		return GF.VersionDiscoveryService
			and GF.VersionDiscoveryService:PreviewNewVersion()
	end
	if actionID == "lfgCoverageAudit" then
		return self:RunLFGCoverageAudit()
	end
	if actionID == "forceLocaleEnUS" then
		return self:ForceLocale("enUS")
	end
	if actionID == "forceLocaleZhCN" then
		return self:ForceLocale("zhCN")
	end
	if actionID == "forceLocaleZhTW" then
		return self:ForceLocale("zhTW")
	end
	return false
end

function Debug:HandleSlashCommand(msg)
	local raw, cmd = normalizeSlashCommand(msg)
	local memoryDiagnostics = GF.SearchMemoryDiagnostics
	if cmd == "bwl" or cmd == "debug bwl" or raw == "黑翼之巢取证" then
		return self:PrintBlackwingDiagnostic()
	end
	if cmd == "classic" or cmd == "debug classic" or raw == "经典旧世取证" then
		return self:PrintClassicDungeonDiagnostic()
	end
	if cmd == "seasonraid" or cmd == "debug seasonraid" or raw == "赛季团本取证" then
		return self:PrintSeasonRaidDiagnostic()
	end
	if cmd == "memdiag on" then
		if memoryDiagnostics and memoryDiagnostics.SetEnabled then
			memoryDiagnostics:SetEnabled(true)
			memoryDiagnostics:PrintStatus()
		end
		return true
	end
	if cmd == "memdiag off" then
		if memoryDiagnostics and memoryDiagnostics.SetEnabled then
			memoryDiagnostics:SetEnabled(false)
			memoryDiagnostics:PrintStatus()
		end
		return true
	end
	if cmd == "memdiag" or cmd == "memdiag status" then
		if memoryDiagnostics and memoryDiagnostics.PrintStatus then
			memoryDiagnostics:PrintStatus()
		end
		return true
	end
	if cmd == "memdiag last" then
		if memoryDiagnostics and memoryDiagnostics.PrintLast then
			memoryDiagnostics:PrintLast()
		end
		return true
	end
	if raw == "开启调试模式" or cmd == "debug on" then
		return self:RunAction("showTab")
	end
	if raw == "关闭调试模式" or cmd == "debug off" then
		return self:RunAction("hideTab")
	end
	if cmd == "testdata" or raw == "测试数据" or cmd == "debug applicants" then
		return self:RunAction("applicantsToggle")
	end
	if cmd == "testdata on" or cmd == "debug applicants on" then
		return self:RunAction("applicantsOn")
	end
	if cmd == "testdata off" or cmd == "debug applicants off" then
		return self:RunAction("applicantsOff")
	end
	if cmd == "testdata basic" or raw == "测试数据 精简" then
		return self:RunAction("scenarioBasic")
	end
	if cmd == "testdata full" or raw == "测试数据 综合" then
		return self:RunAction("scenarioFull")
	end
	if cmd == "testdata scroll" or raw == "测试数据 滚动" then
		return self:RunAction("scenarioScroll")
	end
	if cmd == "debug locale en" or cmd == "debug locale enus" or raw == "英文" then
		return self:RunAction("forceLocaleEnUS")
	end
	if cmd == "debug locale zh" or cmd == "debug locale zhcn" or raw == "简体中文" then
		return self:RunAction("forceLocaleZhCN")
	end
	if cmd == "debug locale zhtw" or raw == "繁体中文" or raw == "繁體中文" then
		return self:RunAction("forceLocaleZhTW")
	end
	if cmd == "debug help" or raw == "调试帮助" then
		return self:RunAction("help")
	end
	return false
end
