--[[
    Commander 3.0 - Utility Commands

    Quality-of-life commands: cvar, calc, volume, fps, pane, emote

    These are general system utilities that don't fit into datasources.
]]

local ADDON_NAME, C3 = ...

-- Use shared utilities
local Colorize = function(...) return C3.Colors.Colorize(...) end
local GetThemeColor = function(...) return C3.Colors.GetColor(...) end

-------------------------------------------------------------------------------
-- cvar - Get/Set Console Variables
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "cvar",
    description = "Get or set console variables",
    category = "utility",
    acceptsInput = false,
    aliases = { "cv" },
    args = {
        {
            name = "name",
            required = false,
            description = "CVar name",
            provider = function(partial, ctx)
                local suggestions = {}

                -- Get all CVars from WoW API
                if ConsoleGetAllCommands then
                    local commands = ConsoleGetAllCommands()
                    for _, cmd in ipairs(commands) do
                        -- Filter for CVars only (commandType 0)
                        if cmd.commandType == 0 then
                            local currentValue = GetCVar(cmd.command)
                            table.insert(suggestions, {
                                text = cmd.command,
                                description = currentValue and ("= " .. currentValue) or "",
                            })
                        end
                    end
                end

                -- Use fuzzy filter
                return C3.Intellisense.fuzzyFilter(suggestions, partial, function(item)
                    return item.text
                end, 20)
            end
        },
        { name = "value", required = false, description = "New value (omit to get current)" },
    },
    execute = function(ctx)
        local name = ctx:GetArg(1)
        local value = ctx:GetArg(2)

        if not name or name == "" then
            return ctx:Error("Usage: cvar <name> [value]")
        end

        if value ~= nil and value ~= "" then
            -- Set CVar
            local success = SetCVar(name, value)
            if success then
                return ctx:Text(string.format("%s %s = %s", Colorize("Set", "accent.success"), name, Colorize(value, "accent.warning")))
            else
                return ctx:Error("Failed to set CVar: " .. name)
            end
        else
            -- Get CVar
            local current = GetCVar(name)
            if current == nil then
                return ctx:Error("Unknown CVar: " .. name)
            end
            local default = GetCVarDefault(name) or "?"
            return ctx:Text(string.format("%s = %s %s", name, Colorize(current, "accent.warning"), Colorize("(default: " .. default .. ")", "status.dim")))
        end
    end
})

-------------------------------------------------------------------------------
-- calc - Math Expression Evaluation
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "calc",
    description = "Evaluate math expression",
    category = "utility",
    acceptsInput = false,
    args = {
        { name = "expression", required = true, vararg = true, description = "Math expression" }
    },
    execute = function(ctx)
        local args = ctx:GetArgs()
        local expr = table.concat(args, " ")

        if not expr or expr == "" then
            return ctx:Error("Usage: calc <expression>")
        end

        -- Create safe math environment
        local env = {
            abs = math.abs,
            ceil = math.ceil,
            floor = math.floor,
            max = math.max,
            min = math.min,
            pow = math.pow,
            sqrt = math.sqrt,
            sin = math.sin,
            cos = math.cos,
            tan = math.tan,
            asin = math.asin,
            acos = math.acos,
            atan = math.atan,
            log = math.log,
            log10 = math.log10,
            exp = math.exp,
            deg = math.deg,
            rad = math.rad,
            random = math.random,
            pi = math.pi,
            e = math.exp(1),
            -- Bitwise operations (WoW has these)
            band = bit and bit.band,
            bor = bit and bit.bor,
            bxor = bit and bit.bxor,
            bnot = bit and bit.bnot,
            lshift = bit and bit.lshift,
            rshift = bit and bit.rshift,
        }

        -- Sanitize: only allow safe characters
        -- Numbers, operators, parens, spaces, and word chars (for function names)
        local sanitized = expr:gsub("[^%d%.%+%-%*/%^%(%)%s%w,]", "")

        -- Try to compile and run
        local fn, compileErr = loadstring("return " .. sanitized)
        if not fn then
            return ctx:Error("Invalid expression: " .. (compileErr or "syntax error"))
        end

        setfenv(fn, env)

        local ok, result = pcall(fn)
        if not ok then
            return ctx:Error("Evaluation error: " .. tostring(result))
        end

        -- Format result nicely
        local resultStr
        if type(result) == "number" then
            if result == math.floor(result) then
                resultStr = string.format("%d", result)
            else
                resultStr = string.format("%.6g", result)
            end
        else
            resultStr = tostring(result)
        end

        return ctx:Text(string.format("%s = %s", expr, Colorize(resultStr, "accent.success")))
    end
})

-------------------------------------------------------------------------------
-- volume - Sound Volume Control
-------------------------------------------------------------------------------

local VOLUME_CHANNELS = {
    master = "Sound_MasterVolume",
    music = "Sound_MusicVolume",
    sfx = "Sound_SFXVolume",
    ambience = "Sound_AmbienceVolume",
    dialog = "Sound_DialogVolume",
}

C3.Types:Register("Volume", {
    description = "Sound volume setting",
    source = "volume",
    properties = {
        { "channel", "Channel name" },
        { "volume", "Volume level (0-100)" },
        { "cvar", "CVar name" },
    },
    format = function(self)
        local bar = ""
        local filled = math.floor(self.volume / 5)
        local filledColor = GetThemeColor("accent.success") or "A3BE8C"
        local emptyColor = GetThemeColor("status.dim") or "4C566A"
        for i = 1, 20 do
            if i <= filled then
                bar = bar .. string.format("|cFF%s%s|r", filledColor, "\226\150\136")
            else
                bar = bar .. string.format("|cFF%s%s|r", emptyColor, "\226\150\145")
            end
        end
        local channelText = Colorize(string.format("%-10s", self.channel), "accent.warning")
        local percentText = Colorize(string.format("%3d%%", self.volume), "status.dim")
        return string.format("%s %s %s", channelText, bar, percentText)
    end
})

-- Build volume channel suggestions for intellisense
local VOLUME_SUGGESTIONS = {}
for name, cvar in pairs(VOLUME_CHANNELS) do
    VOLUME_SUGGESTIONS[#VOLUME_SUGGESTIONS + 1] = {
        text = name,
        description = cvar,
    }
end
table.sort(VOLUME_SUGGESTIONS, function(a, b) return a.text < b.text end)

