--[[
    Horizon Suite - Focus - Slash Commands
    /h focus [cmd] subcommands. Registers with core via addon.RegisterSlashHandler.
]]

local addon = _G.HorizonSuite
if not addon or not addon.RegisterSlashHandler then return end
local HSPrint = addon.HSPrint or function(msg) print("|cFF00CCFFHorizon Suite - Focus:|r " .. tostring(msg or "")) end
local L = addon.L
local colorCheckState = nil

local function DeepCopy(value)
    if type(value) ~= "table" then return value end
    local out = {}
    for k, v in pairs(value) do
        out[k] = DeepCopy(v)
    end
    return out
end

local function StopColorCheck(announce)
    if not colorCheckState then return end
    if colorCheckState.ticker and colorCheckState.ticker.Cancel then
        colorCheckState.ticker:Cancel()
    end

    addon.SetDB("colorMatrix", DeepCopy(colorCheckState.restore.colorMatrix))
    addon.SetDB("highlightColor", DeepCopy(colorCheckState.restore.highlightColor))
    addon.SetDB("completedObjectiveColor", DeepCopy(colorCheckState.restore.completedObjectiveColor))
    addon.SetDB("useCompletedObjectiveColor", colorCheckState.restore.useCompletedObjectiveColor)

    if addon.ApplyFocusColors then
        addon.ApplyFocusColors()
    elseif addon.FullLayout then
        addon.FullLayout()
    end

    colorCheckState = nil
    if announce then
        HSPrint(L["FOCUS_SLASH_COLORCHECK_STOPPED"])
    end
end

local function MatrixKey(category)
    if category == "RARE" then return "RARES" end
    if category == "ACHIEVEMENT" then return "ACHIEVEMENTS" end
    if category == "ENDEAVOR" then return "ENDEAVORS" end
    if category == "DECOR" then return "DECOR" end
    if category == "APPEARANCE" then return "APPEARANCES" end
    if category == "RECIPE" then return "RECIPES" end
    return category
end

local function CategoryFromEntry(entry)
    local category = entry and entry.category
    if category then return category end
    local groupKey = entry and entry.groupKey
    if groupKey == "RARESCANNER" then return "RARESCANNER" end
    if groupKey == "RARES" then return "RARE" end
    if groupKey == "RARE_LOOT" then return "RARE_LOOT" end
    if groupKey == "ACHIEVEMENTS" then return "ACHIEVEMENT" end
    if groupKey == "ENDEAVORS" then return "ENDEAVOR" end
    if groupKey == "DECOR" then return "DECOR" end
    if groupKey == "APPEARANCES" then return "APPEARANCE" end
    if groupKey == "RECIPES" then return "RECIPE" end
    return nil
end

local function CollectVisibleColorKeys()
    local categoryKeys = {}
    local sectionKeys = {}

    if addon.activeMap then
        for _, entry in pairs(addon.activeMap) do
            if entry and (entry.questID or entry.entryKey) then
                local cat = CategoryFromEntry(entry)
                if cat then categoryKeys[MatrixKey(cat)] = true end
            end
        end
    end
    if next(categoryKeys) == nil then
        categoryKeys.DEFAULT = true
    end

    local sectionPool = addon.sectionPool
    if sectionPool then
        for i = 1, addon.SECTION_POOL_SIZE do
            local s = sectionPool[i]
            if s and s.groupKey and s:IsShown() then
                sectionKeys[s.groupKey] = true
            end
        end
    end
    if next(sectionKeys) == nil then
        sectionKeys.DEFAULT = true
    end

    return categoryKeys, sectionKeys
end

-- ============================================================================
-- FOCUS HELP
-- ============================================================================

local function ShowFocusShortHelp()
    HSPrint(L["FOCUS_SLASH_HELP_COMMANDS"])
    HSPrint(L["FOCUS_SLASH_HELP_SHOW"])
    HSPrint(L["FOCUS_SLASH_HELP_TOGGLE"])
    HSPrint(L["FOCUS_SLASH_HELP_COLLAPSE"])
    HSPrint(L["FOCUS_SLASH_HELP_NEARBY"])
    HSPrint(L["FOCUS_SLASH_HELP_RESET"])
    HSPrint(L["FOCUS_SLASH_HELP_RESETPOS"])
    HSPrint(L["FOCUS_SLASH_HELP_TEST"])
    HSPrint(L["FOCUS_SLASH_HELP_TESTITEM"])
    HSPrint(L["FOCUS_SLASH_HELP_TESTSOUND"])
    HSPrint(L["FOCUS_SLASH_HELP_COLORCHECK"])
    HSPrint("")
    HSPrint(L["FOCUS_SLASH_TIP_COLLAPSE"])
    HSPrint(L["FOCUS_SLASH_TIP_SCROLL"])
    HSPrint(L["FOCUS_SLASH_TIP_DRAG"])
    HSPrint(L["FOCUS_SLASH_TIP_CLICK"])
    HSPrint(L["FOCUS_SLASH_TIP_BLIZZARD_PLUS"])
end

local function ShowFocusDebugHelp()
    HSPrint(L["FOCUS_SLASH_DBG_HELP_HEADER"])
    HSPrint(L["FOCUS_SLASH_DBG_HELP_DEBUGLIVE"])
    HSPrint(L["FOCUS_SLASH_DBG_HELP_SCENDEBUG"])
    HSPrint(L["FOCUS_SLASH_DBG_HELP_DEVMODE"])
    HSPrint(L["FOCUS_SLASH_DBG_HELP_OTHERS"])
    HSPrint(L["FOCUS_SLASH_DBG_HELP_OTHERS2"])
    HSPrint(L["FOCUS_SLASH_DBG_HELP_OTHERS3"])
end

-- ============================================================================
-- FOCUS SLASH HANDLER (user + test/demo)
-- ============================================================================