C3.Command:Define({
    name = "volume",
    description = "Get or set sound volume",
    category = "utility",
    acceptsInput = false,
    args = {
        {
            name = "channel",
            required = false,
            description = "Channel: master, music, sfx, ambience, dialog",
            provider = function(partial, ctx)
                return C3.Intellisense.fuzzyFilter(VOLUME_SUGGESTIONS, partial, function(item)
                    return item.text
                end)
            end
        },
        { name = "value", required = false, description = "Volume 0-100" },
    },
    execute = function(ctx)
        local channel = ctx:GetArg(1)
        local value = tonumber(ctx:GetArg(2))

        if not channel or channel == "" then
            -- Show all volumes
            local results = {}
            for name, cvar in pairs(VOLUME_CHANNELS) do
                local vol = tonumber(GetCVar(cvar)) or 0
                results[#results + 1] = C3.Types.Create("Volume", {
                    channel = name,
                    volume = math.floor(vol * 100),
                    cvar = cvar,
                })
            end
            -- Sort by channel name
            table.sort(results, function(a, b) return a.channel < b.channel end)
            return results
        end

        local cvar = VOLUME_CHANNELS[channel:lower()]
        if not cvar then
            local validChannels = {}
            for name, _ in pairs(VOLUME_CHANNELS) do
                validChannels[#validChannels + 1] = name
            end
            return ctx:Error("Unknown channel: " .. channel .. "\nValid: " .. table.concat(validChannels, ", "))
        end

        if value ~= nil then
            -- Clamp to 0-100
            value = math.max(0, math.min(100, value))
            SetCVar(cvar, value / 100)
            return ctx:Text(string.format("%s %s volume to %s", Colorize("Set", "accent.success"), channel, Colorize(value .. "%", "accent.warning")))
        else
            local vol = tonumber(GetCVar(cvar)) or 0
            return C3.Types.Create("Volume", {
                channel = channel,
                volume = math.floor(vol * 100),
                cvar = cvar,
            })
        end
    end
})

-------------------------------------------------------------------------------
-- fps / perf - Performance Statistics
-------------------------------------------------------------------------------

C3.Types:Register("PerfStat", {
    description = "Performance statistic",
    source = "fps, perf",
    properties = {
        { "name", "Stat name" },
        { "value", "Stat value" },
        { "unit", "Unit (fps, ms, MB, etc.)" },
    },
    format = function(self)
        local nameText = Colorize(string.format("%-20s", self.name), "accent.warning")
        local valueText = Colorize(self.value, "accent.success")
        return string.format("%s %s %s", nameText, valueText, self.unit or "")
    end
})

C3.Command:Define({
    name = "fps",
    description = "Show performance statistics",
    category = "utility",
    acceptsInput = false,
    execute = function(ctx)
        -- FPS
        local fps = GetFramerate()

        -- Network latency
        local _, _, latencyHome, latencyWorld = GetNetStats()

        -- Memory usage
        UpdateAddOnMemoryUsage()
        local totalMem = 0
        local numAddOns = C_AddOns and C_AddOns.GetNumAddOns() or GetNumAddOns()
        for i = 1, numAddOns do
            totalMem = totalMem + (GetAddOnMemoryUsage(i) or 0)
        end

        -- Commander3 memory specifically
        local c3Mem = GetAddOnMemoryUsage(ADDON_NAME) or 0

        return {
            C3.Types.Create("PerfStat", { name = "Framerate", value = string.format("%.1f", fps), unit = "fps" }),
            C3.Types.Create("PerfStat", { name = "Latency (Home)", value = tostring(latencyHome), unit = "ms" }),
            C3.Types.Create("PerfStat", { name = "Latency (World)", value = tostring(latencyWorld), unit = "ms" }),
            C3.Types.Create("PerfStat", { name = "Total Addon Memory", value = string.format("%.1f", totalMem / 1024), unit = "MB" }),
            C3.Types.Create("PerfStat", { name = "Commander3 Memory", value = string.format("%.1f", c3Mem), unit = "KB" }),
        }
    end
})

-------------------------------------------------------------------------------
-- options / opt - Open Settings Panel
-------------------------------------------------------------------------------

-- Cache for settings categories (built on first use)
local settingsCategoryCache = nil

---Convert string to URL-style slug (lowercase, spaces to dashes)
---@param str string
---@return string
local function Slugify(str)
    return str and str:lower():gsub("%s+", "-") or ""
end

---Build display name for a category (e.g., "System > Graphics")
---@param cat table
---@return string
local function BuildCategoryDisplayName(cat)
    if cat.group and cat.parent then
        return cat.group .. " > " .. cat.parent .. " > " .. cat.name
    elseif cat.group then
        return cat.group .. " > " .. cat.name
    end
    return cat.name
end

---Add a category to the cache
---@param id number
---@param name string
---@param groupSlug string
---@param groupName string
---@param parentName string?
local function AddCategoryToCache(id, name, groupSlug, groupName, parentName)
    if not name or name == "" or not id then return end

    local slug = groupSlug ~= "" and (groupSlug .. "-" .. Slugify(name)) or Slugify(name)
    settingsCategoryCache[#settingsCategoryCache + 1] = {
        id = id,
        name = name,
        text = slug,
        group = groupName,
        parent = parentName,
    }
end

---Get all settings categories from the Settings API
---@return table[] categories
local function GetSettingsCategories()
    if settingsCategoryCache then return settingsCategoryCache end

    settingsCategoryCache = {}

    -- Guard: No modern Settings API
    if not SettingsPanel or not SettingsPanel.GetCategoryList then
        return settingsCategoryCache
    end

    local categoryList = SettingsPanel:GetCategoryList()
    if not categoryList or not categoryList.groups then
        return settingsCategoryCache
    end

    for _, group in ipairs(categoryList.groups) do
        local groupName = group.groupText
        local groupSlug = Slugify(groupName)

        for _, category in ipairs(group.categories or {}) do
            local name, id = category:GetName(), category:GetID()
            AddCategoryToCache(id, name, groupSlug, groupName, nil)

            -- Add subcategories
            for _, subcat in ipairs(category.subcategories or {}) do
                AddCategoryToCache(subcat:GetID(), subcat:GetName(), groupSlug, groupName, name)
            end
        end
    end

    return settingsCategoryCache
end

---Find a category by slug (exact match only)
---@param slug string The slug like "system-graphics" or "gameplay-interface"
---@return table? category
local function FindSettingsCategory(slug)
    if not slug or slug == "" then return nil end

    local slugNorm = Slugify(slug)
    for _, cat in ipairs(GetSettingsCategories()) do
        if cat.text == slugNorm then return cat end
    end
    return nil
end

---Open the settings panel to a specific category or search
---@param categoryOrSearch string?
---@return boolean success
---@return string? categoryName
local function OpenSettingsPanel(categoryOrSearch)
    -- Guard: No modern Settings API
    if not SettingsPanel then
        if InterfaceOptionsFrame then
            InterfaceOptionsFrame_OpenToCategory(INTERFACEOPTIONS_ADDONCATEGORIES)
            return true, nil
        end
        if VideoOptionsFrame then
            ShowUIPanel(VideoOptionsFrame)
            return true, nil
        end
        return false, nil
    end

    -- Try to find a matching category
    local match = FindSettingsCategory(categoryOrSearch)

    if match and match.id then
        Settings.OpenToCategory(match.id)
        return true, match.name
    end

    -- No category match - open default and search
    if not SettingsPanel:IsShown() then
        Settings.OpenToCategory(Settings.INTERFACE_CATEGORY_ID)
    end

    -- Apply search if provided
    if categoryOrSearch and categoryOrSearch ~= "" and SettingsPanel.SearchBox then
        C_Timer.After(0.1, function()
            SettingsPanel.SearchBox:SetText(categoryOrSearch)
            if SettingsPanel.SearchBox.SearchButton then
                SettingsPanel.SearchBox.SearchButton:Click()
            end
        end)
    end

    return true, nil
end

C3.Command:Define({
    name = "options",
    description = "Open the game settings panel (closes terminal by default)",
    category = "utility",
    acceptsInput = false,
    aliases = { "opt" },
    flags = {
        stay = { short = "s", description = "Keep terminal open after opening settings" },
    },
    args = {
        {
            name = "category",
            required = false,
            vararg = true,
            description = "Category to open or search term",
            provider = function(partial)
                local suggestions = {}
                for _, cat in ipairs(GetSettingsCategories()) do
                    suggestions[#suggestions + 1] = {
                        text = cat.text,
                        displayText = cat.name,
                        highlightPartial = partial,
                        description = BuildCategoryDisplayName(cat),
                    }
                end
                return C3.Intellisense.fuzzyFilter(suggestions, partial, function(item)
                    return item.text .. " " .. item.displayText .. " " .. item.description
                end, 50)
            end
        }
    },
    execute = function(ctx)
        -- Join all args to support multi-word search like "interact key"
        local args = ctx:GetArgs()
        local categoryOrSearch = args and #args > 0 and table.concat(args, " ") or nil

        local success, categoryName = OpenSettingsPanel(categoryOrSearch)

        -- Guard: Panel unavailable
        if not success then
            return ctx:Error("Settings panel not available")
        end

        -- Close terminal by default (unless --stay flag is set)
        if not ctx:GetFlag("stay") then
            local session = ctx.session
            local terminal = session and session.terminal
            if terminal then
                terminal:Hide()
            end
        end

        -- Guard: No argument
        if not categoryOrSearch or categoryOrSearch == "" then return nil end

        -- Opened specific category
        if categoryName then
            return ctx:Text("Opened " .. Colorize(categoryName, "accent.primary") .. " settings")
        end

        -- Searching
        return ctx:Text("Opened settings " .. Colorize("(searching: " .. categoryOrSearch .. ")", "status.dim"))
    end
})

-------------------------------------------------------------------------------
-- kb / keybind - Quick Keybind Mode
-------------------------------------------------------------------------------

-- Track if we opened settings panel for kb mode
local kbOpenedSettingsPanel = false

-- Hook QuickKeybindFrame to close settings when done
local function SetupQuickKeybindHook()
    if not QuickKeybindFrame then return end
    if QuickKeybindFrame._c3Hooked then return end

    QuickKeybindFrame:HookScript("OnHide", function()
        if kbOpenedSettingsPanel and SettingsPanel and SettingsPanel:IsShown() then
            HideUIPanel(SettingsPanel)
        end
        kbOpenedSettingsPanel = false
    end)

    QuickKeybindFrame._c3Hooked = true
end

C3.Command:Define({
    name = "kb",
    description = "Open WoW's quick keybind mode",
    category = "utility",
    acceptsInput = false,
    aliases = { "keybind" },
    flags = {
        settings = { short = "s", description = "Open full keybinding settings instead of quick mode" },
        character = { short = "c", description = "Use character-specific bindings" },
        account = { short = "a", description = "Use account-wide bindings" },
    },
    execute = function(ctx)
        -- Guard: Combat lockdown
        if InCombatLockdown() then
            return ctx:Error("Cannot open keybindings during combat")
        end

        local useSettings = ctx:HasFlag("settings") or ctx:HasFlag("s")
        local useCharacter = ctx:HasFlag("character") or ctx:HasFlag("c")
        local useAccount = ctx:HasFlag("account") or ctx:HasFlag("a")

        -- Guard: Conflicting flags
        if useCharacter and useAccount then
            return ctx:Error("Cannot use both --character and --account")
        end

        -- Switch binding set if requested
        local bindingSetMsg = ""
        if useCharacter or useAccount then
            local targetSet = useCharacter and 2 or 1  -- 1 = account, 2 = character
            local currentSet = GetCurrentBindingSet()

            if currentSet ~= targetSet then
                LoadBindings(targetSet)
                local setName = useCharacter and "character" or "account"
                bindingSetMsg = " " .. Colorize("(" .. setName .. " bindings)", "status.dim")
            end
        end

        -- --settings flag: Open full settings page
        if useSettings then
            if SettingsPanel then
                Settings.OpenToCategory(Settings.KEYBINDING_CATEGORY_ID)
                return ctx:Text("Opened keybinding settings" .. bindingSetMsg)
            end

            if KeyBindingFrame then
                ShowUIPanel(KeyBindingFrame)
                return ctx:Text("Opened keybinding settings" .. bindingSetMsg)
            end

            return ctx:Error("Keybinding settings not available")
        end

        -- QuickKeybindFrame (modern WoW)
        if QuickKeybindFrame then
            -- Guard: Already open
            if QuickKeybindFrame:IsShown() then
                return ctx:Text("Quick keybind mode already open")
            end

            SetupQuickKeybindHook()

            -- Track if settings panel wasn't already open
            kbOpenedSettingsPanel = not (SettingsPanel and SettingsPanel:IsShown())

            QuickKeybindFrame:Show()
            return ctx:Text(Colorize("Opened", "accent.success") .. " quick keybind mode" .. bindingSetMsg)
        end

        -- Fallback: Settings panel
        if SettingsPanel then
            Settings.OpenToCategory(Settings.KEYBINDING_CATEGORY_ID)
            return ctx:Text("Opened keybinding settings" .. bindingSetMsg)
        end

        -- Legacy fallback
        if KeyBindingFrame then
            if KeyBindingFrame:IsShown() then
                HideUIPanel(KeyBindingFrame)
            else
                ShowUIPanel(KeyBindingFrame)
            end
            return nil
        end

        return ctx:Error("Keybinding UI not available")
    end
})