local function HandleFocusSlash(msg)
    local cmd = strtrim(msg or ""):lower()

    if cmd == "toggle" then
        if InCombatLockdown() then
            print("|cFFFF0000Horizon Suite:|r " .. L["FOCUS_SLASH_TOGGLE_COMBAT"])
            return
        end
        local currentlyEnabled = addon:IsModuleEnabled("focus")
        addon:SetModuleEnabled("focus", not currentlyEnabled)
        if addon:IsModuleEnabled("focus") then
            HSPrint(L["FOCUS_SLASH_ENABLED"])
        else
            HSPrint(L["FOCUS_SLASH_DISABLED"])
        end

    elseif cmd == "collapse" then
        addon.ToggleCollapse()
        if addon.focus.collapsed then
            HSPrint(L["FOCUS_SLASH_COLLAPSED"])
        else
            HSPrint(L["FOCUS_SLASH_EXPANDED"])
        end

    elseif cmd == "nearby" then
        local show = not addon.GetDB("showNearbyGroup", true)
        addon.SetDB("showNearbyGroup", show)
        if show then
            if addon.GetDB("animations", true) and addon.StartNearbyTurnOnTransition then
                addon.StartNearbyTurnOnTransition()
            else
                if _G.HorizonSuite_RequestRefresh then _G.HorizonSuite_RequestRefresh() end
                if addon.FullLayout then addon.FullLayout() end
            end
        else
            if addon.GetDB("animations", true) and addon.StartGroupCollapseVisual then
                addon.StartGroupCollapseVisual("NEARBY")
            else
                if _G.HorizonSuite_RequestRefresh then _G.HorizonSuite_RequestRefresh() end
                if addon.FullLayout then addon.FullLayout() end
            end
        end
        if show then
            HSPrint(L["FOCUS_SLASH_NEARBY_SHOWN"])
        else
            HSPrint(L["FOCUS_SLASH_NEARBY_HIDDEN"])
        end

    elseif cmd == "testsound" then
        if addon.PlayRareAddedSound then
            addon.PlayRareAddedSound()
            HSPrint(L["FOCUS_SLASH_SOUND_PLAYED_FMT"]:format(tostring(addon.GetDB("rareAddedSoundChoice", "default"))))
        elseif PlaySound then
            local ok, err = pcall(PlaySound, addon.RARE_ADDED_SOUND)
            if not ok and HSPrint then HSPrint(L["FOCUS_SLASH_SOUND_FAILED_FMT"]:format(tostring(err))) end
            HSPrint(L["FOCUS_SLASH_SOUND_PLAYED"])
        else
            HSPrint(L["FOCUS_SLASH_SOUND_UNAVAILABLE"])
        end

    elseif cmd == "test" then
        HSPrint(L["FOCUS_SLASH_TEST_STARTED"])

        local QC = addon.QUEST_COLORS or {}
        local function qc(k) return QC[k] or QC.DEFAULT or { 0.9, 0.9, 0.9 } end

        -- Seed CURRENT: one quest marked as recently progressed so it appears in Current Quest.
        if addon.focus then
            addon.focus.recentlyProgressedQuests = addon.focus.recentlyProgressedQuests or {}
            addon.focus.recentlyProgressedQuests[90001] = GetTime()
        end

        local testQuests = {
            -- CURRENT_EVENT (event quest, accepted, nearby)
            { entryKey = "test:current_event", questID = 90000, title = "[Test] Current Event: Zone Siege",
              color = qc("CURRENT_EVENT"), category = "SCENARIO", isEventQuest = true,
              questTypeAtlas = "quest-recurring-available",
              isComplete = false, isSuperTracked = true, isNearby = true, isAccepted = true,
              zoneName = "Valdrakken",
              objectives = { { text = "Defend the gate: 2/3", finished = false } }},
            -- CURRENT (recently progressed; seeded above)
            { entryKey = "test:current", questID = 90001, title = "[Test] Current Quest: The Fate of the Horde",
              color = qc("CURRENT"), category = "CAMPAIGN",
              questTypeAtlas = "Quest-Campaign-Available",
              isComplete = false, isSuperTracked = true, isNearby = true, isAccepted = true,
              zoneName = "Valdrakken",
              itemLink = "item:12345:0:0:0:0:0:0:0", itemTexture = "Interface\\Icons\\INV_Misc_Rune_01",
              objectives = { { text = "Speak with Thrall", finished = true }, { text = "Harbingers: 2/5", finished = false } }},
            -- DELVES
            { entryKey = "test:delves", questID = 90002, title = "[Test] Delves: Cinderbrew Mine",
              color = qc("DELVES"), category = "DELVES",
              questTypeAtlas = addon.DELVE_TIER_ATLAS or "delves-scenario-flag",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Isle of Dorn",
              objectives = { { text = "Reach the vault", finished = false } }},
            -- SCENARIO
            { entryKey = "test:scenario", questID = 90003, title = "[Test] Scenario: Twilight's Call",
              color = qc("SCENARIO"), category = "SCENARIO",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "The Waking Shores",
              objectives = { { text = "Step 1/3", finished = false } }},
            -- ACHIEVEMENTS
            { entryKey = "test:ach", achievementID = 90001, questID = nil, title = "[Test] Achievement: Loremaster",
              color = qc("ACHIEVEMENT"), category = "ACHIEVEMENT", isAchievement = true,
              achievementIcon = "Interface\\Icons\\Achievement_General",
              isComplete = false, objectives = { { text = "Complete 50 quests: 37/50", finished = false, numFulfilled = 37, numRequired = 50 } }},
            -- ENDEAVORS
            { entryKey = "test:endeavor", endeavorID = 90001, questID = nil, title = "[Test] Endeavor: Gather 10 Herbs",
              color = qc("ENDEAVOR"), category = "ENDEAVOR", isEndeavor = true,
              isComplete = false, objectives = { { text = "Herbs: 6/10", finished = false } }},
            -- DECOR
            { entryKey = "test:decor", decorID = 90001, questID = nil, title = "[Test] Decor: Ancient Statue",
              color = qc("DECOR"), category = "DECOR", isDecor = true,
              decorIcon = "Interface\\Icons\\INV_Misc_Statue_01",
              isComplete = false, objectives = { { text = "Collect from raid", finished = false } }},
            -- APPEARANCES
            { entryKey = "test:appearance", appearanceID = 90001, questID = nil, title = "[Test] Appearance: Mantle of the Lost",
              color = qc("APPEARANCE"), category = "APPEARANCE", isAppearance = true,
              appearanceIcon = "Interface\\Icons\\INV_Shoulder_04",
              isComplete = false, objectives = { { text = "Drop: Boss Name (Heroic)", finished = false } }},
            -- RECIPES
            { entryKey = "test:recipe", recipeID = 90001, questID = nil, title = "[Test] Recipe: Flasks of the Currents",
              color = qc("RECIPE"), category = "RECIPE", isRecipe = true,
              recipeIcon = "Interface\\Icons\\INV_Potion_01",
              isComplete = false, objectives = { { text = "Craft 5", finished = false } }},
            -- ADVENTURE
            { entryKey = "test:adventure", questID = 90004, title = "[Test] Adventure Guide: Chromie Time",
              color = qc("ADVENTURE"), category = "ADVENTURE", isAdventureGuide = true,
              questTypeAtlas = "QuestNormal",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Stormwind",
              objectives = { { text = "Select expansion", finished = false } }},
            -- DUNGEON
            { entryKey = "test:dungeon", questID = 90005, title = "[Test] Dungeon: Brackenhide Hollow",
              color = qc("DUNGEON"), category = "DUNGEON", isDungeonQuest = true,
              questTypeAtlas = "questlog-questtypeicon-dungeon",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Ohn'ahran Plains",
              objectives = { { text = "Defeat Treemouth", finished = false } }},
            -- RAID
            { entryKey = "test:raid", questID = 90006, title = "[Test] Raid: Amirdrassil",
              color = qc("RAID"), category = "RAID", isRaidQuest = true,
              questTypeAtlas = "questlog-questtypeicon-raid",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Emerald Dream",
              objectives = { { text = "Defeat Fyrakk", finished = false } }},
            -- NEARBY (accepted, in zone)
            { entryKey = "test:nearby", questID = 90007, title = "[Test] Nearby: Aiding the Accord",
              color = qc("NEARBY"), category = "DEFAULT",
              isComplete = false, isNearby = true, isAccepted = true, zoneName = "Valdrakken",
              objectives = { { text = "Dragon Glyphs: 3/5", finished = false } }},
            -- COMPLETE
            { entryKey = "test:complete", questID = 90008, title = "[Test] Ready: Boar Pelts",
              color = qc("COMPLETE"), category = "COMPLETE", baseCategory = "DEFAULT",
              questTypeAtlas = "QuestTurnin",
              isComplete = true, isAutoComplete = true, isNearby = false, isAccepted = true,
              zoneName = "Elwynn Forest",
              objectives = { { text = "Boar Pelts: 10/10", finished = true } }},
            -- WORLD
            { entryKey = "test:world", questID = 90009, title = "[Test] World Quest: Doomwalker",
              color = qc("WORLD"), category = "WORLD",
              questTypeAtlas = "quest-recurring-available",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Thaldraszus",
              objectives = { { text = "Slay Doomwalker", finished = false } }},
            -- WEEKLY
            { entryKey = "test:weekly", questID = 90010, title = "[Test] Weekly: Dragonflight Dungeons",
              color = qc("WEEKLY"), category = "WEEKLY",
              questTypeAtlas = "quest-recurring-available",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Valdrakken",
              objectives = { { text = "Complete 4 dungeons: 2/4", finished = false } }},
            -- DAILY
            { entryKey = "test:daily", questID = 90011, title = "[Test] Daily: Fishing Contest",
              color = qc("DAILY"), category = "DAILY",
              questTypeAtlas = "quest-recurring-available",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Stranglethorn",
              objectives = { { text = "Catch 10 fish", finished = false } }},
            -- PREY weekly (accepted quest; appears first)
            { entryKey = "test:prey", questID = 90012, title = "[Test] Prey: Hunt the Shadowstalker",
              color = (QC.PREY or QC.WEEKLY), category = "PREY", isPreyWorldQuest = false,
              questTypeAtlas = "quest-recurring-available",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Quel'Thalas",
              objectives = { { text = "Track and defeat target", finished = false } }},
            -- PREY activity (world quest; appears second, shows "Activity" as zone)
            { entryKey = "test:prey_wq", questID = 90018, title = "[Test] Prey: Ambush at the Crossroads",
              color = (QC.PREY or QC.WEEKLY), category = "PREY", isPreyWorldQuest = true,
              questTypeAtlas = "quest-recurring-available",
              isComplete = false, isNearby = true, isAccepted = true, zoneName = "Activity",
              objectives = { { text = "Complete hunt objectives", finished = false } }},
            -- RARES
            { entryKey = "test:rare", questID = nil, title = "[Test] Rare: Gorged Buzzard",
              color = qc("RARE"), category = "RARE", isRare = true,
              isComplete = false, isNearby = true, zoneName = "Ohn'ahran Plains",
              objectives = {} },
            -- AVAILABLE (Events in Zone: in zone, not accepted)
            { entryKey = "test:available", questID = 90013, title = "[Test] Available: The Lost Artifact",
              color = qc("AVAILABLE"), category = "DEFAULT", isEventQuest = true,
              isComplete = false, isNearby = true, isAccepted = false, zoneName = "Valdrakken",
              objectives = { { text = "Find the artifact", finished = false } }},
            -- CAMPAIGN
            { entryKey = "test:campaign", questID = 90014, title = "[Test] Campaign: Threads of Fate",
              color = qc("CAMPAIGN"), category = "CAMPAIGN",
              questTypeAtlas = "Quest-Campaign-Available",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "The Waking Shores",
              objectives = { { text = "Explore the Loom: 1/3", finished = false } }},
            -- IMPORTANT
            { entryKey = "test:important", questID = 90015, title = "[Test] Important: The Legendary Cloak",
              color = qc("IMPORTANT"), category = "IMPORTANT",
              questTypeAtlas = "importantavailablequesticon",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Ohn'ahran Plains",
              objectives = { { text = "Collect 50 Echoes: 37/50", finished = false } }},
            -- LEGENDARY
            { entryKey = "test:legendary", questID = 90016, title = "[Test] Legendary: Ashjra'kamas",
              color = qc("LEGENDARY"), category = "LEGENDARY",
              questTypeAtlas = "UI-QuestPoiLegendary-QuestBang",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Vale of Eternal Blossoms",
              objectives = { { text = "Gather 50 coalescing visions", finished = false } }},
            -- DEFAULT
            { entryKey = "test:default", questID = 90017, title = "[Test] Default: Supply Run",
              color = qc("DEFAULT"), category = "DEFAULT",
              isComplete = false, isNearby = false, isAccepted = true, zoneName = "Stormwind City",
              objectives = { { text = "Deliver supplies: 0/1", finished = false } }},
        }

        -- Inject test data into the quest pipeline and use the normal layout engine.
        addon.testQuests = testQuests
        if addon.focus.collapsed then
            addon.focus.collapsed = false
            addon.chevron:SetText("-")
            addon.scrollFrame:Show()
            addon.SetDB("collapsed", false)
        end
        addon.FullLayout()

    elseif cmd == "testitem" then
        HSPrint(L["FOCUS_SLASH_TESTITEM_INJECTED"])
        addon.testQuestItem = {
            entryKey       = 89999,
            questID        = 89999,
            title          = "Debug: Quest Item",
            objectives     = { { text = "Use the item button to test", finished = false } },
            color          = addon.QUEST_COLORS and addon.QUEST_COLORS.DEFAULT or "|cFFFFFFFF",
            category       = "DEFAULT",
            isComplete     = false,
            isSuperTracked = false,
            isNearby       = true,
            isAccepted     = true,
            zoneName       = "Debug",
            itemLink       = "item:12345:0:0:0:0:0:0:0",
            itemTexture    = "Interface\\Icons\\INV_Misc_Rune_01",
            questTypeAtlas = nil,
            isDungeonQuest = false,
            isTracked      = true,
            level          = nil,
        }
        if addon.focus.collapsed then
            addon.focus.collapsed = false
            addon.chevron:SetText("-")
            addon.scrollFrame:Show()
            addon.SetDB("collapsed", false)
        end
        addon.FullLayout()

    elseif cmd == "clear" then
        -- Clear any injected test data and return to live quest data.
        addon.testQuests = nil
        addon.testQuestItem = nil
        if addon.focus and addon.focus.recentlyProgressedQuests then
            addon.focus.recentlyProgressedQuests[90001] = nil
        end
        addon.ScheduleRefresh()
        HSPrint(L["FOCUS_SLASH_RESET_DONE"])

    elseif cmd == "reset" then
        addon.HS:ClearAllPoints()
        addon.HS:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", addon.PANEL_X, addon.PANEL_Y)
        addon.SetDB("point", nil)
        addon.SetDB("relPoint", nil)
        addon.SetDB("x", nil)
        addon.SetDB("y", nil)
        HSPrint(L["FOCUS_SLASH_RESETPOS_DONE"])

    elseif cmd == "colorcheck" then
        if colorCheckState then
            StopColorCheck(true)
            return
        end

        local steps = {
            { name = "Red",   color = { 1.00, 0.25, 0.25 } },
            { name = "Green", color = { 0.25, 0.95, 0.35 } },
            { name = "Blue",  color = { 0.35, 0.60, 1.00 } },
            { name = "Gold",  color = { 1.00, 0.85, 0.25 } },
        }
        local categoryKeys, sectionKeys = CollectVisibleColorKeys()

        colorCheckState = {
            idx = 1,
            steps = steps,
            categoryKeys = categoryKeys,
            sectionKeys = sectionKeys,
            restore = {
                colorMatrix = DeepCopy(addon.GetDB("colorMatrix", nil)),
                highlightColor = DeepCopy(addon.GetDB("highlightColor", nil)),
                completedObjectiveColor = DeepCopy(addon.GetDB("completedObjectiveColor", nil)),
                useCompletedObjectiveColor = addon.GetDB("useCompletedObjectiveColor", true),
            },
        }

        local function ApplyStep(step)
            local matrix = DeepCopy(addon.GetDB("colorMatrix", nil))
            if type(matrix) ~= "table" then matrix = {} end
            matrix.categories = matrix.categories or {}
            matrix.overrides = matrix.overrides or {}

            for key in pairs(colorCheckState.categoryKeys) do
                matrix.categories[key] = matrix.categories[key] or {}
                matrix.categories[key].title = { step.color[1], step.color[2], step.color[3] }
                matrix.categories[key].objective = { step.color[1], step.color[2], step.color[3] }
                matrix.categories[key].zone = { step.color[1], step.color[2], step.color[3] }
            end
            for key in pairs(colorCheckState.sectionKeys) do
                matrix.categories[key] = matrix.categories[key] or {}
                matrix.categories[key].section = { step.color[1], step.color[2], step.color[3] }
            end

            addon.SetDB("colorMatrix", matrix)
            addon.SetDB("highlightColor", { step.color[1], step.color[2], step.color[3] })
            addon.SetDB("completedObjectiveColor", { step.color[1], step.color[2], step.color[3] })
            addon.SetDB("useCompletedObjectiveColor", true)

            if addon.ApplyFocusColors then
                addon.ApplyFocusColors()
            elseif addon.FullLayout then
                addon.FullLayout()
            end
        end

        local function Advance()
            if not colorCheckState then return end
            local step = colorCheckState.steps[colorCheckState.idx]
            if not step then
                StopColorCheck(false)
                HSPrint(L["FOCUS_SLASH_COLORCHECK_DONE"])
                return
            end
            ApplyStep(step)
            HSPrint(L["FOCUS_SLASH_COLORCHECK_STEP_FMT"]:format(colorCheckState.idx, #colorCheckState.steps, step.name))
            colorCheckState.idx = colorCheckState.idx + 1
        end

        Advance()
        if C_Timer and C_Timer.NewTicker then
            colorCheckState.ticker = C_Timer.NewTicker(0.9, Advance, #steps)
        else
            StopColorCheck(false)
            HSPrint(L["FOCUS_SLASH_COLORCHECK_NO_TIMER"])
        end

    else
        ShowFocusShortHelp()
    end
end

-- ============================================================================
-- FOCUS DEBUG HANDLER
-- ============================================================================

local function HandleFocusDebugSlash(msg)
    local cmd = strtrim(msg or ""):lower()

    if cmd == "" or cmd == "help" then
        ShowFocusDebugHelp()
        return
    end

    if cmd == "wqdebug" then
        if addon.DumpWorldQuestDiscovery then
            addon.DumpWorldQuestDiscovery()
        else
            HSPrint(L["FOCUS_SLASH_DBG_WQ_UNAVAILABLE"])
        end

    elseif cmd == "scendebug" then
        local v = not (addon.GetDB and addon.GetDB("scenarioDebug", false))
        if addon.SetDB then addon.SetDB("scenarioDebug", v) end
        HSPrint(L["FOCUS_SLASH_DBG_SCEN_LOG_FMT"]:format(v and L["FOCUS_SLASH_STATE_ON"] or L["FOCUS_SLASH_STATE_OFF"]))
        if addon.ScheduleRefresh then addon.ScheduleRefresh() end
        -- One-shot timer dump for immediate feedback (run in scenario to diagnose missing timers)
        if addon.DumpScenarioTimerInfo then addon.DumpScenarioTimerInfo() end

    elseif cmd == "devmode" then
        local v = not (addon.GetDB and addon.GetDB("focusDevMode", false))
        if addon.SetDB then
            addon.SetDB("focusDevMode", v)
        end
        HSPrint(L["FOCUS_SLASH_DBG_DEVMODE_FMT"]:format(v and L["FOCUS_SLASH_STATE_ON"] or L["FOCUS_SLASH_STATE_OFF"]))
        if addon.focus and addon.focus.enabled then
            if v then
                if addon.RestoreTracker then addon.RestoreTracker() end
            else
                if addon.TrySuppressTracker then addon.TrySuppressTracker() end
            end
        end

    elseif cmd == "mplusdebug" then
        addon.mplusDebugPreview = not addon.mplusDebugPreview
        if addon.FullLayout then addon.FullLayout() end
        HSPrint(L["FOCUS_SLASH_DBG_MPLUS_PREVIEW_FMT"]:format(addon.mplusDebugPreview and L["FOCUS_SLASH_STATE_ON"] or L["FOCUS_SLASH_STATE_OFF"]))

    elseif cmd == "mplusaffixdebug" then
        local mapId = C_ChallengeMode and C_ChallengeMode.GetActiveChallengeMapID and C_ChallengeMode.GetActiveChallengeMapID()
        HSPrint("--- M+ Affix Debug ---")
        HSPrint("GetActiveChallengeMapID: " .. (mapId and tostring(mapId) or "nil"))
        if not mapId then
            HSPrint("  (Not in an active challenge; run in M+ dungeon with key inserted)")
            return
        end
        if C_ChallengeMode and C_ChallengeMode.GetMapUIInfo then
            local name, _, timeLimit = C_ChallengeMode.GetMapUIInfo(mapId)
            HSPrint("MapUIInfo: name=" .. tostring(name) .. " timeLimit=" .. tostring(timeLimit))
        end
        if C_ChallengeMode and C_ChallengeMode.GetActiveKeystoneInfo then
            local ok, level, affixes, wasEnergized = pcall(C_ChallengeMode.GetActiveKeystoneInfo)
            if ok then
                HSPrint("GetActiveKeystoneInfo: level=" .. tostring(level) .. " wasEnergized=" .. tostring(wasEnergized))
                if affixes and type(affixes) == "table" then
                    local ids = {}
                    for i = 1, #affixes do ids[#ids + 1] = tostring(affixes[i]) end
                    HSPrint("  affixes (IDs): " .. (#ids > 0 and table.concat(ids, ", ") or "empty"))
                    for i = 1, #affixes do
                        local id = affixes[i]
                        if id and type(id) == "number" and C_ChallengeMode.GetAffixInfo then
                            local aOk, name, desc, iconFileID = pcall(C_ChallengeMode.GetAffixInfo, id)
                            if aOk and name then
                                HSPrint("    ID " .. tostring(id) .. " -> " .. tostring(name))
                            else
                                HSPrint("    ID " .. tostring(id) .. " -> GetAffixInfo error or empty")
                            end
                        end
                    end
                else
                    HSPrint("  affixes: " .. (affixes == nil and "nil" or type(affixes)))
                end
            else
                HSPrint("GetActiveKeystoneInfo error: " .. tostring(level))
            end
        end
        if C_MythicPlus and C_MythicPlus.GetCurrentAffixes then
            local ok, currentAffixes = pcall(C_MythicPlus.GetCurrentAffixes)
            if ok and currentAffixes and type(currentAffixes) == "table" then
                local parts = {}
                for _, a in ipairs(currentAffixes) do
                    local id = (a and type(a) == "table" and a.id) or (type(a) == "number" and a) or nil
                    if id then parts[#parts + 1] = tostring(id) end
                end
                HSPrint("GetCurrentAffixes: " .. (#parts > 0 and table.concat(parts, ", ") or "empty"))
            else
                HSPrint("GetCurrentAffixes: " .. (ok and "empty/nil" or ("error: " .. tostring(currentAffixes))))
            end
        else
            HSPrint("GetCurrentAffixes: API not available")
        end
        if addon.GetMplusData then
            local data = addon.GetMplusData()
            if data and data.affixes then
                if #data.affixes > 0 then
                    local names = {}
                    for _, a in ipairs(data.affixes) do names[#names + 1] = a.name or "(nil)" end
                    HSPrint("GetMplusData.affixes (final): " .. table.concat(names, ", "))
                else
                    HSPrint("GetMplusData.affixes: empty")
                end
            else
                HSPrint("GetMplusData: nil or no affixes key")
            end
        else
            HSPrint("GetMplusData: not available")
        end
        HSPrint("--- End M+ Affix Debug ---")

    elseif cmd == "groupdebug" then
        HSPrint("|cFF00CCFF--- Group Quest Debug ---|r")
        local pool = addon.pool
        if pool then
            for i = 1, addon.POOL_SIZE do
                local e = pool[i]
                if e and e.questID and e.questID > 0 and e:IsShown() then
                    local qid = e.questID
                    local title = (e.titleText and e.titleText:GetText()) or tostring(qid)
                    local parts = { ("|cFFFFFF00%s|r (ID %d):"):format(title, qid) }
                    if C_QuestLog and C_QuestLog.GetQuestTagInfo then
                        local ok, ti = pcall(C_QuestLog.GetQuestTagInfo, qid)
                        if ok and ti then
                            parts[#parts+1] = ("  tagID=%s worldQuestType=%s tagName=%s"):format(
                                tostring(ti.tagID), tostring(ti.worldQuestType), tostring(ti.tagName))
                        else
                            parts[#parts+1] = "  GetQuestTagInfo: nil"
                        end
                    end
                    if C_TaskQuest and C_TaskQuest.GetQuestInfoByQuestID then
                        local ok, info = pcall(C_TaskQuest.GetQuestInfoByQuestID, qid)
                        if ok and info then
                            if type(info) == "table" then
                                local keys = {}
                                for k, v in pairs(info) do keys[#keys+1] = k .. "=" .. tostring(v) end
                                parts[#parts+1] = "  TaskQuestInfo: " .. table.concat(keys, ", ")
                            else
                                parts[#parts+1] = "  TaskQuestInfo: " .. tostring(info) .. " (type=" .. type(info) .. ")"
                            end
                        else
                            parts[#parts+1] = "  TaskQuestInfo: nil"
                        end
                    end
                    if C_QuestLog and C_QuestLog.GetLogIndexForQuestID then
                        local logIdx = C_QuestLog.GetLogIndexForQuestID(qid)
                        if logIdx and C_QuestLog.GetInfo then
                            local ok, info = pcall(C_QuestLog.GetInfo, logIdx)
                            if ok and info then
                                parts[#parts+1] = ("  suggestedGroup=%s"):format(tostring(info.suggestedGroup))
                            end
                        else
                            parts[#parts+1] = "  Not in quest log"
                        end
                    end
                    local isGroup = addon.IsGroupQuest and addon.IsGroupQuest(qid) or false
                    parts[#parts+1] = ("  |cFF00FF00IsGroupQuest = %s|r"):format(tostring(isGroup))
                    for _, line in ipairs(parts) do
                        HSPrint(line)
                    end
                end
            end
        end
        HSPrint("|cFF00CCFF--- End Group Quest Debug ---|r")

    elseif cmd == "nearbydebug" then
        if addon.GetNearbyDebugInfo then
            HSPrint("|cFF00CCFF--- Nearby / Current Zone debug ---|r")
            for _, line in ipairs(addon.GetNearbyDebugInfo()) do
                HSPrint(line)
            end
        else
            HSPrint(L["FOCUS_SLASH_DBG_NEARBY_UNAVAIL"])
        end

    elseif cmd == "headercountdebug" then
        if addon.DebugHeaderCount then
            addon.DebugHeaderCount()
        else
            HSPrint(L["FOCUS_SLASH_DBG_HEADER_UNAVAIL"])
        end

    elseif cmd == "achievementdebug" then
        HSPrint("|cFF00CCFF--- Achievement Tracking Debug ---|r")
        local TRACKING_TYPE_ACHIEVEMENT = (Enum and Enum.ContentTrackingType and Enum.ContentTrackingType.Achievement) or 2
        local idList = {}
        if C_ContentTracking and C_ContentTracking.GetTrackedIDs then
            local ids = C_ContentTracking.GetTrackedIDs(TRACKING_TYPE_ACHIEVEMENT)
            if ids and type(ids) == "table" then
                for _, id in ipairs(ids) do
                    if type(id) == "number" and id > 0 then idList[#idList + 1] = id end
                end
            end
        elseif GetTrackedAchievements then
            for i = 1, 10 do
                local id = select(i, GetTrackedAchievements())
                if type(id) == "number" and id > 0 then idList[#idList + 1] = id end
            end
        end
        HSPrint("Tracked achievement IDs: " .. (#idList > 0 and table.concat(idList, ", ") or "none"))
        for _, achID in ipairs(idList) do
            HSPrint("|cFFFFCC00Achievement " .. tostring(achID) .. ":|r")
            if GetAchievementInfo then
                local aOk, id, name, points, completed, month, day, year, description, flags, icon = pcall(GetAchievementInfo, achID)
                if aOk then
                    HSPrint("  name=" .. tostring(name) .. "  completed=" .. tostring(completed) .. "  points=" .. tostring(points))
                    HSPrint("  description=" .. tostring(description))
                else
                    HSPrint("  GetAchievementInfo FAILED: " .. tostring(id))
                end
            end
            local numCriteria = 0
            if GetAchievementNumCriteria then
                local nOk, n = pcall(GetAchievementNumCriteria, achID)
                if nOk and type(n) == "number" then numCriteria = n end
                HSPrint("  numCriteria=" .. tostring(nOk and n or "error"))
            end
            if GetAchievementCriteriaInfo and numCriteria > 0 then
                for ci = 1, math.min(numCriteria, 15) do
                    local cOk, criteriaString, criteriaType, completedCrit, quantity, reqQuantity, charName, cflags, assetID, quantityString, criteriaID =
                        pcall(GetAchievementCriteriaInfo, achID, ci)
                    if cOk then
                        HSPrint(("  [%d] str=%q type=%s done=%s qty=%s/%s qtyStr=%q criteriaID=%s"):format(
                            ci, tostring(criteriaString or ""), tostring(criteriaType), tostring(completedCrit),
                            tostring(quantity), tostring(reqQuantity), tostring(quantityString or ""), tostring(criteriaID or "?")))
                    else
                        HSPrint(("  [%d] GetAchievementCriteriaInfo FAILED: %s"):format(ci, tostring(criteriaString)))
                    end
                end
                if numCriteria > 15 then
                    HSPrint("  ... (" .. tostring(numCriteria - 15) .. " more criteria)")
                end
            elseif numCriteria == 0 then
                HSPrint("  (no criteria)")
            end
        end
        HSPrint("|cFF00CCFF--- End Achievement Debug ---|r")

    elseif cmd == "endeavordebug" then
        HSPrint("|cFF00CCFF--- Endeavor API debug ---|r")
        HSPrint("C_ContentTracking: " .. (C_ContentTracking and "yes" or "no"))
        if Enum and Enum.ContentTrackingType then
            local t = {}
            for k, v in pairs(Enum.ContentTrackingType) do
                if type(v) == "number" then t[#t + 1] = k .. "=" .. tostring(v) end
            end
            HSPrint("ContentTrackingType: " .. table.concat(t, ", "))
        end
        for _, typ in ipairs({ 0, 1, 2, 3, 4, 5 }) do
            if C_ContentTracking and C_ContentTracking.GetTrackedIDs then
                local ok, ids = pcall(C_ContentTracking.GetTrackedIDs, typ)
                if ok and ids and type(ids) == "table" and #ids > 0 then
                    HSPrint("  GetTrackedIDs(" .. typ .. "): " .. #ids .. " ids: " .. table.concat(ids, ", "))
                end
            end
        end
        HSPrint("C_Endeavors: " .. (C_Endeavors and "yes" or "no"))
        if C_Endeavors then
            for _, fn in ipairs({ "GetTrackedIDs", "GetEndeavorInfo", "GetInfo", "GetActiveEndeavorID" }) do
                if C_Endeavors[fn] then HSPrint("  C_Endeavors." .. fn .. ": yes") end
            end
        end
        HSPrint("C_PlayerHousing: " .. (C_PlayerHousing and "yes" or "no"))
        if C_PlayerHousing then
            for _, fn in ipairs({ "GetActiveEndeavorID", "GetActiveEndeavorInfo", "GetEndeavorInfo" }) do
                if C_PlayerHousing[fn] then HSPrint("  C_PlayerHousing." .. fn .. ": yes") end
            end
        end
        HSPrint("C_NeighborhoodInitiative: " .. (C_NeighborhoodInitiative and "yes" or "no"))
        if C_NeighborhoodInitiative then
            for _, fn in ipairs({ "GetTrackedInitiativeTasks", "GetInitiativeTaskInfo", "RemoveTrackedInitiativeTask", "GetInitiativeTaskChatLink" }) do
                if C_NeighborhoodInitiative[fn] then HSPrint("  C_NeighborhoodInitiative." .. fn .. ": yes") end
            end
        end
        HSPrint("HousingFramesUtil: " .. (HousingFramesUtil and "yes" or "no"))
        if HousingFramesUtil and HousingFramesUtil.OpenFrameToTaskID then
            HSPrint("  HousingFramesUtil.OpenFrameToTaskID: yes")
        end
        HSPrint("ReadTrackedEndeavors count: " .. (addon.ReadTrackedEndeavors and #addon.ReadTrackedEndeavors() or 0))
        HSPrint("ReadTrackedDecor count: " .. (addon.ReadTrackedDecor and #addon.ReadTrackedDecor() or 0))
        HSPrint("ReadTrackedAppearances count: " .. (addon.ReadTrackedAppearances and #addon.ReadTrackedAppearances() or 0))
        if C_NeighborhoodInitiative and C_NeighborhoodInitiative.GetTrackedInitiativeTasks and C_NeighborhoodInitiative.GetInitiativeTaskInfo then
            local ok, result = pcall(C_NeighborhoodInitiative.GetTrackedInitiativeTasks)
            local ids = {}
            if ok and result then
                if result.trackedIDs and type(result.trackedIDs) == "table" then
                    ids = result.trackedIDs
                elseif type(result) == "table" and #result > 0 then
                    ids = result
                end
            end
            if #ids == 0 and addon.GetTrackedEndeavorIDs then
                ids = addon.GetTrackedEndeavorIDs() or {}
            end
            HSPrint("|cFF00CCFF--- GetInitiativeTaskInfo dump (" .. #ids .. " tracked) ---|r")
            for _, taskID in ipairs(ids) do
                local getOk, info = pcall(C_NeighborhoodInitiative.GetInitiativeTaskInfo, taskID)
                if getOk and info and type(info) == "table" then
                    HSPrint("  Endeavor " .. tostring(taskID) .. " (" .. tostring(info.taskName or "?") .. "):")
                    local keys = {}
                    for k in pairs(info) do keys[#keys + 1] = k end
                    table.sort(keys)
                    for _, k in ipairs(keys) do
                        local v = info[k]
                        if type(v) == "table" then
                            HSPrint("    " .. tostring(k) .. " = (table, #=" .. tostring(#v) .. ")")
                        else
                            HSPrint("    " .. tostring(k) .. " = " .. tostring(v))
                        end
                    end
                else
                    HSPrint("  Endeavor " .. tostring(taskID) .. ": GetInitiativeTaskInfo returned " .. (getOk and "nil" or ("error: " .. tostring(info))))
                end
            end
        end

    elseif cmd == "unaccepted" then
        if addon.ShowUnacceptedPopup then
            addon.ShowUnacceptedPopup()
            HSPrint(L["FOCUS_SLASH_DBG_UNACCEPTED_OPENED"])
        else
            HSPrint(L["FOCUS_SLASH_DBG_UNACCEPTED_UNAVAIL"])
        end

    elseif cmd == "profiledebug" then
        HSPrint("|cFF00CCFF--- Profile Routing Debug ---|r")
        local charName = _G.UnitName and _G.UnitName("player") or "?"
        local realm = _G.GetNormalizedRealmName and _G.GetNormalizedRealmName() or "?"
        local charFullKey = tostring(charName) .. "-" .. tostring(realm)
        HSPrint("Character: " .. charFullKey)
        local numSpecs = _G.GetNumSpecializations and _G.GetNumSpecializations() or "?"
        local curSpec = _G.GetSpecialization and _G.GetSpecialization() or "?"
        HSPrint("Specs: " .. tostring(numSpecs) .. " | Current spec index: " .. tostring(curSpec))
        if _G[addon.DATABASE] then
            local db = _G[addon.DATABASE]
            HSPrint("useGlobalProfile: " .. tostring(db.useGlobalProfile))
            HSPrint("globalProfileKey: " .. tostring(db.globalProfileKey))
            HSPrint("usePerSpecProfiles: " .. tostring(db.usePerSpecProfiles))
            local charSpecs = db.charPerSpecKeys and db.charPerSpecKeys[charFullKey:gsub("%s+", "")]
            if charSpecs then
                for i = 1, 4 do
                    if charSpecs[i] then
                        local specName = _G.GetSpecializationInfo and select(2, _G.GetSpecializationInfo(i)) or ("Spec " .. i)
                        HSPrint("  charPerSpec[" .. i .. "] (" .. tostring(specName) .. "): " .. tostring(charSpecs[i]))
                    end
                end
            else
                HSPrint("  charPerSpecKeys: (none for this character)")
            end
            HSPrint("charProfileKeys:")
            if db.charProfileKeys then
                for ck, pk in pairs(db.charProfileKeys) do
                    HSPrint("  " .. tostring(ck) .. " -> " .. tostring(pk))
                end
            end
            HSPrint("Existing profiles:")
            if db.profiles then
                for k in pairs(db.profiles) do
                    HSPrint("  " .. tostring(k))
                end
            end
        end
        local effectiveKey = addon.GetEffectiveProfileKey and addon.GetEffectiveProfileKey() or "?"
        local activeKey = addon.GetActiveProfileKey and addon.GetActiveProfileKey() or "?"
        HSPrint("GetEffectiveProfileKey(): " .. tostring(effectiveKey))
        HSPrint("GetActiveProfileKey(): " .. tostring(activeKey))
        if addon.GetActiveProfile then
            local _, profileKey = addon.GetActiveProfile()
            HSPrint("GetActiveProfile() key: " .. tostring(profileKey))
        end

    elseif cmd == "clickdebug" then
        addon.focus.clickDispatchDebug = not addon.focus.clickDispatchDebug
        HSPrint(addon.focus.clickDispatchDebug and L["FOCUS_SLASH_DBG_CLICK_DISPATCH_ON"] or L["FOCUS_SLASH_DBG_CLICK_DISPATCH_OFF"])

    elseif cmd == "clicktodebug" then
        HSPrint("|cFF00CCFF--- Click-to-complete debug ---|r")
        if not C_QuestLog then
            HSPrint("C_QuestLog: not available")
        else
            HSPrint("ShowQuestComplete: " .. (ShowQuestComplete and "yes" or "no"))
            HSPrint("requireModifierForClickToComplete: " .. tostring(addon.GetDB("requireModifierForClickToComplete", false)))
            local n = C_QuestLog.GetNumQuestWatches and C_QuestLog.GetNumQuestWatches() or 0
            HSPrint("Tracked quests: " .. tostring(n))
            local eligible = 0
            for i = 1, n do
                local qid = C_QuestLog.GetQuestIDForQuestWatchIndex and C_QuestLog.GetQuestIDForQuestWatchIndex(i)
                if qid and qid > 0 then
                    local title = C_QuestLog.GetTitleForQuestID and C_QuestLog.GetTitleForQuestID(qid) or ("Quest " .. tostring(qid))
                    local logIdx = C_QuestLog.GetLogIndexForQuestID and C_QuestLog.GetLogIndexForQuestID(qid)
                    local isComplete = C_QuestLog.IsComplete and C_QuestLog.IsComplete(qid)
                    local isAuto = false
                    if logIdx and C_QuestLog.GetInfo then
                        local ok, info = pcall(C_QuestLog.GetInfo, logIdx)
                        if ok and info then isAuto = info.isAutoComplete and true or false end
                    end
                    local status = (isAuto and isComplete) and "|cFF00FF00eligible|r" or "not eligible"
                    if isAuto and isComplete then eligible = eligible + 1 end
                    HSPrint("  [" .. tostring(qid) .. "] " .. tostring(title):sub(1, 40) .. " | complete=" .. tostring(isComplete) .. " | autoComplete=" .. tostring(isAuto) .. " | " .. status)
                end
            end
            HSPrint("Eligible for click-to-complete: " .. tostring(eligible))
        end

    elseif cmd == "recipedebug" or cmd:match("^recipedebug%s") then
        local arg = cmd:match("^recipedebug%s+(%d+)")
        local rid = arg and tonumber(arg)
        if addon.DebugRecipeReagents then
            addon.DebugRecipeReagents(rid)
        else
            HSPrint("DebugRecipeReagents not available.")
        end

    elseif cmd == "delvedebug" then
        HSPrint("|cFF00CCFF--- Delve / Tier debug (run inside a Delve) ---|r")
        local function GetStepAndTrackerSetIDs()
            local stepSetID, objSetID
            if C_Scenario and C_Scenario.GetStepInfo then
                local ok, t = pcall(function() return { C_Scenario.GetStepInfo() } end)
                if ok and t and type(t) == "table" and #t >= 12 then
                    local ws = t[12]
                    if type(ws) == "number" and ws ~= 0 then stepSetID = ws end
                end
            end
            if C_UIWidgetManager and C_UIWidgetManager.GetObjectiveTrackerWidgetSetID then
                local ok, s = pcall(C_UIWidgetManager.GetObjectiveTrackerWidgetSetID)
                if ok and type(s) == "number" then objSetID = s end
            end
            return stepSetID, objSetID
        end
        if C_PartyInfo and C_PartyInfo.IsDelveInProgress then
            local ok, v = pcall(C_PartyInfo.IsDelveInProgress)
            HSPrint("IsDelveInProgress: " .. tostring(ok and v or (ok and "false") or ("error: " .. tostring(v))))
        else
            HSPrint("IsDelveInProgress: not available")
        end
        if C_GossipInfo and C_GossipInfo.GetActiveDelveGossip then
            local ok, g = pcall(C_GossipInfo.GetActiveDelveGossip)
            HSPrint("GetActiveDelveGossip: " .. (ok and g and type(g.orderIndex) == "number" and ("tier=" .. tostring(g.orderIndex + 1)) or "nil/error"))
        end
        if GetCVarTableValue and C_DelvesUI and C_DelvesUI.GetTieredEntrancePDEID then
            local ok, pdeID = pcall(C_DelvesUI.GetTieredEntrancePDEID)
            if ok and pdeID then
                local vOk, tier = pcall(GetCVarTableValue, "lastSelectedTieredEntranceTier", pdeID, 0)
                HSPrint("GetCVarTableValue(lastSelectedTieredEntranceTier, pdeID=" .. tostring(pdeID) .. "): " .. (vOk and tostring(tier) or ("error: " .. tostring(tier))))
            end
        end
        -- Midnight: GetCVarNumberOrDefault may throw (CvarUtil expects another signature); use GetCVar for debug.
        if GetCVar then
            local raw = GetCVar("lastSelectedDelvesTier")
            local n = raw and tonumber(raw)
            HSPrint("GetCVar(lastSelectedDelvesTier): " .. tostring(raw) .. (n and (" (number " .. tostring(n) .. ")") or ""))
        end
        if GetInstanceInfo then
            local ok, name, instType, diffID, diffName = pcall(GetInstanceInfo)
            if ok then
                HSPrint("GetInstanceInfo: name=" .. tostring(name) .. " type=" .. tostring(instType) .. " diffID=" .. tostring(diffID) .. " diffName=" .. tostring(diffName))
            end
        end
        if addon.GetDelveScenarioHeaderMetadata then
            local meta = addon.GetDelveScenarioHeaderMetadata()
            if meta then
                HSPrint("GetDelveScenarioHeaderMetadata: nemesisHasData=" .. tostring(meta.nemesisHasData)
                    .. " remaining=" .. tostring(meta.nemesisGroupsRemaining)
                    .. " total=" .. tostring(meta.nemesisGroupsTotal)
                    .. " complete=" .. tostring(meta.nemesisIsComplete))
            end
        end
        if addon.GetDelveScenarioWidgetDebugSnapshot then
            local snap = addon.GetDelveScenarioWidgetDebugSnapshot()
            HSPrint("Delve widgets: shouldRead=" .. tostring(snap.shouldRead)
                .. " isDelveActive=" .. tostring(snap.isDelveActive)
                .. " setIDs=[" .. tostring(snap.setIDs) .. "]"
                .. " headerWidgets=" .. tostring(snap.headerCount))
        end
        if addon.GetDelvesAffixes then
            local affixes = addon.GetDelvesAffixes()
            if affixes and #affixes > 0 then
                local names = {}
                for _, a in ipairs(affixes) do names[#names + 1] = a.name or "(nil)" end
                HSPrint("GetDelvesAffixes: " .. table.concat(names, ", "))
            else
                HSPrint("GetDelvesAffixes: nil or empty")
                if C_UIWidgetManager and C_UIWidgetManager.GetAllWidgetsBySetID and C_UIWidgetManager.GetScenarioHeaderDelvesWidgetVisualizationInfo then
                    local stepSetID, objSetID = GetStepAndTrackerSetIDs()
                    HSPrint(("  widgetSetID: GetStepInfo=%s GetObjectiveTracker=%s"):format(
                        stepSetID and tostring(stepSetID) or "nil",
                        objSetID and tostring(objSetID) or "nil"))
                    local setID = stepSetID or objSetID
                    if setID then
                        local wOk, widgets = pcall(C_UIWidgetManager.GetAllWidgetsBySetID, setID)
                        if wOk and widgets and type(widgets) == "table" then
                            local n = 0
                            for _ in pairs(widgets) do n = n + 1 end
                            local WIDGET_DELVES = (Enum and Enum.UIWidgetVisualizationType and Enum.UIWidgetVisualizationType.ScenarioHeaderDelves) or 29
                            HSPrint("  widgets: " .. tostring(n))
                            for k, v in pairs(widgets) do
                                local testID = (v and type(v) == "table" and v.widgetID) or (type(v) == "number" and v) or nil
                                if testID and type(testID) == "number" then
                                    local wType = (v and type(v) == "table") and v.widgetType
                                    local isDelves = (wType == WIDGET_DELVES) and " [ScenarioHeaderDelves]" or ""
                                    local dOk, wi = pcall(C_UIWidgetManager.GetScenarioHeaderDelvesWidgetVisualizationInfo, testID)
                                    local spells = (dOk and wi and wi.spells) and #wi.spells or 0
                                    HSPrint(("  widget k=%s id=%s type=%s%s -> %d spells"):format(
                                        tostring(k), tostring(testID), tostring(wType or "?"), isDelves, spells))
                                end
                            end
                        end
                    end
                end
            end
        end

        -- Scenario objective progress debug (inspect both likely widget sets and multiple widget types).
        HSPrint("|cFF00CCFF--- Scenario objective progress (widgets + criteria) ---|r")
        local function ShortText(value, maxLen)
            local s = tostring(value or "")
            local limit = maxLen or 80
            s = s:gsub("\n", " "):gsub("\r", " ")
            if #s > limit then
                return s:sub(1, limit - 3) .. "..."
            end
            return s
        end
        local function GetWidgetTypeName(widgetType)
            if Enum and Enum.UIWidgetVisualizationType then
                for k, v in pairs(Enum.UIWidgetVisualizationType) do
                    if v == widgetType then return k end
                end
            end
            return "type=" .. tostring(widgetType)
        end
        local function PrintPercentPair(prefix, minValue, maxValue, value)
            if type(maxValue) ~= "number" or type(value) ~= "number" then return end
            local minNum = type(minValue) == "number" and minValue or 0
            local range = maxValue - minNum
            local pctRaw = (maxValue > 0) and math.floor(100 * value / maxValue) or 0
            local pctMin = (range > 0) and math.floor(100 * (value - minNum) / range) or 0
            HSPrint(("%s raw=%s%% withMin=%s%%"):format(prefix, tostring(pctRaw), tostring(pctMin)))
        end
        local function DumpWidgetSet(label, setID)
            if not setID or setID == 0 then
                HSPrint(("  %s: no widget set"):format(label))
                return
            end
            if not (C_UIWidgetManager and C_UIWidgetManager.GetAllWidgetsBySetID) then
                HSPrint(("  %s: widget manager unavailable"):format(label))
                return
            end
            local wOk, widgets = pcall(C_UIWidgetManager.GetAllWidgetsBySetID, setID)
            if not wOk or type(widgets) ~= "table" then
                HSPrint(("  %s: failed to read widget set %s"):format(label, tostring(setID)))
                return
            end
            local count = 0
            for _ in pairs(widgets) do count = count + 1 end
            HSPrint(("  %s widgetSetID=%s widgets=%d"):format(label, tostring(setID), count))
            for _, wInfo in pairs(widgets) do
                local widgetID = type(wInfo) == "table" and wInfo.widgetID or (type(wInfo) == "number" and wInfo)
                local widgetType = type(wInfo) == "table" and wInfo.widgetType or nil
                if widgetID then
                    local widgetTypeName = GetWidgetTypeName(widgetType)
                    HSPrint(("    widget id=%s type=%s"):format(tostring(widgetID), widgetTypeName))

                    if C_UIWidgetManager.GetDiscreteProgressStepsVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetDiscreteProgressStepsVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and type(info.progressMax) == "number" then
                            HSPrint(("      DiscreteProgress: min=%s max=%s val=%s steps=%s tooltip=%q"):format(
                                tostring(info.progressMin), tostring(info.progressMax), tostring(info.progressVal),
                                tostring(info.numSteps), ShortText(info.tooltip, 70)))
                            PrintPercentPair("      Discrete percent:", info.progressMin, info.progressMax, info.progressVal)
                        end
                    end

                    if C_UIWidgetManager.GetFillUpFramesWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetFillUpFramesWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and type(info.fillMax) == "number" then
                            HSPrint(("      FillUpFrames: min=%s max=%s val=%s totalFrames=%s fullFrames=%s tooltip=%q"):format(
                                tostring(info.fillMin), tostring(info.fillMax), tostring(info.fillValue),
                                tostring(info.numTotalFrames), tostring(info.numFullFrames), ShortText(info.tooltip, 70)))
                            PrintPercentPair("      FillUp percent:", info.fillMin, info.fillMax, info.fillValue)
                        end
                    end

                    if C_UIWidgetManager.GetStatusBarWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetStatusBarWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and type(info.barMax) == "number" then
                            HSPrint(("      StatusBar: min=%s max=%s val=%s text=%q override=%q valueTextType=%s"):format(
                                tostring(info.barMin), tostring(info.barMax), tostring(info.barValue),
                                ShortText(info.text, 60), ShortText(info.overrideBarText, 40), tostring(info.barValueTextType)))
                            PrintPercentPair("      StatusBar percent:", info.barMin, info.barMax, info.barValue)
                        end
                    end

                    if C_UIWidgetManager.GetDoubleStatusBarWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetDoubleStatusBarWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and type(info.leftBarMax) == "number" then
                            HSPrint(("      DoubleStatus: text=%q left=%s/%s right=%s/%s"):format(
                                ShortText(info.text, 60), tostring(info.leftBarValue), tostring(info.leftBarMax),
                                tostring(info.rightBarValue), tostring(info.rightBarMax)))
                            PrintPercentPair("      Double left percent:", info.leftBarMin, info.leftBarMax, info.leftBarValue)
                            PrintPercentPair("      Double right percent:", info.rightBarMin, info.rightBarMax, info.rightBarValue)
                        end
                    end

                    if C_UIWidgetManager.GetUnitPowerBarWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetUnitPowerBarWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and type(info.barMax) == "number" then
                            HSPrint(("      UnitPowerBar: min=%s max=%s val=%s override=%q valueTextType=%s"):format(
                                tostring(info.barMin), tostring(info.barMax), tostring(info.barValue),
                                ShortText(info.overrideBarText, 40), tostring(info.barValueTextType)))
                            PrintPercentPair("      UnitPower percent:", info.barMin, info.barMax, info.barValue)
                        end
                    end

                    if C_UIWidgetManager.GetIconAndTextWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetIconAndTextWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and info.text and info.text ~= "" then
                            HSPrint(("      IconAndText: text=%q tooltip=%q dynamicTooltip=%q"):format(
                                ShortText(info.text, 60), ShortText(info.tooltip, 60), ShortText(info.dynamicTooltip, 60)))
                        end
                    end

                    if C_UIWidgetManager.GetDoubleIconAndTextWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetDoubleIconAndTextWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and ((info.label and info.label ~= "") or (info.leftText and info.leftText ~= "") or (info.rightText and info.rightText ~= "")) then
                            HSPrint(("      DoubleIconAndText: label=%q left=%q right=%q"):format(
                                ShortText(info.label, 40), ShortText(info.leftText, 40), ShortText(info.rightText, 40)))
                        end
                    end

                    if C_UIWidgetManager.GetTextWithStateWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetTextWithStateWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and info.text and info.text ~= "" then
                            HSPrint(("      TextWithState: text=%q tooltip=%q"):format(
                                ShortText(info.text, 70), ShortText(info.tooltip, 60)))
                        end
                    end

                    if C_UIWidgetManager.GetTextWithSubtextWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetTextWithSubtextWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and ((info.text and info.text ~= "") or (info.subText and info.subText ~= "")) then
                            HSPrint(("      TextWithSubtext: text=%q subText=%q"):format(
                                ShortText(info.text, 60), ShortText(info.subText, 60)))
                        end
                    end

                    if C_UIWidgetManager.GetTextColumnRowVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetTextColumnRowVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and type(info.entries) == "table" and #info.entries > 0 then
                            local parts = {}
                            for i = 1, math.min(#info.entries, 3) do
                                parts[#parts + 1] = ShortText(info.entries[i].text, 28)
                            end
                            HSPrint(("      TextColumnRow: %s"):format(table.concat(parts, " | ")))
                        end
                    end

                    if C_UIWidgetManager.GetTextureAndTextVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetTextureAndTextVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and info.text and info.text ~= "" then
                            HSPrint(("      TextureAndText: text=%q tooltip=%q"):format(
                                ShortText(info.text, 70), ShortText(info.tooltip, 60)))
                        end
                    end

                    if C_UIWidgetManager.GetScenarioHeaderDelvesWidgetVisualizationInfo then
                        local ok, info = pcall(C_UIWidgetManager.GetScenarioHeaderDelvesWidgetVisualizationInfo, widgetID)
                        if ok and info and type(info) == "table" and ((info.headerText and info.headerText ~= "") or (info.tierText and info.tierText ~= "")) then
                            HSPrint(("      ScenarioHeaderDelves: header=%q tier=%q tooltip=%q spells=%s currencies=%s"):format(
                                ShortText(info.headerText, 50), ShortText(info.tierText, 20), ShortText(info.tooltip, 60),
                                tostring(info.spells and #info.spells or 0),
                                tostring(info.currencies and #info.currencies or 0)))
                            if type(info.currencies) == "table" then
                                for ci, cur in ipairs(info.currencies) do
                                    if type(cur) == "table" then
                                        HSPrint(("        currency[%d]: text=%q icon=%s state=%s tooltip=%q"):format(
                                            ci,
                                            ShortText(cur.text, 20),
                                            tostring(cur.iconFileID),
                                            tostring(cur.textEnabledState),
                                            ShortText(cur.tooltip, 40)))
                                    end
                                end
                            end
                            if type(info.spells) == "table" then
                                for si, sp in ipairs(info.spells) do
                                    if type(sp) == "table" then
                                        local sname = (addon.GetSpellNameAndIcon and addon.GetSpellNameAndIcon(sp.spellID)) or ""
                                        local descCount = ""
                                        if (not sp.stackDisplay or sp.stackDisplay == 0)
                                            and type(sp.spellID) == "number" and sp.spellID > 0
                                            and C_Spell and C_Spell.GetSpellDescription then
                                            local dOk, desc = pcall(C_Spell.GetSpellDescription, sp.spellID)
                                            if dOk and type(desc) == "string" then
                                                local n = desc:match("remaining[^%d]+(%d+)")
                                                if n then descCount = " descRemaining=" .. n end
                                            end
                                        end
                                        HSPrint(("        spell[%d]: id=%s name=%q stackDisplay=%s%s"):format(
                                            si,
                                            tostring(sp.spellID),
                                            ShortText(sname, 30),
                                            tostring(sp.stackDisplay),
                                            descCount))
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
        local stepSetID, objSetID = GetStepAndTrackerSetIDs()
        DumpWidgetSet("StepInfo", stepSetID)
        if objSetID ~= stepSetID then
            DumpWidgetSet("ObjectiveTracker", objSetID)
        end
        -- Criteria dump
        if C_ScenarioInfo and (C_ScenarioInfo.GetCriteriaInfo or C_ScenarioInfo.GetCriteriaInfoByStep) then
            local numCrit = 0
            local ok, _, _, n = pcall(C_Scenario.GetStepInfo)
            if ok and n and type(n) == "number" then numCrit = n + 3 end
            if numCrit > 0 then
                for i = 1, math.min(numCrit, 20) do
                    local cOk, crit = pcall(C_ScenarioInfo.GetCriteriaInfo, i)
                    if not cOk or not crit then cOk, crit = pcall(C_ScenarioInfo.GetCriteriaInfoByStep, 1, i) end
                    if cOk and crit and (crit.description and crit.description ~= "" or crit.quantityString and crit.quantityString ~= "" or (crit.quantity and crit.totalQuantity)) then
                        local q = crit.quantity
                        local tq = crit.totalQuantity
                        local qs = crit.quantityString or ""
                        local pct = (tq and tq > 0 and q) and math.floor(100 * q / tq) or "n/a"
                        local desc = (crit.description or crit.criteriaString or "") ~= "" and (crit.description or crit.criteriaString) or "(no desc)"
                        HSPrint(("  Criteria #%d: desc=%q quantity=%s totalQuantity=%s quantityString=%q percent=%s isWeighted=%s"):format(
                            i, tostring(desc):sub(1, 50), tostring(q), tostring(tq), tostring(qs):sub(1, 30), tostring(pct), tostring(crit.isWeightedProgress or false)))
                    end
                end
            end
        end

    else
        ShowFocusDebugHelp()
    end
end

addon.RegisterSlashHandler("focus", HandleFocusSlash)
if addon.RegisterSlashHandlerDebug then
    addon.RegisterSlashHandlerDebug("focus", HandleFocusDebugSlash)
end
if addon.RegisterDebugLive then
    addon.RegisterDebugLive("focus", function(v) if addon.focus and addon.focus.SetDebugLive then addon.focus.SetDebugLive(v) end end)
end