-------------------------------------------------------------------------------
-- pane - Open UI Panels
-------------------------------------------------------------------------------

local PANELS = {
    character = function() ToggleCharacter("PaperDollFrame") end,
    char = function() ToggleCharacter("PaperDollFrame") end,
    spellbook = function() ToggleSpellBook(BOOKTYPE_SPELL) end,
    spells = function() ToggleSpellBook(BOOKTYPE_SPELL) end,
    talents = function()
        if PlayerTalentFrame then
            if PlayerTalentFrame:IsShown() then
                HideUIPanel(PlayerTalentFrame)
            else
                ShowUIPanel(PlayerTalentFrame)
            end
        elseif ClassTalentFrame then
            if ClassTalentFrame:IsShown() then
                HideUIPanel(ClassTalentFrame)
            else
                ShowUIPanel(ClassTalentFrame)
            end
        elseif ToggleTalentFrame then
            ToggleTalentFrame()
        end
    end,
    achievements = function()
        ToggleAchievementFrame()
    end,
    ach = function()
        ToggleAchievementFrame()
    end,
    collections = function()
        if ToggleCollectionsJournal then
            ToggleCollectionsJournal()
        end
    end,
    mounts = function()
        if ToggleCollectionsJournal then
            ToggleCollectionsJournal(1)
        end
    end,
    pets = function()
        if ToggleCollectionsJournal then
            ToggleCollectionsJournal(2)
        end
    end,
    toys = function()
        if ToggleCollectionsJournal then
            ToggleCollectionsJournal(3)
        end
    end,
    heirlooms = function()
        if ToggleCollectionsJournal then
            ToggleCollectionsJournal(4)
        end
    end,
    wardrobe = function()
        if ToggleCollectionsJournal then
            ToggleCollectionsJournal(5)
        end
    end,
    pvp = function()
        if PVEFrame then
            if PVEFrame:IsShown() then
                HideUIPanel(PVEFrame)
            else
                ShowUIPanel(PVEFrame)
                if PVEFrame.SetTab then
                    PVEFrame:SetTab(2)
                end
            end
        end
    end,
    lfg = function()
        if PVEFrame then
            if PVEFrame:IsShown() then
                HideUIPanel(PVEFrame)
            else
                ShowUIPanel(PVEFrame)
            end
        end
    end,
    guild = function()
        if ToggleGuildFrame then
            ToggleGuildFrame()
        end
    end,
    social = function()
        ToggleFriendsFrame()
    end,
    friends = function()
        ToggleFriendsFrame()
    end,
    map = function()
        ToggleWorldMap()
    end,
    quest = function()
        if QuestLogFrame then
            if QuestLogFrame:IsShown() then
                HideUIPanel(QuestLogFrame)
            else
                ShowUIPanel(QuestLogFrame)
            end
        elseif QuestMapFrame then
            ToggleWorldMap()
        end
    end,
    journal = function()
        if ToggleEncounterJournal then
            ToggleEncounterJournal()
        end
    end,
    calendar = function()
        if ToggleCalendar then
            ToggleCalendar()
        end
    end,
    bags = function()
        ToggleAllBags()
    end,
    bag = function()
        ToggleAllBags()
    end,
    inventory = function()
        ToggleAllBags()
    end,
}

-- Build suggestions list for intellisense with descriptions
local PANEL_SUGGESTIONS = {
    { text = "character", description = "Character Info (PaperDollFrame)" },
    { text = "char", description = "Character Info" },
    { text = "spellbook", description = "Spellbook (PlayerSpellsFrame)" },
    { text = "spells", description = "Spellbook" },
    { text = "talents", description = "Talents (ClassTalentFrame)" },
    { text = "achievements", description = "Achievements" },
    { text = "ach", description = "Achievements" },
    { text = "collections", description = "Collections Journal" },
    { text = "mounts", description = "Mounts (CollectionsJournal)" },
    { text = "pets", description = "Pets (CollectionsJournal)" },
    { text = "toys", description = "Toys (CollectionsJournal)" },
    { text = "heirlooms", description = "Heirlooms (CollectionsJournal)" },
    { text = "wardrobe", description = "Appearances (CollectionsJournal)" },
    { text = "pvp", description = "PvP (PVEFrame)" },
    { text = "lfg", description = "Group Finder (PVEFrame)" },
    { text = "guild", description = "Guild (GuildFrame)" },
    { text = "social", description = "Social/Friends (FriendsFrame)" },
    { text = "friends", description = "Friends (FriendsFrame)" },
    { text = "map", description = "World Map" },
    { text = "quest", description = "Quest Log" },
    { text = "journal", description = "Encounter Journal" },
    { text = "calendar", description = "Calendar" },
    { text = "bags", description = "Bags/Inventory" },
    { text = "bag", description = "Bags/Inventory" },
    { text = "inventory", description = "Bags/Inventory" },
}

-- Build simple name list for error messages
local PANEL_NAMES = {}
for _, p in ipairs(PANEL_SUGGESTIONS) do
    PANEL_NAMES[#PANEL_NAMES + 1] = p.text
end

C3.Command:Define({
    name = "pane",
    description = "Open/toggle a UI panel",
    category = "utility",
    acceptsInput = false,
    args = {
        {
            name = "panel",
            required = true,
            description = "Panel name",
            provider = function(partial, ctx)
                return C3.Intellisense.fuzzyFilter(PANEL_SUGGESTIONS, partial, function(item)
                    return item.text
                end)
            end
        }
    },
    execute = function(ctx)
        local panel = ctx:GetArg(1)
        if not panel or panel == "" then
            return ctx:Error("Usage: pane <panel>\nPanels: " .. table.concat(PANEL_NAMES, ", "))
        end

        local toggleFn = PANELS[panel:lower()]
        if not toggleFn then
            return ctx:Error("Unknown panel: " .. panel .. "\nValid: " .. table.concat(PANEL_NAMES, ", "))
        end

        toggleFn()
        return nil  -- Panel opens, no text output needed
    end
})

-------------------------------------------------------------------------------
-- emote - Perform Emotes
-------------------------------------------------------------------------------

-- All standard WoW emotes (as strings for simple list)
local EMOTE_NAMES = {
    "agree", "amaze", "angry", "apologize", "applaud", "attacktarget", "bark",
    "bashful", "beckon", "beg", "bite", "bleed", "blink", "blush", "boggle",
    "bonk", "bored", "bounce", "bow", "bravo", "burp", "bye", "cackle", "calm",
    "charge", "cheer", "chew", "chicken", "chuckle", "clap", "cold", "comfort",
    "commend", "confused", "congratulate", "cough", "cower", "crack", "cringe",
    "cry", "cuddle", "curious", "curtsey", "dance", "disappointed", "doh",
    "doom", "drink", "drool", "duck", "eat", "embarrass", "encourage", "enemy",
    "excited", "eye", "facepalm", "fart", "fidget", "flap", "flee", "flex",
    "flirt", "flop", "follow", "frown", "gasp", "gaze", "giggle", "glare",
    "gloat", "glower", "golfclap", "goodbye", "greet", "grin", "groan", "grovel",
    "growl", "guffaw", "hail", "happy", "headache", "healme", "hello", "helpme",
    "hi", "highfive", "hug", "hungry", "impatient", "incoming", "insult",
    "introduce", "jealous", "jk", "joke", "kiss", "kneel", "laugh", "laydown",
    "lick", "listen", "look", "lost", "love", "luck", "map", "massage", "moan",
    "mock", "moo", "moon", "mourn", "mountspecial", "no", "nod", "nosepick",
    "oom", "openfire", "panic", "pat", "peer", "peon", "pet", "pinch", "pity",
    "plead", "point", "poke", "ponder", "pounce", "praise", "pray", "proud",
    "pulse", "punch", "purr", "puzzle", "question", "raise", "rasp", "raspberry",
    "ready", "read", "retreat", "revenge", "roar", "rofl", "rude", "ruffle",
    "sad", "salute", "scared", "scold", "scowl", "scratch", "sexy", "shake",
    "shakefist", "shame", "shimmy", "shiver", "shoo", "shrug", "shy", "sigh",
    "signal", "silence", "silly", "sing", "sit", "slap", "sleep", "smell",
    "smile", "smirk", "snap", "snarl", "snicker", "sniff", "snub", "soothe",
    "sorry", "spit", "spoon", "squeal", "stand", "stare", "stink", "stop",
    "strong", "strut", "surprised", "surrender", "talk", "tap", "taunt", "tease",
    "thank", "thanks", "think", "thirsty", "threaten", "tickle", "tired",
    "train", "truce", "twiddle", "ty", "veto", "victory", "violin", "volunteer",
    "wait", "warn", "wave", "weak", "welcome", "whine", "whistle", "wicked",
    "wink", "work", "wrath", "yawn", "yes", "yummy"
}

-- Convert to suggestion format for fuzzyFilter
local EMOTE_SUGGESTIONS = {}
for _, emote in ipairs(EMOTE_NAMES) do
    EMOTE_SUGGESTIONS[#EMOTE_SUGGESTIONS + 1] = { text = emote, description = "Emote" }
end

C3.Command:Define({
    name = "emote",
    description = "Perform an emote",
    category = "action",
    acceptsInput = false,
    args = {
        {
            name = "emote",
            required = true,
            description = "Emote name",
            provider = function(partial, ctx)
                return C3.Intellisense.fuzzyFilter(EMOTE_SUGGESTIONS, partial, function(item)
                    return item.text
                end, 20)
            end
        }
    },
    execute = function(ctx)
        local emoteName = ctx:GetArg(1)
        if not emoteName or emoteName == "" then
            return ctx:Error("Usage: emote <name>")
        end

        DoEmote(emoteName:upper())
        return nil  -- Emote output shows in chat
    end
})

-------------------------------------------------------------------------------
-- zoom - Font Zoom Control
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "zoom",
    description = "Get/set terminal font zoom level",
    category = "utility",
    acceptsInput = false,
    args = {
        {
            name = "action",
            required = false,
            description = "in, out, reset, default, or percentage (50-250)",
            provider = function(partial, ctx)
                local suggestions = {
                    { text = "in", description = "Zoom in (+10%)" },
                    { text = "out", description = "Zoom out (-10%)" },
                    { text = "reset", description = "Reset to default zoom" },
                    { text = "default", description = "Set current zoom as default" },
                    { text = "100", description = "Set zoom to 100%" },
                    { text = "120", description = "Set zoom to 120%" },
                    { text = "150", description = "Set zoom to 150%" },
                }
                return C3.Intellisense.fuzzyFilter(suggestions, partial, function(item)
                    return item.text
                end)
            end
        }
    },
    execute = function(ctx)
        local Theme = C3.UI.Theme
        if not Theme then
            return ctx:Error("Theme not available")
        end

        local action = ctx:GetArg(1)

        -- No action = show current zoom
        if not action or action == "" then
            local current = Theme:GetZoomPercent()
            local default = Theme:GetDefaultZoomPercent()
            local effective = Theme:GetEffectiveFontSize()

            local defaultInfo = ""
            if default ~= 100 then
                defaultInfo = string.format(" %s", Colorize("(default: " .. default .. "%)", "status.dim"))
            end

            return ctx:Text(string.format(
                "Zoom: %s %s%s",
                Colorize(current .. "%", "accent.primary"),
                Colorize("(" .. effective .. "pt)", "status.dim"),
                defaultInfo
            ))
        end

        local actionLower = action:lower()

        if actionLower == "in" or actionLower == "+" then
            Theme:ZoomIn()
            return ctx:Text(string.format("Zoom: %s", Colorize(Theme:GetZoomPercent() .. "%", "accent.primary")))

        elseif actionLower == "out" or actionLower == "-" then
            Theme:ZoomOut()
            return ctx:Text(string.format("Zoom: %s", Colorize(Theme:GetZoomPercent() .. "%", "accent.primary")))

        elseif actionLower == "reset" or actionLower == "0" then
            Theme:ZoomReset()
            return ctx:Text(string.format("Zoom reset to %s", Colorize(Theme:GetZoomPercent() .. "%", "accent.primary")))

        elseif actionLower == "default" then
            -- Set current zoom as the new default
            Theme:SetDefaultZoom(Theme.FontScale)
            return ctx:Text(string.format(
                "Default zoom set to %s",
                Colorize(Theme:GetDefaultZoomPercent() .. "%", "accent.success")
            ))

        else
            -- Try to parse as percentage
            local pct = tonumber(action)
            if pct and pct >= 50 and pct <= 250 then
                Theme.FontScale = pct / 100
                Theme:_notifyZoomChanged()
                return ctx:Text(string.format("Zoom: %s", Colorize(pct .. "%", "accent.primary")))
            else
                return ctx:Error("Usage: zoom [in|out|reset|default|50-250]")
            end
        end
    end
})

-------------------------------------------------------------------------------
-- linespacing - Line Spacing Control (Development)
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "linespacing",
    description = "Get/set line spacing in output (development setting)",
    category = "utility",
    acceptsInput = false,
    args = {
        {
            name = "value",
            required = false,
            description = "Spacing in pixels (0-10), or +/- to adjust",
            provider = function(partial, ctx)
                local suggestions = {
                    { text = "+", description = "Increase by 1 pixel" },
                    { text = "-", description = "Decrease by 1 pixel" },
                    { text = "0", description = "Tight (no extra spacing)" },
                    { text = "2", description = "Compact" },
                    { text = "3", description = "Default" },
                    { text = "4", description = "Comfortable" },
                    { text = "6", description = "Loose" },
                }
                return C3.Intellisense.fuzzyFilter(suggestions, partial, function(item)
                    return item.text
                end)
            end
        }
    },
    execute = function(ctx)
        local Theme = C3.UI.Theme
        if not Theme then
            return ctx:Error("Theme not available")
        end

        local value = ctx:GetArg(1)

        -- No value = show current spacing
        if not value or value == "" then
            local current = Theme:GetLineSpacing()
            local lineHeight = Theme:GetEffectiveLineHeight()
            local fontSize = Theme:GetEffectiveFontSize()

            return ctx:Text(string.format(
                "Line spacing: %s %s",
                Colorize(current .. "px", "accent.primary"),
                Colorize("(line height: " .. lineHeight .. "px = " .. fontSize .. "pt font + " .. current .. "px)", "status.dim")
            ))
        end

        if value == "+" then
            local current = Theme:GetLineSpacing()
            Theme:SetLineSpacing(current + 1)
            return ctx:Text(string.format("Line spacing: %s", Colorize(Theme:GetLineSpacing() .. "px", "accent.primary")))

        elseif value == "-" then
            local current = Theme:GetLineSpacing()
            Theme:SetLineSpacing(current - 1)
            return ctx:Text(string.format("Line spacing: %s", Colorize(Theme:GetLineSpacing() .. "px", "accent.primary")))

        else
            -- Try to parse as number
            local num = tonumber(value)
            if num and num >= 0 and num <= 10 then
                Theme:SetLineSpacing(num)
                return ctx:Text(string.format("Line spacing: %s", Colorize(num .. "px", "accent.primary")))
            else
                return ctx:Error("Usage: linespacing [+|-|0-10]")
            end
        end
    end
})

-------------------------------------------------------------------------------
-- fonttest - Test Font Character Alignment
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "fonttest",
    description = "Display UI elements to test font and theme",
    category = "utility",
    acceptsInput = false,
    execute = function(ctx)
        local results = {}

        local function add(text)
            table.insert(results, ctx:Text(text or ""))
        end

        local function header(text)
            add("")
            add(Colorize("━━━ " .. text .. " ━━━", "accent.primary"))
        end

        local function subheader(text)
            add(Colorize(text, "accent.info"))
        end

        -- Title (no box - color codes break alignment)
        add("")
        add(Colorize("══════════════════════════════════════════════════════════════", "accent.primary"))
        add("            " .. Colorize("Commander 3.0 - Font & UI Test", "accent.warning"))
        add(Colorize("══════════════════════════════════════════════════════════════", "accent.primary"))

        -- =====================================================================
        header("BOX DRAWING BORDERS")
        -- =====================================================================

        subheader("Light Border:")
        --   1234567890123456789012345678901234567890
        add("┌────────────────────────────────────────┐")
        add("│ Light box with plain ASCII content     │")
        add("│ Second line of content here            │")
        add("└────────────────────────────────────────┘")

        add("")
        subheader("Double Border:")
        --   1234567890123456789012345678901234567890
        add("╔════════════════════════════════════════╗")
        add("║ Double box with plain ASCII content    ║")
        add("║ Commonly used for dialogs and headers  ║")
        add("╚════════════════════════════════════════╝")

        add("")
        subheader("Rounded Border:")
        --   1234567890123456789012345678901234567890
        add("╭────────────────────────────────────────╮")
        add("│ Rounded corners for modern look        │")
        add("│ Friendly and softer appearance         │")
        add("╰────────────────────────────────────────╯")

        add("")
        subheader("Heavy Border:")
        --   1234567890123456789012345678901234567890
        add("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
        add("┃ Heavy box for emphasis                 ┃")
        add("┃ Bold important content stands out     ┃")
        add("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")

        add("")
        subheader("Box Drawing Characters Reference:")
        add("Light:   ─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼")
        add("Heavy:   ━ ┃ ┏ ┓ ┗ ┛ ┣ ┫ ┳ ┻ ╋")
        add("Double:  ═ ║ ╔ ╗ ╚ ╝ ╠ ╣ ╦ ╩ ╬")
        add("Rounded: ╭ ╮ ╯ ╰")

        -- =====================================================================
        header("FILE TREE DISPLAY")
        -- =====================================================================

        add(Colorize(" /home/scripts", "accent.warning"))
        add("├──  config/")
        add("│   ├──  settings.lua")
        add("│   ├──  keybinds.lua")
        add("│   └──  theme.lua")
        add("├──  commands/")
        add("│   ├──  utility/")
        add("│   │   └──  init.lua")
        add("│   └──  query/")
        add("│       └──  operators.lua")
        add("├──  README.md")
        add("└──  init.lua")

        -- =====================================================================
        header("DATA TABLES")
        -- =====================================================================

        subheader("Plain Table (no color codes):")
        --   123456789012345678 12345678 1234567890
        add("┌──────────────────┬────────┬──────────┐")
        add("│ Name             │ Level  │ Class    │")
        add("├──────────────────┼────────┼──────────┤")
        add("│ Thrall           │   70   │ Shaman   │")
        add("│ Jaina            │   70   │ Mage     │")
        add("│ Arthas           │   80   │ DK       │")
        add("└──────────────────┴────────┴──────────┘")

        add("")
        subheader("Colored List (no borders - color codes break alignment):")
        add(Colorize("  Qty  Item                          iLvl   Quality", "accent.warning"))
        add("  1    " .. Colorize("Hearthstone", "status.dim") .. "                   1      Common")
        add("  20   " .. Colorize("Runed Copper Rod", "accent.success") .. "          15     Uncommon")
        add("  1    " .. Colorize("Staff of Infinite Mysteries", "accent.info") .. "  115    Rare")
        add("  1    " .. Colorize("Thunderfury", "accent.special") .. "               80     Legendary")

        -- =====================================================================
        header("PROGRESS BARS & STATUS")
        -- =====================================================================

        subheader("Health/Mana Bars:")
        local healthColor = GetThemeColor("accent.success") or "A3BE8C"
        local manaColor = GetThemeColor("accent.info") or "81A1C1"
        local emptyColor = GetThemeColor("status.dim") or "4C566A"

        local function makeBar(filled, total, fillColor)
            local bar = ""
            for i = 1, total do
                if i <= filled then
                    bar = bar .. string.format("|cFF%s█|r", fillColor)
                else
                    bar = bar .. string.format("|cFF%s░|r", emptyColor)
                end
            end
            return bar
        end

        add("Health: " .. makeBar(17, 20, healthColor) .. " " .. Colorize("85%", "accent.success"))
        add("Mana:   " .. makeBar(12, 20, manaColor) .. " " .. Colorize("60%", "accent.info"))
        add("Cast:   " .. makeBar(8, 20, GetThemeColor("accent.warning") or "EBCB8B") .. " " .. Colorize("Fireball", "accent.warning"))

        add("")
        subheader("Quest Progress:")
        add("  " .. Colorize("Kill 10 Murlocs", "accent.warning") .. "     " .. makeBar(7, 10, healthColor) .. " 7/10")
        add("  " .. Colorize("Collect 5 Pearls", "accent.warning") .. "    " .. makeBar(5, 10, healthColor) .. " " .. Colorize("✓ Complete", "accent.success"))
        add("  " .. Colorize("Speak to Jaina", "accent.warning") .. "      " .. makeBar(0, 10, healthColor) .. " 0/1")

        add("")
        subheader("Volume Sliders:")
        add("Master:   " .. makeBar(16, 20, healthColor) .. " " .. Colorize("80%", "status.dim"))
        add("Music:    " .. makeBar(10, 20, healthColor) .. " " .. Colorize("50%", "status.dim"))
        add("SFX:      " .. makeBar(20, 20, healthColor) .. " " .. Colorize("100%", "status.dim"))
        add("Ambience: " .. makeBar(6, 20, healthColor) .. " " .. Colorize("30%", "status.dim"))

        -- =====================================================================
        header("STATUS INDICATORS")
        -- =====================================================================

        add(Colorize("●", "accent.success") .. " Online    " .. Colorize("●", "accent.error") .. " Offline   " .. Colorize("●", "accent.warning") .. " Away      " .. Colorize("●", "status.dim") .. " Unknown")
        add(Colorize("✓", "accent.success") .. " Success   " .. Colorize("✗", "accent.error") .. " Failed    " .. Colorize("⚠", "accent.warning") .. " Warning   " .. Colorize("ℹ", "accent.info") .. " Info")
        add(Colorize("▶", "accent.success") .. " Running   " .. Colorize("■", "accent.error") .. " Stopped   " .. Colorize("⏸", "accent.warning") .. " Paused    " .. Colorize("⟳", "accent.info") .. " Loading")

        add("")
        subheader("Guild Roster Sample:")
        add(Colorize("+", "accent.success") .. " Thrall          70 Shaman     " .. Colorize("Guild Master", "accent.warning"))
        add(Colorize("+", "accent.success") .. " Jaina           70 Mage       " .. Colorize("Officer", "accent.info"))
        add(Colorize("-", "status.dim") .. " " .. Colorize("Arthas", "status.dim") .. "          " .. Colorize("80 DK         Offline 3d", "status.dim"))
        add(Colorize("+", "accent.success") .. " Sylvanas        70 Hunter     Member")

        -- =====================================================================
        header("NERD FONT ICONS")
        -- =====================================================================

        subheader("File Types:")
        add("  Lua       Text      Config    Code")
        add("  Folder    Package   Git       Data")

        add("")
        subheader("UI Elements:")
        add("  Close     Menu      Search    Settings")
        add("  Home      User      Lock      Unlock")

        add("")
        subheader("Arrows & Navigation:")
        add("  Left      Right     Up        Down")
        add("  Back      Forward   Expand    Collapse")

        add("")
        subheader("Status & Feedback:")
        add("  Check     Cross     Warning   Info")
        add("  Star      Heart     Fire      Lightning")

        -- =====================================================================
        header("COLOR PALETTE (Nord Theme)")
        -- =====================================================================

        add(Colorize("████", "D8DEE9") .. " nord4  Primary Text     " .. Colorize("████", "ECEFF4") .. " nord6  Bright Text")
        add(Colorize("████", "4C566A") .. " nord3  Dim/Comments     " .. Colorize("████", "81A1C1") .. " nord9  Info/Labels")
        add("")
        add(Colorize("████", "A3BE8C") .. " nord14 Success/Green    " .. Colorize("████", "BF616A") .. " nord11 Error/Red")
        add(Colorize("████", "EBCB8B") .. " nord13 Warning/Yellow   " .. Colorize("████", "D08770") .. " nord12 Orange")
        add(Colorize("████", "88C0D0") .. " nord8  Primary/Cyan     " .. Colorize("████", "B48EAD") .. " nord15 Special/Purple")

        add("")
        subheader("Item Quality Colors:")
        add(Colorize("████ Poor", "9D9D9D") .. "   " .. Colorize("████ Common", "FFFFFF") .. "   " .. Colorize("████ Uncommon", "1EFF00") .. "   " .. Colorize("████ Rare", "0070FF"))
        add(Colorize("████ Epic", "A335EE") .. "   " .. Colorize("████ Legendary", "FF8000") .. "   " .. Colorize("████ Artifact", "E6CC80") .. "   " .. Colorize("████ Heirloom", "00CCFF"))

        -- =====================================================================
        header("MOCK COMMAND OUTPUT")
        -- =====================================================================

        subheader("$ bags | where quality > 1 | take 3")
        add("  " .. Colorize("[Staff of Jordan]", "A335EE") .. "          iLvl 35   Epic")
        add("  " .. Colorize("[Runecloth Bag]", "1EFF00") .. "            16 slot   Uncommon")
        add("  " .. Colorize("[Major Healing Potion]", "0070FF") .. " x5  Consumable Rare")
        add(Colorize("3 items", "status.dim"))

        add("")
        subheader("$ mounts | where isFlying | count")
        add(Colorize("42", "accent.success") .. Colorize(" flying mounts collected", "status.dim"))

        add("")
        subheader("$ watch $health")
        add(Colorize("─── Watch #1 ───", "accent.warning"))
        add("Health: " .. makeBar(17, 25, healthColor) .. " " .. Colorize("68%", "accent.success"))

        add("")
        subheader("$ ls /home")
        add(" /home")
        add("├──  scripts/")
        add("├──  macros/")
        add("├──  notes.txt")
        add("└──  config.lua")

        -- =====================================================================
        header("ALIGNMENT TEST")
        -- =====================================================================

        add("Pipe char:    |    |    |    |    |")
        add("Light box:    │    │    │    │    │")
        add("Heavy box:    ┃    ┃    ┃    ┃    ┃")
        add("Double box:   ║    ║    ║    ║    ║")
        add("")
        add("Numbers:      0123456789")
        add("Lowercase:    abcdefghijklmnopqrstuvwxyz")
        add("Uppercase:    ABCDEFGHIJKLMNOPQRSTUVWXYZ")
        add("Mixed:        AaBbCcDdEeFfGgHhIiJjKkLlMm")

        add("")
        add(Colorize("═══════════════════════════════════════════════════════════════", "status.dim"))
        add(Colorize("                    End of Font Test", "status.dim"))
        add(Colorize("═══════════════════════════════════════════════════════════════", "status.dim"))
        add("")

        return results
    end
})

-------------------------------------------------------------------------------
-- dice - Roll Dice
-------------------------------------------------------------------------------

-- Dice icons (Font Awesome)
local DICE_ICONS = {
    [1] = "",  -- U+EDEF fa-dice_one
    [2] = "",  -- U+EDF2 fa-dice_two
    [3] = "",  -- U+EDF1 fa-dice_three
    [4] = "",  -- U+EDEE fa-dice_four
    [5] = "",  -- U+EDED fa-dice_five
    [6] = "",  -- U+EDF0 fa-dice_six
}

C3.Command:Define({
    name = "dice",
    description = "Roll dice (1-10 six-sided dice)",
    category = "utility",
    acceptsInput = false,
    args = {
        {
            name = "count",
            required = false,
            description = "Number of dice to roll (1-10, default 1)",
            provider = function(partial, ctx)
                local suggestions = {}
                for i = 1, 10 do
                    suggestions[#suggestions + 1] = {
                        text = tostring(i),
                        description = "Roll " .. i .. " dice",
                    }
                end
                return C3.Intellisense.fuzzyFilter(suggestions, partial, function(item)
                    return item.text
                end)
            end
        }
    },
    flags = {
        verbose = { short = "v", description = "Show individual roll values" },
    },
    execute = function(ctx)
        local countArg = ctx:GetArg(1)
        local count = tonumber(countArg) or 1
        local verbose = ctx:GetFlag("verbose")

        -- Clamp to 1-10
        count = math.max(1, math.min(10, count))

        local results = {}
        local total = 0

        -- Roll each die
        for i = 1, count do
            local roll = math.random(1, 6)
            results[i] = roll
            total = total + roll
        end

        -- Build output with dice icons
        local diceStr = ""
        for i, roll in ipairs(results) do
            if i > 1 then
                diceStr = diceStr .. "  "
            end
            diceStr = diceStr .. "|cFFFFFFFF" .. DICE_ICONS[roll] .. "|r"
        end

        -- Check for special rolls
        local special = nil
        if count >= 2 then
            -- Check if all dice are the same (Yahtzee for 5+, or just "all same")
            local allSame = true
            for i = 2, count do
                if results[i] ~= results[1] then
                    allSame = false
                    break
                end
            end

            if allSame then
                if results[1] == 6 then
                    special = Colorize("  Perfect!", "accent.warning")
                elseif results[1] == 1 then
                    special = Colorize("  Snake eyes!", "accent.error")
                elseif count >= 5 then
                    special = Colorize("  Yahtzee!", "accent.warning")
                else
                    special = Colorize("  All " .. results[1] .. "s!", "accent.info")
                end
            end

            -- Check for straight (1-2-3-4-5 or 2-3-4-5-6)
            if not special and count >= 5 then
                local sorted = {}
                for i, v in ipairs(results) do sorted[i] = v end
                table.sort(sorted)

                local isStraight = true
                for i = 2, count do
                    if sorted[i] ~= sorted[i-1] + 1 then
                        isStraight = false
                        break
                    end
                end

                if isStraight then
                    if sorted[count] == 6 then
                        special = Colorize("  Large straight!", "accent.warning")
                    else
                        special = Colorize("  Straight!", "accent.info")
                    end
                end
            end

            -- Lucky 7 with 2 dice
            if not special and count == 2 and total == 7 then
                special = Colorize("  Lucky 7!", "accent.success")
            end
        end

        -- Format output
        local output
        if verbose and count > 1 then
            -- Verbose: show individual results and total
            local numsStr = ""
            for i, roll in ipairs(results) do
                if i > 1 then
                    numsStr = numsStr .. " + "
                end
                numsStr = numsStr .. tostring(roll)
            end
            output = diceStr .. " " .. Colorize(numsStr .. " = " .. total, "accent.success")
        else
            -- Default: just icons and total
            output = diceStr .. " = " .. Colorize(tostring(total), "accent.success")
        end

        if special then
            output = output .. special
        end

        return ctx:Text(output)
    end
})

-------------------------------------------------------------------------------
-- reload - Reload UI
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "reload",
    description = "Reload the UI",
    category = "utility",
    acceptsInput = false,
    aliases = { "rl" },
    execute = function(ctx)
        ReloadUI()
        return nil  -- Never returns
    end
})

return true
