local addonName, SinsToolkit = ...

if not C_AddOns.IsAddOnLoaded("ElvUI") or not _G.ElvUI then
    return
end

local E = unpack(_G.ElvUI)
local EP = E.Libs.EP
local Plugin = E:NewModule("SinsToolkit", "AceHook-3.0", "AceEvent-3.0")

local function EnsureDB()
    if SinsToolkit.EnsureDB then
        SinsToolkit.EnsureDB()
    end
end

local function GetToolkitValue(key)
    EnsureDB()
    return SinsToolkitDB and SinsToolkitDB[key]
end

local function SetToolkitValue(key, value)
    EnsureDB()
    SinsToolkitDB[key] = value
    if SinsToolkit.TriggerUpdate then
        SinsToolkit.TriggerUpdate(key)
    end
end

local uiErrorListCache
local function GetUIErrorList()
    if not uiErrorListCache then
        uiErrorListCache = {}
        if GetGameMessageInfo then
            for i = 1, 1600 do
                local stringID = GetGameMessageInfo(i)
                local text = stringID and _G[stringID]
                if stringID and stringID:match("^ERR_") and text and text ~= "" then
                    uiErrorListCache[#uiErrorListCache + 1] = { key = stringID, text = text, searchText = (stringID .. " " .. text):lower() }
                end
            end
        end
        table.sort(uiErrorListCache, function(a, b) return a.text < b.text end)
    end
    return uiErrorListCache
end

local errorSearchString = ""

local currentExportName = ""
local currentExportString = ""
local profileVaultBackupName = ""
local selectedProfileVaultBackup = ""
local cvarSearchString = ""
local cvarModifiedOnly = false
local selectedCVar = ""
local cvarPendingValue = ""
local uiErrorGroupCache = {}

local GEAR_SLOT_VALUES = {
    [INVSLOT_HEAD] = HEADSLOT,
    [INVSLOT_NECK] = NECKSLOT,
    [INVSLOT_SHOULDER] = SHOULDERSLOT,
    [INVSLOT_BACK] = BACKSLOT,
    [INVSLOT_CHEST] = CHESTSLOT,
    [INVSLOT_WRIST] = WRISTSLOT,
    [INVSLOT_HAND] = HANDSSLOT,
    [INVSLOT_WAIST] = WAISTSLOT,
    [INVSLOT_LEGS] = LEGSSLOT,
    [INVSLOT_FEET] = FEETSLOT,
    [INVSLOT_FINGER1] = FINGER0SLOT,
    [INVSLOT_FINGER2] = FINGER1SLOT,
    [INVSLOT_TRINKET1] = TRINKET0SLOT,
    [INVSLOT_TRINKET2] = TRINKET1SLOT,
    [INVSLOT_MAINHAND] = MAINHANDSLOT,
    [INVSLOT_OFFHAND] = SECONDARYHANDSLOT,
}

local function SanitizeExportString(value)
    return (value or ""):gsub("[\r\n\t]+", "")
end

local function GetProfileVaultBackupValues()
    EnsureDB()
    local values = { [""] = "None" }
    if not SinsToolkit.ProfileVault then return values end

    for _, backup in ipairs(SinsToolkit.ProfileVault:GetBackups()) do
        local label = ("%s - %s - %s"):format(
            backup.name or "Backup",
            backup.className or backup.classToken or "Unknown",
            backup.createdAt or "Unknown date"
        )
        values[backup.id] = label
    end
    return values
end

local function GetSelectedProfileVaultBackup()
    EnsureDB()
    return selectedProfileVaultBackup ~= ""
        and SinsToolkitDB.profileVaultBackups
        and SinsToolkitDB.profileVaultBackups[selectedProfileVaultBackup]
        or nil
end

local function GetProfileVaultSummary()
    local backup = GetSelectedProfileVaultBackup()
    if not backup then
        return "Select a saved backup to view its details."
    end

    local actionCount = backup.actions and #backup.actions or 0
    local accountMacroCount = backup.macros and backup.macros.account and #backup.macros.account or 0
    local characterMacroCount = backup.macros and backup.macros.character and #backup.macros.character or 0
    local bindingCount = backup.bindings and #backup.bindings or 0

    return ("Backup: %s\nCharacter: %s\nRealm: %s\nClass: %s\nDate: %s\n\nIncludes: %d action slots, %d account macros, %d character macros, %d key bindings."):format(
        backup.name or "Backup",
        backup.character or "Unknown",
        backup.realm or "Unknown",
        backup.className or backup.classToken or "Unknown",
        backup.createdAt or "Unknown",
        actionCount,
        accountMacroCount,
        characterMacroCount,
        bindingCount
    )
end

local function GetCVarValues()
    if not SinsToolkit.CVars then
        return { [""] = "CVar browser unavailable" }
    end
    return SinsToolkit.CVars:GetValues(cvarSearchString, cvarModifiedOnly)
end

local function GetSelectedCVarInfo()
    if not SinsToolkit.CVars or selectedCVar == "" then
        return nil
    end
    return SinsToolkit.CVars:GetInfo(selectedCVar)
end

local function GetSelectedCVarSummary()
    if not SinsToolkit.CVars then
        return "The CVar browser module is not loaded."
    end
    return SinsToolkit.CVars:GetSummary(selectedCVar)
end

local function UpdateErrorToggles()
    if not E.Options.args.sinsToolkit then return end
    local args = E.Options.args.sinsToolkit.args.uiErrors.args
    
    for k in pairs(args) do
        if type(k) == "string" and k:match("^ERR_") then args[k] = nil end
    end
    
    local search = (errorSearchString or ""):lower()
    local blocked = SinsToolkitDB and SinsToolkitDB.blockedUIErrors or {}
    local sorted = uiErrorGroupCache[search]

    if not sorted then
        local groupedByText = {}
        sorted = {}
        for _, err in ipairs(GetUIErrorList()) do
            if search == "" or err.searchText:find(search, 1, true) then
                local grouped = groupedByText[err.text]
                if grouped then
                    grouped.keys[#grouped.keys + 1] = err.key
                    grouped.searchText = grouped.searchText .. " " .. err.searchText
                else
                    grouped = {
                        key = err.key,
                        keys = { err.key },
                        text = err.text,
                        searchText = err.searchText,
                    }
                    groupedByText[err.text] = grouped
                    sorted[#sorted + 1] = grouped
                end
            end
        end

        table.sort(sorted, function(a, b)
            return a.text < b.text
        end)
        uiErrorGroupCache[search] = sorted
    end
    
    table.sort(sorted, function(a, b)
        local function IsAnyBlocked(err)
            for _, key in ipairs(err.keys) do
                if blocked[key] == true then return true end
            end
            return false
        end

        local aBlocked = IsAnyBlocked(a)
        local bBlocked = IsAnyBlocked(b)
        if aBlocked ~= bBlocked then return aBlocked end
        return a.text < b.text
    end)
    
    for i, err in ipairs(sorted) do
        args[err.key] = {
            type = "toggle",
            name = err.text,
            order = 1000 + i,
            width = "double",
            get = function()
                EnsureDB()
                if not SinsToolkitDB.blockedUIErrors then return false end
                for _, key in ipairs(err.keys) do
                    if SinsToolkitDB.blockedUIErrors[key] then return true end
                end
                return false
            end,
            set = function(_, value)
                EnsureDB()
                if not SinsToolkitDB.blockedUIErrors then SinsToolkitDB.blockedUIErrors = {} end
                for _, key in ipairs(err.keys) do
                    SinsToolkitDB.blockedUIErrors[key] = value or nil
                end
                if SinsToolkit.TriggerUpdate then SinsToolkit.TriggerUpdate("blockedUIErrors") end
                UpdateErrorToggles()
                if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
            end,
        }
    end
end

if C_Timer then
    C_Timer.After(1, GetUIErrorList)
    C_Timer.After(5, function()
        if SinsToolkit.CVars then
            SinsToolkit.CVars:GetValues("", false)
        end
    end)
end

local BRAND_NAME = "|cffb868dcSin's|r |cffe8b4ecToolkit QOL|r"
local BRAND_SUBTITLE = "|cffbca7d6Compact ElvUI quality-of-life tools with a dark violet theme.|r"
local BRAND_LOGO = "Interface\\AddOns\\SinsToolkit\\Media\\Logo.tga"
local BRAND_BANNER = "Interface\\AddOns\\SinsToolkit\\Media\\Banner.tga"

local function GradientText(text)
    local startColor = { 106, 47, 153 }
    local endColor = { 255, 255, 255 }
    local out, visibleIndex = {}, 0
    local visibleCount = 0
    for i = 1, #text do
        if text:sub(i, i) ~= " " then
            visibleCount = visibleCount + 1
        end
    end

    for i = 1, #text do
        local char = text:sub(i, i)
        if char == " " then
            out[#out + 1] = char
        else
            visibleIndex = visibleIndex + 1
            local t = visibleCount > 1 and ((visibleIndex - 1) / (visibleCount - 1)) or 1
            local r = math.floor(startColor[1] + ((endColor[1] - startColor[1]) * t) + 0.5)
            local g = math.floor(startColor[2] + ((endColor[2] - startColor[2]) * t) + 0.5)
            local b = math.floor(startColor[3] + ((endColor[3] - startColor[3]) * t) + 0.5)
            out[#out + 1] = ("|cff%02x%02x%02x%s|r"):format(r, g, b, char)
        end
    end
    return table.concat(out)
end

local function PageIntro(title, body)
    return {
        type = "description",
        name = "|cffb868dc" .. title .. "|r\n|cffbca7d6" .. body .. "|r",
        fontSize = "medium",
        order = 0,
    }
end

function Plugin:InsertOptions()
    if not E.Options or not E.Options.args then return end

    E.Options.args.sinsToolkit = {
        type = "group",
        name = "|T" .. BRAND_LOGO .. ":16:16|t " .. GradientText("Sin's Toolkit QOL"),
        desc = "Quality-of-life tools integrated as an ElvUI plugin.",
        order = 1000,
        childGroups = "tree",
        args = {
            info = {
                type = "group",
                name = GradientText("Information"),
                order = 1,
                args = {
                    brand = {
                        type = "description",
                        name = " ",
                        image = BRAND_BANNER,
                        imageWidth = 700,
                        imageHeight = 280,
                        fontSize = "medium",
                        order = 0,
                    },
                    header = {
                        type = "description",
                        name = "|cff9b59b6Sin's Toolkit QOL Feature Overview|r\n\nWelcome to Sin's Toolkit QOL, a fan-made ElvUI plugin built to keep multiple small quality-of-life tools in one clean place. The goal is simple: reduce addon clutter, keep useful options easy to find, and keep expanding the toolkit as World of Warcraft changes.\n\n- |cffd8a84aGear Scanner:|r Scans bag gear automatically or with |cff3498db/stk scan|r, compares upgrades using Pawn when installed or ClassCodex fallback priorities, respects class armor types, supports locked slots, tooltip scores, won't-equip reasons, upgrade explanations, unbound-item protection, max-level disable, silent mode, and configurable loot-roll/soulbind confirmation options. Inspired by AutoGear.\n\n- |cffd8a84aUI Tweaks:|r Filters selected red UI error messages, keeps checked blocked messages sorted to the top, hides the Druid shapeshift-form error only while Auto Unshift is enabled, and adds No Auto Close behavior for supported Blizzard panels. Inspired by NoAutoClose.\n\n- |cffd8a84aCVars:|r Browse live client CVars, search by name or description, compare current/default values, filter modified-only settings, edit supported CVars, reset supported CVars, and show modified CVars in |cff55ff55green|r. Inspired by AdvancedInterfaceOptions.\n\n- |cffd8a84aAutomation:|r Includes Auto Gossip with a bypass modifier, Auto Unshift for Feral/Guardian Druids as its own separate option, and Druid Auto Flight Form to help move from ground Travel Form toward Flight Form in safe outdoor flyable areas. Auto Unshift can be helpful if you use GSE, also known as Gnome Sequencer.\n\n- |cffd8a84aProfessions & Lookups:|r Adds a secure profession item hotkey overlay for milling, prospecting, disenchanting, and opening, plus copyable Wowhead, Armory, and Raider.IO lookup popups from supported tooltips. Inspired by Molinari and Wowhead Quick Link.\n\n- |cffd8a84aIdentifier:|r Adds |cff3498db/stkid|r tools to identify frames under your cursor, toggle hover-identify mode, copy frame details, switch simple/detail output, and inspect global frames by name. Inspired by Tweak Identifier.\n\n- |cffd8a84aTeleport Hub:|r Adds |cff3498db/stktp|r one-button access to known teleports, portals, hearthstones, and teleport toys with a searchable secure-button panel and optional minimap button. Inspired by OneButton Teleport.\n\n- |cffd8a84aProfile Vault:|r Saves and restores character setup backups including action bars, account macros, character macros, keybinds, character name, realm, class, backup date, and custom backup name. Inspired by MySlot-style profile backups.\n\n- |cffd8a84aExport Strings:|r Stores useful import/export text such as UI layouts, WeakAuras, talent strings, addon profile strings, and other notes you want to keep handy. Created for Sin's Toolkit QOL.\n\n|cffd8a84aRequirements:|r ElvUI is required. Pawn and ClassCodex are optional Gear Scanner providers. GSE is optional and only useful if you use Auto Unshift with Gnome Sequencer sequences.\n\n|cffd8a84aAvailable Commands:|r\n- |cff3498db/stk status|r - Prints the active status of toolkit features.\n- |cff3498db/stk scan|r - Forces a manual Gear Scanner scan.\n- |cff3498db/stkid|r - Identifies the frame under your cursor.\n- |cff3498db/stkid on|r / |cff3498dboff|r - Toggles Identifier hover mode.\n- |cff3498db/stkid copy|r - Copies the last Identifier result.\n- |cff3498db/stktp|r - Opens or closes Teleport Hub.\n- |cff3498db/stktp minimap|r - Shows or hides the Teleport Hub minimap button.\n- |cff3498db/sau status|r - Checks Auto Unshift for Druids.\n- |cff3498db/sau toggle|r - Toggles Auto Unshift for Druids.",
                        fontSize = "medium",
                        order = 1,
                    },
                },
            },
            gear = {
                type = "group",
                name = GradientText("Gear Scanner"),
                order = 4,
                args = {
                    intro = PageIntro("Gear Scanner", "Automatic and manual gear scanning powered by Pawn when installed, with ClassCodex stat priorities as fallback."),
                    enabled = {
                        type = "toggle",
                        name = "Enable Gear Scanner",
                        desc = "Master switch for Gear Scanner. When enabled, Sin's Toolkit can scan bag items, compare them against your equipped gear, and equip upgrades using Pawn when installed or ClassCodex fallback scoring.",
                        order = 1,
                        disabled = function() return SinsToolkit.IsGearScannerProviderAvailable and not SinsToolkit.IsGearScannerProviderAvailable() end,
                        get = function() return GetToolkitValue("gearScannerEnabled") end,
                        set = function(_, value) SetToolkitValue("gearScannerEnabled", value) end,
                    },
                    maxLevel = {
                        type = "toggle",
                        name = "Disable at Max Level",
                        desc = "Turns Gear Scanner off automatically when your character reaches the current expansion level cap. This keeps leveling automation from interfering with max-level gearing decisions.",
                        order = 2,
                        get = function() return GetToolkitValue("gearScannerMaxLevelDisable") end,
                        set = function(_, value) SetToolkitValue("gearScannerMaxLevelDisable", value) end,
                    },
                    silent = {
                        type = "toggle",
                        name = "Silent Mode",
                        desc = "Suppresses Gear Scanner chat messages when items are equipped, scans are run, or loot-roll actions are taken.",
                        order = 3,
                        get = function() return GetToolkitValue("gearScannerSilent") end,
                        set = function(_, value) SetToolkitValue("gearScannerSilent", value) end,
                    },
                    scan = {
                        type = "execute",
                        name = "Force Gear Scan",
                        desc = "Runs an immediate bag scan using the current Gear Scanner settings. This is the same action as /stk scan.",
                        order = 4,
                        func = function()
                            if SinsToolkit.ForceGearScan then
                                SinsToolkit.ForceGearScan()
                            end
                        end,
                    },
                    behaviorHeader = {
                        type = "header",
                        name = "Behavior",
                        order = 5,
                    },
                    scanOnLoot = {
                        type = "toggle",
                        name = "Scan When Bags Change",
                        desc = "Automatically scans your bags after loot or bag changes. Turn this off if you only want Gear Scanner to run when you click Force Gear Scan or type /stk scan.",
                        order = 6,
                        get = function() return GetToolkitValue("gearScannerScanOnLoot") end,
                        set = function(_, value) SetToolkitValue("gearScannerScanOnLoot", value) end,
                    },
                    allowUnbound = {
                        type = "toggle",
                        name = "Allow Unbound Upgrades",
                        desc = "Allows Gear Scanner to equip unbound upgrades from your bags. Leave this off if you want BoE items left untouched for selling, trading, mailing, or auctioning.",
                        order = 7,
                        get = function() return GetToolkitValue("gearScannerAllowUnbound") end,
                        set = function(_, value) SetToolkitValue("gearScannerAllowUnbound", value) end,
                    },
                    minimumUpgrade = {
                        type = "range",
                        name = "Minimum Upgrade Score",
                        desc = "Requires a new item to beat your equipped item by at least this many score points before it is equipped. Raise this if you only want larger upgrades equipped.",
                        order = 8,
                        min = 0,
                        max = 500,
                        step = 5,
                        get = function() return GetToolkitValue("gearScannerMinimumUpgrade") or 0 end,
                        set = function(_, value) SetToolkitValue("gearScannerMinimumUpgrade", value) end,
                    },
                    pawnHeader = {
                        type = "header",
                        name = "Pawn Preference",
                        order = 9,
                    },
                    usePawn = {
                        type = "toggle",
                        name = "Use Pawn When Installed",
                        desc = "When Pawn is installed, Sin's Toolkit asks Pawn's own upgrade advisor first. If Pawn is missing, disabled, throttled, or cannot evaluate an item, Gear Scanner falls back to ClassCodex stat priorities.",
                        order = 9.1,
                        get = function() return GetToolkitValue("gearScannerUsePawn") end,
                        set = function(_, value) SetToolkitValue("gearScannerUsePawn", value) end,
                    },
                    lootRollHeader = {
                        type = "header",
                        name = "Loot Rolls",
                        order = 9.2,
                    },
                    autoRollGreensBopBlues = {
                        type = "toggle",
                        name = "Automatically Roll on Greens and BoP Blues",
                        desc = "Automatically handles green gear and blue bind-on-pickup gear loot rolls. If an item is an upgrade, Sin's Toolkit rolls Need unless Never Auto-Need is enabled. Non-upgrades roll Greed when Greed is available.",
                        order = 9.3,
                        get = function() return GetToolkitValue("gearScannerAutoRollGreensBopBlues") end,
                        set = function(_, value) SetToolkitValue("gearScannerAutoRollGreensBopBlues", value) end,
                    },
                    autoRollBoeBlues = {
                        type = "toggle",
                        name = "Automatically Roll on BoE Blues",
                        desc = "Automatically handles blue bind-on-equip gear loot rolls. Use this carefully if you prefer to sell, trade, or auction BoE blue items instead of treating them like leveling upgrades.",
                        order = 9.4,
                        get = function() return GetToolkitValue("gearScannerAutoRollBoeBlues") end,
                        set = function(_, value) SetToolkitValue("gearScannerAutoRollBoeBlues", value) end,
                    },
                    autoRollEpics = {
                        type = "toggle",
                        name = "Automatically Roll on Epics",
                        desc = "Automatically handles epic gear loot rolls. This is off by default because many players want manual control over epic loot.",
                        order = 9.5,
                        get = function() return GetToolkitValue("gearScannerAutoRollEpics") end,
                        set = function(_, value) SetToolkitValue("gearScannerAutoRollEpics", value) end,
                    },
                    rollNonGearLoot = {
                        type = "toggle",
                        name = "Roll on Non-Gear Loot",
                        desc = "Automatically rolls Greed on handled non-gear loot. This does not evaluate upgrades because the item is not equippable gear.",
                        order = 9.6,
                        get = function() return GetToolkitValue("gearScannerRollNonGearLoot") end,
                        set = function(_, value) SetToolkitValue("gearScannerRollNonGearLoot", value) end,
                    },
                    neverAutoNeed = {
                        type = "toggle",
                        name = "Never Auto-Need; Only Greed",
                        desc = "Prevents Sin's Toolkit from rolling Need automatically. When this is enabled, upgrade candidates will roll Greed instead of Need when Greed is available.",
                        order = 9.7,
                        get = function() return GetToolkitValue("gearScannerNeverAutoNeed") end,
                        set = function(_, value) SetToolkitValue("gearScannerNeverAutoNeed", value) end,
                    },
                    bindingHeader = {
                        type = "header",
                        name = "Soulbind Confirmation",
                        order = 9.8,
                    },
                    autoConfirmBindOther = {
                        type = "toggle",
                        name = "Confirm Soulbinding for Non-Blue/Non-Epic Gear",
                        desc = "Automatically confirms the soulbind popup when Gear Scanner equips an upgrade below blue quality. Leave this off if you want to manually confirm binding items.",
                        order = 9.9,
                        get = function() return GetToolkitValue("gearScannerAutoConfirmBindOther") end,
                        set = function(_, value) SetToolkitValue("gearScannerAutoConfirmBindOther", value) end,
                    },
                    autoConfirmBindBlues = {
                        type = "toggle",
                        name = "Confirm Soulbinding for Blues",
                        desc = "Automatically confirms the soulbind popup when Gear Scanner equips a blue upgrade. Leave this off if you want manual control over binding blue items.",
                        order = 9.91,
                        get = function() return GetToolkitValue("gearScannerAutoConfirmBindBlues") end,
                        set = function(_, value) SetToolkitValue("gearScannerAutoConfirmBindBlues", value) end,
                    },
                    autoConfirmBindEpics = {
                        type = "toggle",
                        name = "Confirm Soulbinding for Epics",
                        desc = "Automatically confirms the soulbind popup when Gear Scanner equips an epic upgrade. This is off by default because epic items are usually worth reviewing manually.",
                        order = 9.92,
                        get = function() return GetToolkitValue("gearScannerAutoConfirmBindEpics") end,
                        set = function(_, value) SetToolkitValue("gearScannerAutoConfirmBindEpics", value) end,
                    },
                    tooltipsHeader = {
                        type = "header",
                        name = "Tooltips",
                        order = 10,
                    },
                    scoreTooltips = {
                        type = "toggle",
                        name = "Show Item Score",
                        desc = "Adds a Sin's Toolkit gear score line to item tooltips. When Use Pawn When Installed is enabled and Pawn is available, Pawn upgrade candidates are prioritized; otherwise scores use ClassCodex stat priorities.",
                        order = 11,
                        get = function() return GetToolkitValue("gearScannerScoreTooltips") end,
                        set = function(_, value) SetToolkitValue("gearScannerScoreTooltips", value) end,
                    },
                    reasonTooltips = {
                        type = "toggle",
                        name = "Show Won't-Equip Reasons",
                        desc = "Adds a tooltip note explaining why an item will or will not equip, such as not being an upgrade, being blocked by a locked slot, Pawn fallback behavior, or being an upgrade candidate.",
                        order = 12,
                        get = function() return GetToolkitValue("gearScannerReasonsTooltips") end,
                        set = function(_, value) SetToolkitValue("gearScannerReasonsTooltips", value) end,
                    },
                    lockHeader = {
                        type = "header",
                        name = "Locked Slots",
                        order = 20,
                    },
                    lockSlots = {
                        type = "toggle",
                        name = "Lock Gear Slots",
                        desc = "Enables the locked slot list below. Locked slots are still scored, but Gear Scanner will not replace items in those slots.",
                        order = 21,
                        get = function() return GetToolkitValue("gearScannerLockSlots") end,
                        set = function(_, value) SetToolkitValue("gearScannerLockSlots", value) end,
                    },
                    lockedSlots = {
                        type = "multiselect",
                        name = "Slots to Lock",
                        desc = "Choose the equipment slots Gear Scanner should never replace during automatic scans or manual /stk scan checks.",
                        order = 22,
                        values = GEAR_SLOT_VALUES,
                        disabled = function() return not GetToolkitValue("gearScannerLockSlots") end,
                        get = function(_, key)
                            EnsureDB()
                            return SinsToolkitDB.gearScannerLockedSlots and SinsToolkitDB.gearScannerLockedSlots[key] == true
                        end,
                        set = function(_, key, value)
                            EnsureDB()
                            SinsToolkitDB.gearScannerLockedSlots = SinsToolkitDB.gearScannerLockedSlots or {}
                            SinsToolkitDB.gearScannerLockedSlots[key] = value or nil
                        end,
                    },
                },
            },
            tweaks = {
                type = "group",
                name = GradientText("UI Tweaks"),
                order = 5,
                args = {
                    intro = PageIntro("UI Tweaks", "Small interface behavior changes that keep everyday panels and messages cleaner."),
                    panels = {
                        type = "toggle",
                        name = "No Auto Close Panels",
                        desc = "Uses Sin's Toolkit No Auto Close behavior so standard Blizzard UI panels can stay open together. Reload after disabling.",
                        order = 2,
                        get = function() return GetToolkitValue("keepPanelsOpen") end,
                        set = function(_, value) SetToolkitValue("keepPanelsOpen", value) end,
                    },
                },
            },
            uiErrors = {
                type = "group",
                name = GradientText("UI Errors"),
                order = 6,
                args = {
                    intro = PageIntro("UI Errors", "Choose which red UI error messages Sin's Toolkit should block."),
                    hideOutOfRangeErrors = {
                        type = "toggle",
                        name = "Enable UI Error Filtering",
                        desc = "Enable or disable the filtering of UI error messages entirely.",
                        order = 1,
                        get = function() return GetToolkitValue("hideOutOfRangeErrors") end,
                        set = function(_, value) SetToolkitValue("hideOutOfRangeErrors", value) end,
                    },
                    search = {
                        type = "input",
                        name = "Search Errors",
                        desc = "Filter the error list below. Text updates dynamically.",
                        order = 2,
                        width = "full",
                        get = function() return errorSearchString end,
                        set = function(_, value)
                            errorSearchString = value
                            UpdateErrorToggles()
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                        end,
                    },
                    listHeader = {
                        type = "header",
                        name = "Blocked Messages",
                        order = 999,
                    },
                },
            },
            cvars = {
                type = "group",
                name = GradientText("CVars"),
                order = 7,
                args = {
                    intro = {
                        type = "description",
                        name = "Search live client CVars, compare current values against Blizzard defaults, and edit selected values. Modified CVars appear |cff55ff55green|r in the selector. The list is validated through the WoW client at runtime so removed or renamed CVars are skipped instead of breaking the panel.",
                        order = 1,
                        fontSize = "medium",
                    },
                    search = {
                        type = "input",
                        name = "Search CVars",
                        desc = "Search by CVar name, category, or client-provided description.",
                        order = 2,
                        width = "full",
                        get = function() return cvarSearchString end,
                        set = function(_, value)
                            cvarSearchString = value or ""
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                        end,
                    },
                    modifiedOnly = {
                        type = "toggle",
                        name = "Show Modified Only",
                        desc = "Only show CVars where the current value differs from the client default.",
                        order = 3,
                        get = function() return cvarModifiedOnly end,
                        set = function(_, value)
                            cvarModifiedOnly = value
                            if selectedCVar ~= "" and SinsToolkit.CVars then
                                local values = SinsToolkit.CVars:GetValues(cvarSearchString, cvarModifiedOnly)
                                if not values[selectedCVar] then
                                    selectedCVar = ""
                                    cvarPendingValue = ""
                                end
                            end
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                        end,
                    },
                    refresh = {
                        type = "execute",
                        name = "Refresh List",
                        desc = "Refresh the CVar list and current/default values from the WoW client.",
                        order = 4,
                        func = function()
                            if SinsToolkit.CVars then
                                SinsToolkit.CVars:Refresh()
                                local info = GetSelectedCVarInfo()
                                cvarPendingValue = info and tostring(info.value or "") or ""
                            end
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                        end,
                    },
                    selector = {
                        type = "select",
                        name = "CVar",
                        desc = "Choose a CVar to inspect or edit.",
                        order = 5,
                        width = "full",
                        values = GetCVarValues,
                        get = function() return selectedCVar end,
                        set = function(_, value)
                            selectedCVar = value or ""
                            local info = GetSelectedCVarInfo()
                            cvarPendingValue = info and tostring(info.value or "") or ""
                        end,
                    },
                    details = {
                        type = "description",
                        name = GetSelectedCVarSummary,
                        order = 6,
                        fontSize = "medium",
                    },
                    newValue = {
                        type = "input",
                        name = "New Value",
                        desc = "Enter the value to apply to the selected CVar.",
                        order = 7,
                        width = "full",
                        disabled = function()
                            local info = GetSelectedCVarInfo()
                            return not info or info.readOnly or info.locked
                        end,
                        get = function() return cvarPendingValue end,
                        set = function(_, value) cvarPendingValue = value or "" end,
                    },
                    apply = {
                        type = "execute",
                        name = "Apply Value",
                        desc = "Apply the new value to the selected CVar.",
                        order = 8,
                        disabled = function()
                            local info = GetSelectedCVarInfo()
                            return not info or info.readOnly or info.locked or cvarPendingValue == ""
                        end,
                        func = function()
                            if not SinsToolkit.CVars then return end
                            local ok, message = SinsToolkit.CVars:SetValue(selectedCVar, cvarPendingValue)
                            local info = GetSelectedCVarInfo()
                            cvarPendingValue = info and tostring(info.value or "") or cvarPendingValue
                            print("|cff9b59b6[Sin's Toolkit]|r " .. (message or (ok and "CVar updated." or "CVar update failed.")))
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                        end,
                    },
                    reset = {
                        type = "execute",
                        name = "Reset to Default",
                        desc = "Set the selected CVar back to the default value reported by the WoW client.",
                        order = 9,
                        disabled = function()
                            local info = GetSelectedCVarInfo()
                            return not info or info.readOnly or info.locked
                        end,
                        func = function()
                            if not SinsToolkit.CVars then return end
                            local ok, message = SinsToolkit.CVars:ResetToDefault(selectedCVar)
                            local info = GetSelectedCVarInfo()
                            cvarPendingValue = info and tostring(info.value or "") or ""
                            print("|cff9b59b6[Sin's Toolkit]|r " .. (message or (ok and "CVar reset." or "CVar reset failed.")))
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                        end,
                    },
                },
            },
            automation = {
                type = "group",
                name = GradientText("Automation"),
                order = 8,
                args = {
                    intro = PageIntro("Automation", "Optional helpers for repeated interaction and Druid form handling."),
                    automateGossip = {
                        type = "toggle",
                        name = "Automate Gossip",
                        desc = "Automatically selects safe single-option gossip while avoiding city direction, trainer, bank, auction house, and storyteller-style gossip.",
                        order = 1,
                        get = function() return GetToolkitValue("automateGossip") end,
                        set = function(_, value) SetToolkitValue("automateGossip", value) end,
                    },
                    automateGossipModifier = {
                        type = "select",
                        name = "Gossip Bypass Modifier",
                        desc = "Hold this modifier key to temporarily bypass auto-gossip.",
                        order = 2,
                        values = { NONE = "None", SHIFT = "Shift", CTRL = "Ctrl", ALT = "Alt" },
                        get = function() return GetToolkitValue("automateGossipModifier") or "SHIFT" end,
                        set = function(_, value) SetToolkitValue("automateGossipModifier", value) end,
                    },
                    gseAutoUnshift = {
                        type = "toggle",
                        name = "Auto Unshift for Druids",
                        desc = "Disables auto-unshift in combat for Feral/Guardian Druids. This can be helpful if you use GSE, also known as Gnome Sequencer. While enabled, Sin's Toolkit hides the SPELL_FAILED_NOT_SHAPESHIFT UI error. When unchecked, that specific error returns to Blizzard's default display behavior. Requires GSE.",
                        order = 3,
                        disabled = function() return SinsToolkit.IsGSEAvailable and not SinsToolkit.IsGSEAvailable() end,
                        get = function() return GetToolkitValue("gseAutoUnshift") end,
                        set = function(_, value) SetToolkitValue("gseAutoUnshift", value) end,
                    },
                    druidAutoFlightForm = {
                        type = "toggle",
                        name = "Druid Auto Flight Form",
                        desc = "When you are a Druid in ground Travel Form and move into an outdoor flyable area, Sin's Toolkit QOL cancels ground Travel Form so the client can move you into Flight Form on your next travel-form action. This is separate from Auto Unshift for Druids and never runs in combat, instances, challenge modes, while flying, or while submerged.",
                        order = 4,
                        disabled = function()
                            local _, classToken = UnitClass("player")
                            return classToken ~= "DRUID"
                        end,
                        get = function() return GetToolkitValue("druidAutoFlightForm") end,
                        set = function(_, value) SetToolkitValue("druidAutoFlightForm", value) end,
                    },
                },
            },
            professions = {
                type = "group",
                name = GradientText("Professions & Lookups"),
                order = 9,
                args = {
                    intro = PageIntro("Professions & Lookups", "Modifier tools for profession items and quick copyable web lookups."),
                    professionItemHotkeyEnabled = {
                        type = "toggle",
                        name = "Profession Item Hotkey",
                        desc = "Shows a secure modifier-click overlay on eligible profession items.",
                        order = 1,
                        get = function() return GetToolkitValue("professionItemHotkeyEnabled") end,
                        set = function(_, value) SetToolkitValue("professionItemHotkeyEnabled", value) end,
                    },
                    professionItemHotkeyModifier = {
                        type = "select",
                        name = "Modifier Key",
                        desc = "The modifier key to hold to show the profession item overlay.",
                        order = 2,
                        values = { ALT = "Alt", CTRL = "Alt + Ctrl", SHIFT = "Alt + Shift" },
                        get = function() return GetToolkitValue("professionItemHotkeyModifier") or "ALT" end,
                        set = function(_, value) SetToolkitValue("professionItemHotkeyModifier", value) end,
                    },
                    wowheadSearchEnabled = {
                        type = "toggle",
                        name = "Wowhead Quick Link",
                        desc = "Mouse over supported UI elements and press Ctrl+C to open a copyable Wowhead, Armory, or Raider.IO link.",
                        order = 3,
                        get = function() return GetToolkitValue("wowheadSearchEnabled") end,
                        set = function(_, value) SetToolkitValue("wowheadSearchEnabled", value) end,
                    },
                },
            },
            identifier = {
                type = "group",
                name = GradientText("Identifier"),
                order = 10,
                args = {
                    intro = PageIntro("Identifier", "Inspect frames under your cursor, copy frame details, and look up global frame names."),
                    enabled = {
                        type = "toggle",
                        name = "Enable Hover Identifier",
                        desc = "Shows a small identifier tooltip while you move your cursor over UI frames. Use /stkid for a one-time identification without leaving hover mode enabled.",
                        order = 1,
                        get = function() return GetToolkitValue("identifierEnabled") end,
                        set = function(_, value) SetToolkitValue("identifierEnabled", value) end,
                    },
                    tooltip = {
                        type = "toggle",
                        name = "Show Identifier Tooltip",
                        desc = "Displays the current frame details near your cursor while hover identifier mode is enabled.",
                        order = 2,
                        get = function() return GetToolkitValue("identifierTooltip") end,
                        set = function(_, value) SetToolkitValue("identifierTooltip", value) end,
                    },
                    detailed = {
                        type = "toggle",
                        name = "Detailed Output",
                        desc = "Includes frame size, strata, level, alpha, point, and source-location details when the client exposes them.",
                        order = 3,
                        get = function() return GetToolkitValue("identifierDetailed") end,
                        set = function(_, value) SetToolkitValue("identifierDetailed", value) end,
                    },
                    identifyNow = {
                        type = "execute",
                        name = "Identify Cursor Frame",
                        desc = "Prints information about the frame currently under your cursor. Same as /stkid.",
                        order = 4,
                        func = function()
                            if SinsToolkit.Identifier then SinsToolkit.Identifier:IdentifyNow() end
                        end,
                    },
                    copyLast = {
                        type = "execute",
                        name = "Copy Last Result",
                        desc = "Opens a copyable popup for the last identified frame.",
                        order = 5,
                        func = function()
                            if SinsToolkit.Identifier then SinsToolkit.Identifier:CopyLast() end
                        end,
                    },
                    commands = {
                        type = "description",
                        name = "|cffd8a84aCommands:|r\n|cff3498db/stkid|r - Identify the frame under your cursor.\n|cff3498db/stkid on|r / |cff3498dboff|r - Enable or disable hover mode.\n|cff3498db/stkid copy|r - Copy the last result.\n|cff3498db/stkid name <FrameName>|r - Identify a global frame by name.\n|cff3498db/stkid simple|r / |cff3498dbdetail|r - Change output detail.",
                        fontSize = "medium",
                        order = 6,
                    },
                },
            },
            bugCatcher = {
                type = "group",
                name = GradientText("Bug Catcher"),
                order = 11,
                args = {
                    intro = PageIntro("Bug Catcher", "Capture Lua errors into a compact BugSack-style window with a minimap button. For testing, this can run alongside !BugGrabber. If BugSack is loaded, Sin's Toolkit disables this plugin version because BugSack owns the display window."),
                    enabled = {
                        type = "toggle",
                        name = "Enable Bug Catcher",
                        desc = "Captures Lua errors, protected-action errors, and Lua warnings inside Sin's Toolkit. This can run alongside !BugGrabber for testing, but is disabled when BugSack is loaded.",
                        order = 1,
                        get = function() return GetToolkitValue("bugCatcherEnabled") end,
                        set = function(_, value) SetToolkitValue("bugCatcherEnabled", value) end,
                    },
                    minimap = {
                        type = "toggle",
                        name = "Show Minimap Button",
                        desc = "Shows the Bug Catcher minimap button. Click to show the last error, Shift-click to reload UI, Alt-click to clear errors, Right-click to hide the button.",
                        order = 2,
                        get = function() return GetToolkitValue("bugCatcherMinimap") end,
                        set = function(_, value) SetToolkitValue("bugCatcherMinimap", value) end,
                    },
                    maxErrors = {
                        type = "range",
                        name = "Stored Error Limit",
                        desc = "Maximum number of Lua errors Sin's Toolkit stores in its own saved variables.",
                        order = 3,
                        min = 25,
                        max = 250,
                        step = 25,
                        get = function() return GetToolkitValue("bugCatcherMaxErrors") or 100 end,
                        set = function(_, value) SetToolkitValue("bugCatcherMaxErrors", value) end,
                    },
                    sound = {
                        type = "select",
                        name = "Error Sound",
                        desc = "Choose the sound Sin's Toolkit plays when Bug Catcher captures a Lua error.",
                        order = 4,
                        values = {
                            NONE = "None",
                            RAID_WARNING = "Raid Warning",
                            ALARM = "Alarm",
                            WHISPER = "Whisper",
                            READY_CHECK = "Ready Check",
                            BUGSACK_FATALITY = "BugSack: Fatality",
                        },
                        get = function() return GetToolkitValue("bugCatcherSound") or "NONE" end,
                        set = function(_, value) SetToolkitValue("bugCatcherSound", value) end,
                    },
                    open = {
                        type = "execute",
                        name = "Open Last Error",
                        desc = "Opens the Bug Catcher window and displays the most recent captured error.",
                        order = 5,
                        func = function()
                            if SinsToolkit.BugCatcher then SinsToolkit.BugCatcher:ShowLast() end
                        end,
                    },
                    testSound = {
                        type = "execute",
                        name = "Test Error Sound",
                        desc = "Plays the currently selected Bug Catcher sound. If BugSack: Fatality is selected but BugSack media is not available, Sin's Toolkit uses a built-in Blizzard alert sound.",
                        order = 6,
                        func = function()
                            if SinsToolkit.BugCatcher and SinsToolkit.BugCatcher.TestSound then
                                SinsToolkit.BugCatcher:TestSound()
                            end
                        end,
                    },
                    clear = {
                        type = "execute",
                        name = "Clear Errors",
                        desc = "Clears captured errors from Sin's Toolkit.",
                        order = 7,
                        func = function()
                            if SinsToolkit.BugCatcher then SinsToolkit.BugCatcher:Reset() end
                        end,
                    },
                    commands = {
                        type = "description",
                        name = "|cffd8a84aCommands:|r\n|cff3498db/stkbug|r - Show the last captured Lua error.\n|cff3498db/stkbug clear|r - Clear all captured errors.\n|cff3498db/stkbug copy|r - Copy the currently selected error.\n|cff3498db/stkbug sound|r - Test the selected Bug Catcher sound.\n|cff3498db/stkbug minimap|r - Show or hide the minimap button.\n|cff3498db/stkbug sync|r - Sync captured errors from !BugGrabber for testing.\n|cff3498db/stkbug on|r / |cff3498dboff|r - Enable or disable Bug Catcher.\n|cff3498db/stkbug reload|r - Reload the UI.\n\n|cffd8a84aMinimap:|r Click shows the last error, Shift-click reloads the UI, Alt-click clears errors, and Right-click hides the minimap button.",
                        fontSize = "medium",
                        order = 7,
                    },
                },
            },
            teleportHub = {
                type = "group",
                name = GradientText("Teleport Hub"),
                order = 12,
                args = {
                    intro = PageIntro("Teleport Hub", "One-button access to known teleports, portals, hearthstones, and teleport toys."),
                    enabled = {
                        type = "toggle",
                        name = "Enable Teleport Hub",
                        desc = "Enables Sin's Toolkit QOL Teleport Hub and the /stktp commands.",
                        order = 1,
                        get = function() return GetToolkitValue("teleportHubEnabled") end,
                        set = function(_, value) SetToolkitValue("teleportHubEnabled", value) end,
                    },
                    minimap = {
                        type = "toggle",
                        name = "Show Minimap Button",
                        desc = "Shows a small Sin's Toolkit QOL minimap button for opening the Teleport Hub. You can also use /stktp if you prefer no minimap button.",
                        order = 2,
                        disabled = function() return not GetToolkitValue("teleportHubEnabled") end,
                        get = function() return GetToolkitValue("teleportHubMinimap") end,
                        set = function(_, value) SetToolkitValue("teleportHubMinimap", value) end,
                    },
                    showLocked = {
                        type = "toggle",
                        name = "Show Locked Teleports",
                        desc = "Shows tracked teleports you have not learned or toys you do not own yet. Locked entries are dimmed and not clickable.",
                        order = 3,
                        get = function() return GetToolkitValue("teleportHubShowLocked") end,
                        set = function(_, value) SetToolkitValue("teleportHubShowLocked", value) end,
                    },
                    showDungeonNames = {
                        type = "toggle",
                        name = "Show Dungeon Names",
                        desc = "Shows dungeon or destination names when available instead of only spell names.",
                        order = 4,
                        get = function() return GetToolkitValue("teleportHubShowDungeonNames") end,
                        set = function(_, value) SetToolkitValue("teleportHubShowDungeonNames", value) end,
                    },
                    view = {
                        type = "select",
                        name = "Panel View",
                        desc = "Choose a compact icon grid or a readable list for the Teleport Hub panel.",
                        order = 5,
                        values = {
                            grid = "Grid",
                            list = "List",
                        },
                        get = function() return GetToolkitValue("teleportHubView") or "grid" end,
                        set = function(_, value) SetToolkitValue("teleportHubView", value) end,
                    },
                    open = {
                        type = "execute",
                        name = "Open Teleport Hub",
                        desc = "Opens the Teleport Hub panel. Same as /stktp.",
                        order = 6,
                        disabled = function() return not GetToolkitValue("teleportHubEnabled") end,
                        func = function()
                            if SinsToolkit.TeleportHub then SinsToolkit.TeleportHub:Open() end
                        end,
                    },
                    scan = {
                        type = "execute",
                        name = "Scan Known Teleports",
                        desc = "Prints how many tracked teleports are usable on this character.",
                        order = 7,
                        func = function()
                            if SinsToolkit.TeleportHub then SinsToolkit.TeleportHub:Scan() end
                        end,
                    },
                    commands = {
                        type = "description",
                        name = "|cffd8a84aCommands:|r\n|cff3498db/stktp|r - Open or close Teleport Hub.\n|cff3498db/stktp on|r / |cff3498dboff|r - Enable or disable Teleport Hub.\n|cff3498db/stktp minimap|r - Show or hide the minimap button.\n|cff3498db/stktp scan|r - Print known teleport count.\n|cff3498db/stktp locked|r - Show or hide locked teleports.\n|cff3498db/stktp view|r - Switch between grid and list view.",
                        fontSize = "medium",
                        order = 8,
                    },
                },
            },
            profileVault = {
                type = "group",
                name = GradientText("Profile Vault"),
                order = 13,
                args = {
                    intro = PageIntro("Profile Vault", "Save and restore character setup data for action bars, macros, and keybinds."),
                    backupName = {
                        type = "input",
                        name = "Backup Name",
                        desc = "Name this backup before saving your current action bars, macros, and keybinds.",
                        order = 1,
                        width = "full",
                        get = function() return profileVaultBackupName end,
                        set = function(_, value) profileVaultBackupName = value end,
                    },
                    createBackup = {
                        type = "execute",
                        name = "Create Backup",
                        desc = "Save this character's action bars, account macros, character macros, and keybinds.",
                        order = 2,
                        func = function()
                            if not SinsToolkit.ProfileVault then return end
                            local backup = SinsToolkit.ProfileVault:CreateBackup(profileVaultBackupName)
                            selectedProfileVaultBackup = backup.id
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                            print("|cff9b59b6[Sin's Toolkit]|r Profile backup saved: " .. backup.name)
                        end,
                    },
                    savedBackups = {
                        type = "select",
                        name = "Saved Backups",
                        desc = "Choose a backup to restore or delete.",
                        order = 3,
                        width = "full",
                        values = GetProfileVaultBackupValues,
                        get = function() return selectedProfileVaultBackup end,
                        set = function(_, value) selectedProfileVaultBackup = value end,
                    },
                    summary = {
                        type = "description",
                        name = GetProfileVaultSummary,
                        fontSize = "medium",
                        order = 4,
                    },
                    restore = {
                        type = "execute",
                        name = "Restore to This Character",
                        desc = "Apply the selected backup to the current character. Cannot be used in combat.",
                        order = 5,
                        disabled = function() return selectedProfileVaultBackup == "" end,
                        func = function()
                            if not SinsToolkit.ProfileVault then return end
                            local ok, message = SinsToolkit.ProfileVault:RestoreBackup(selectedProfileVaultBackup)
                            print("|cff9b59b6[Sin's Toolkit]|r " .. (message or (ok and "Profile restored." or "Profile restore failed.")))
                        end,
                    },
                    delete = {
                        type = "execute",
                        name = "Delete Backup",
                        desc = "Delete the selected backup from Sin's Toolkit saved variables.",
                        order = 6,
                        disabled = function() return selectedProfileVaultBackup == "" end,
                        func = function()
                            if not SinsToolkit.ProfileVault then return end
                            SinsToolkit.ProfileVault:DeleteBackup(selectedProfileVaultBackup)
                            selectedProfileVaultBackup = ""
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                            print("|cff9b59b6[Sin's Toolkit]|r Profile backup deleted.")
                        end,
                    },
                },
            },
            exports = {
                type = "group",
                name = GradientText("Export Strings"),
                order = 14,
                args = {
                    intro = PageIntro("Export Strings", "Store useful import strings and profile text in one quick reference list."),
                    savedExports = {
                        type = "select",
                        name = "Saved Exports",
                        desc = "Select a saved export to view or edit.",
                        order = 1,
                        values = function()
                            EnsureDB()
                            local list = { [""] = "None" }
                            if SinsToolkitDB.exportStrings then
                                for k in pairs(SinsToolkitDB.exportStrings) do
                                    list[k] = k
                                end
                            end
                            return list
                        end,
                        get = function() return currentExportName end,
                        set = function(_, value)
                            currentExportName = value
                            if value ~= "" then
                                currentExportString = SinsToolkitDB.exportStrings[value] or ""
                            else
                                currentExportString = ""
                            end
                        end,
                    },
                    exportName = {
                        type = "input",
                        name = "Export Name",
                        desc = "Name of the export string to save or rename.",
                        order = 2,
                        get = function() return currentExportName end,
                        set = function(_, value) currentExportName = value end,
                    },
                    exportData = {
                        type = "input",
                        name = "Export String",
                        desc = "Paste your export string here.",
                        order = 3,
                        width = "full",
                        multiline = 10,
                        get = function() return currentExportString end,
                        set = function(_, value) currentExportString = SanitizeExportString(value) end,
                    },
                    save = {
                        type = "execute",
                        name = "Save",
                        desc = "Save the current export string.",
                        order = 4,
                        disabled = function() return currentExportName == "" or currentExportString == "" end,
                        func = function()
                            EnsureDB()
                            SinsToolkitDB.exportStrings = SinsToolkitDB.exportStrings or {}
                            SinsToolkitDB.exportStrings[currentExportName] = currentExportString
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                            print("|cff9b59b6[Sin's Toolkit]|r Export string saved: "..currentExportName)
                        end,
                    },
                    delete = {
                        type = "execute",
                        name = "Delete",
                        desc = "Delete the selected export string.",
                        order = 5,
                        disabled = function() return currentExportName == "" end,
                        func = function()
                            EnsureDB()
                            if SinsToolkitDB.exportStrings then
                                SinsToolkitDB.exportStrings[currentExportName] = nil
                            end
                            print("|cff9b59b6[Sin's Toolkit]|r Export string deleted: "..currentExportName)
                            currentExportName = ""
                            currentExportString = ""
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                        end,
                    },
                    clear = {
                        type = "execute",
                        name = "Clear",
                        desc = "Clear the input fields.",
                        order = 6,
                        func = function()
                            currentExportName = ""
                            currentExportString = ""
                            if E.Libs.AceConfigRegistry then E.Libs.AceConfigRegistry:NotifyChange("ElvUI") end
                        end,
                    },
                },
            },
            changelog = {
                type = "group",
                name = GradientText("Changelog"),
                order = 15,
                args = {
                    intro = PageIntro("Changelog", "Recent Sin's Toolkit updates with the date and what changed."),
                    notes = {
                        type = "description",
                        order = 1,
                        fontSize = "medium",
                        name = "|cffd8a84aVersion 1.1.49 - June 9, 2026|r\n"
                            .. "- Fixed Automate Gossip quest checks for modern Retail clients by using table-returning C_GossipInfo quest APIs when available.\n"
                            .. "- Added a compatibility fallback for older gossip quest count APIs so the feature fails more gracefully across client changes.\n\n"
                            .. "|cffd8a84aVersion 1.1.48 - June 8, 2026|r\n"
                            .. "- Updated Automate Gossip to behave more like Leatrix-style safe gossip handling.\n"
                            .. "- Automate Gossip now only auto-selects safe single-option gossip with no active or available gossip quests.\n"
                            .. "- Added safeguards to avoid city direction, bank, auction house, trainer, profession, and storyteller-style gossip options.\n\n"
                            .. "|cffd8a84aVersion 1.1.47 - June 8, 2026|r\n"
                            .. "- Bundled the Bug Catcher Fatality alert sound inside Sin's Toolkit so the sound works even when BugSack is not installed.\n\n"
                            .. "|cffd8a84aVersion 1.1.46 - June 8, 2026|r\n"
                            .. "- Fixed Bug Catcher sound alerts after removing BugSack/BugGrabber by falling back to a built-in Blizzard alert when external BugSack media is unavailable.\n"
                            .. "- Added a Test Error Sound button and /stkbug sound to test the currently selected Bug Catcher alert sound in-game.\n\n"
                            .. "|cffd8a84aVersion 1.1.45 - June 8, 2026|r\n"
                            .. "- Fixed a Bug Catcher window error by guarding FontString wrapping methods that are not available in every 12.0.x client load context.\n\n"
                            .. "|cffd8a84aVersion 1.1.44 - June 8, 2026|r\n"
                            .. "- Updated Bug Catcher Clear so it wipes current and saved Toolkit errors, clears mirrored BugGrabber IDs, and clears BugGrabber's DB when BugGrabber is enabled for testing.\n\n"
                            .. "|cffd8a84aVersion 1.1.43 - June 8, 2026|r\n"
                            .. "- Added a Copy button and /stkbug copy command for copying the currently selected Bug Catcher error.\n"
                            .. "- Copy opens a selectable text window with the current error highlighted for Ctrl+C.\n\n"
                            .. "|cffd8a84aVersion 1.1.42 - June 8, 2026|r\n"
                            .. "- Replaced the Bug Catcher error renderer with a scrollable FontString so captured error text displays reliably instead of rendering blank.\n"
                            .. "- Changed the minimap error state to tint only the bag red, without a full red glow around the minimap icon.\n\n"
                            .. "|cffd8a84aVersion 1.1.41 - June 8, 2026|r\n"
                            .. "- Fixed Bug Catcher window creation on 12.0.x by replacing the missing CharacterFrameTabButtonTemplate dependency with safe panel buttons.\n"
                            .. "- Kept All, Session, and Last view switching while preventing the window from stopping before the scroll text area is created.\n\n"
                            .. "|cffd8a84aVersion 1.1.40 - June 8, 2026|r\n"
                            .. "- Added BugSack: Fatality as a Bug Catcher error sound option when BugSack/LibSharedMedia provides that sound.\n\n"
                            .. "|cffd8a84aVersion 1.1.39 - June 8, 2026|r\n"
                            .. "- Fixed the Bug Catcher display area so captured errors render in the scroll window instead of appearing blank.\n"
                            .. "- Added explicit text sizing, anchoring, color, wrapping, and a clearer empty-view message with Toolkit/BugGrabber sync guidance.\n\n"
                            .. "|cffd8a84aVersion 1.1.38 - June 8, 2026|r\n"
                            .. "- Made Bug Catcher's !BugGrabber testing bridge more robust by syncing on window open, addon load, and short delayed passes after login.\n"
                            .. "- Added /stkbug sync to manually pull captured errors from !BugGrabber during testing.\n"
                            .. "- Hardened the BugGrabber callback argument handling so captured error IDs are read correctly.\n\n"
                            .. "|cffd8a84aVersion 1.1.37 - June 8, 2026|r\n"
                            .. "- Fixed Bug Catcher testing with !BugGrabber by mirroring BugGrabber.BugGrabbed events into Sin's Toolkit's own error list.\n"
                            .. "- Added an initial BugGrabber database sync so existing captured errors appear in the Sin's Toolkit window after login/reload.\n"
                            .. "- Registered Sin's Toolkit as a BugGrabber display while testing so BugGrabber does not need BugSack to show captured errors.\n\n"
                            .. "|cffd8a84aVersion 1.1.36 - June 8, 2026|r\n"
                            .. "- Allowed Sin's Toolkit Bug Catcher to run alongside !BugGrabber for testing.\n"
                            .. "- Kept real BugSack as a conflict because it owns the BugSack display globals and window skinning path.\n"
                            .. "- Updated Bug Catcher status and option text to explain the testing behavior.\n\n"
                            .. "|cffd8a84aVersion 1.1.35 - June 8, 2026|r\n"
                            .. "- Added a Bug Catcher sound dropdown for choosing an alert sound when Lua errors are captured.\n"
                            .. "- Changed the clean minimap icon to a purple WoW bag icon and kept the red glow hidden unless captured errors exist.\n\n"
                            .. "|cffd8a84aVersion 1.1.34 - June 8, 2026|r\n"
                            .. "- Changed the Bug Catcher minimap icon to a bag-style indicator.\n"
                            .. "- The Bug Catcher bag is purple when clean and turns red when Lua errors are captured.\n\n"
                            .. "|cffd8a84aVersion 1.1.33 - June 8, 2026|r\n"
                            .. "- Changed Bug Catcher to fully stand down when BugSack or !BugGrabber is loaded.\n"
                            .. "- Added clear user messaging that BugSack and !BugGrabber must be removed or disabled before using the Sin's Toolkit plugin version.\n"
                            .. "- Kept the toolkit Bug Catcher self-contained with its own saved error list, minimap button, and BugSack-style window frames.\n\n"
                            .. "|cffd8a84aVersion 1.1.32 - June 8, 2026|r\n"
                            .. "- Added Bug Catcher, a compact BugSack-style Lua error viewer inside Sin's Toolkit QOL.\n"
                            .. "- Added /stkbug and /sinbugs commands for opening the last error, clearing errors, toggling the minimap button, and reloading the UI.\n"
                            .. "- Added a Bug Catcher minimap button: click shows the last error, Shift-click reloads UI, Alt-click clears errors, and Right-click hides the button.\n"
                            .. "- Added BugSack-compatible frame/global names for skin compatibility when real BugSack is not loaded.\n"
                            .. "- Bug Catcher uses its own lightweight Lua error catcher when BugSack and !BugGrabber are not loaded.\n\n"
                            .. "|cffd8a84aVersion 1.1.31 - June 8, 2026|r\n"
                            .. "- Reworked Teleport Hub secure buttons to match the original OneButton pattern more closely.\n"
                            .. "- Pre-created a secure button pool with no parent, then assigned buttons to the scroll panel during render.\n"
                            .. "- Restored generic secure attributes for teleports: type/spell, type/item, and type/toy.\n"
                            .. "- Registered teleport buttons for AnyUp and AnyDown clicks like the source addon.\n\n"
                            .. "|cffd8a84aVersion 1.1.30 - June 8, 2026|r\n"
                            .. "- Removed the purple tile fill behind Teleport Hub icons for a cleaner look.\n"
                            .. "- Changed Teleport Hub spell clicks to explicit left-click secure spell attributes using type1 and spell1.\n"
                            .. "- Kept toy and hearthstone actions on explicit left-click secure actions.\n"
                            .. "- Left the Teleport Hub panel open after clicks so any remaining client-side cast issue is visible while testing.\n\n"
                            .. "|cffd8a84aVersion 1.1.29 - June 8, 2026|r\n"
                            .. "- Added Grid/List view selection for Teleport Hub.\n"
                            .. "- Added /stktp view, /stktp grid, and /stktp list commands.\n"
                            .. "- Made the full Teleport Hub tile or list row clickable instead of only the icon texture.\n"
                            .. "- Tested secure macrotext actions for Teleport Hub during click-path troubleshooting.\n"
                            .. "- Stopped closing the Teleport Hub panel immediately after clicking so failed clicks are visible while testing.\n\n"
                            .. "|cffd8a84aVersion 1.1.28 - June 8, 2026|r\n"
                            .. "- Made the Teleport Hub window resizable and saved its size and position.\n"
                            .. "- Increased the default Teleport Hub window size and tightened the teleport grid to reduce scrolling.\n"
                            .. "- Slightly reduced Teleport Hub icon size and widened label cells so longer names are easier to read.\n"
                            .. "- Updated Teleport Hub secure spell buttons to cast by resolved spell name for more reliable clicking.\n\n"
                            .. "|cffd8a84aVersion 1.1.27 - June 8, 2026|r\n"
                            .. "- Removed the duplicate No Auto Close page from Blizzard's AddOns settings list.\n"
                            .. "- Kept No Auto Close controlled from the Sin's Toolkit QOL ElvUI plugin page.\n"
                            .. "- Removed the confusing default-position mover UI from the user-facing No Auto Close settings.\n\n"
                            .. "|cffd8a84aVersion 1.1.26 - June 8, 2026|r\n"
                            .. "- Added Teleport Hub with /stktp slash commands.\n"
                            .. "- Added a searchable secure-button panel for known teleports, portals, hearthstones, and teleport toys.\n"
                            .. "- Added Teleport Hub options for enabling the feature, showing the minimap button, showing locked teleports, and choosing destination names.\n"
                            .. "- Added an optional minimap button that can be hidden from the panel or with /stktp minimap.\n\n"
                            .. "|cffd8a84aVersion 1.1.25 - June 8, 2026|r\n"
                            .. "- Updated the in-game Information tab with the full current Sin's Toolkit QOL feature set.\n"
                            .. "- Added clearer descriptions for Gear Scanner providers, Druid automation, Identifier, Profile Vault, Export Strings, and available commands.\n\n"
                            .. "|cffd8a84aVersion 1.1.24 - June 8, 2026|r\n"
                            .. "- Added the new Identifier page and /stkid commands.\n"
                            .. "- Added hover-identify mode for inspecting UI frames under the cursor.\n"
                            .. "- Added one-time cursor identification, global frame-name lookup, detailed/simple output, and copyable result popups.\n"
                            .. "- Included secret-safe frame inspection checks for newer WoW client behavior.\n\n"
                            .. "|cffd8a84aVersion 1.1.23 - June 8, 2026|r\n"
                            .. "- Renamed the addon display to Sin's Toolkit QOL.\n"
                            .. "- Added a separate Druid Auto Flight Form toggle under Automation.\n"
                            .. "- Druid Auto Flight Form can cancel ground Travel Form in safe outdoor flyable areas so Druids can move toward Flight Form without touching Auto Unshift.\n"
                            .. "- Kept Auto Unshift for Druids as its own separate toggle and behavior.\n\n"
                            .. "|cffd8a84aVersion 1.1.22 - June 7, 2026|r\n"
                            .. "- Cleaned up Gear Scanner Pawn upgrade messages so huge upgrades display as major upgrade instead of an oversized percentage.\n"
                            .. "- Hid raw Pawn item strings from auto-equip chat output unless Pawn provides a proper clickable item link.\n\n"
                            .. "|cffd8a84aVersion 1.1.21 - June 7, 2026|r\n"
                            .. "- Updated Gear Scanner dependency handling so Pawn or ClassCodex can power upgrade evaluation.\n"
                            .. "- Gear Scanner no longer pauses only because ClassCodex is missing when Pawn is installed and loaded.\n"
                            .. "- Updated Gear Scanner panel text and /stk scan messages to say Pawn or ClassCodex is required.\n\n"
                            .. "|cffd8a84aVersion 1.1.20 - June 7, 2026|r\n"
                            .. "- Added Gear Scanner auto-equip explanations to the chat message when an upgrade is equipped.\n"
                            .. "- When Pawn is installed and provides details, the message can show the Pawn scale, percentage upgrade, and compared item.\n"
                            .. "- When ClassCodex fallback scoring is used, the message shows the score difference and replacement slot/item.\n\n"
                            .. "|cffd8a84aVersion 1.1.19 - June 7, 2026|r\n"
                            .. "- Renamed the Gear Scanner Pawn option to Use Pawn When Installed.\n"
                            .. "- Made Pawn preference enabled by default for new profiles while keeping Pawn an optional dependency.\n"
                            .. "- Gear Scanner now prefers Pawn's installed upgrade advisor when available, then falls back to ClassCodex if Pawn is missing or cannot evaluate an item.\n"
                            .. "- Updated Gear Scanner tooltip wording so Pawn fallback behavior is clearer.\n\n"
                            .. "|cffd8a84aVersion 1.1.18 - June 7, 2026|r\n"
                            .. "- Reworked Gear Scanner upgrade evaluation so ClassCodex remains the primary scoring source while Pawn acts as an optional advisory upgrade signal.\n"
                            .. "- Updated Pawn support to use Pawn's intended bag-upgrade API when available instead of relying on lower-level internals.\n"
                            .. "- Fixed hybrid primary-stat detection, including Feral and Guardian Druids using Agility correctly.\n"
                            .. "- Added minimum-level checks so Gear Scanner does not try to equip items your character cannot use yet.\n"
                            .. "- Added a Pawn-style 0.5% upgrade threshold on top of the configurable Minimum Upgrade Score slider.\n"
                            .. "- Fixed manual /stk scan timing so it waits for item data callbacks before reporting no upgrades found.\n\n"
                            .. "|cffd8a84aVersion 1.1.17 - June 7, 2026|r\n"
                            .. "- Updated the Information page banner using the cleaner Sin's Toolkit banner asset.\n"
                            .. "- Fixed the banner display ratio so it no longer appears cramped or stretched at the top.\n"
                            .. "- Optimized UI Errors tab loading by caching grouped error-message results.\n"
                            .. "- Optimized the CVar selector by caching dropdown values per search and modified-only filter.\n\n"
                            .. "|cffd8a84aVersion 1.1.16 - June 7, 2026|r\n"
                            .. "- Fixed UI Error Filtering so duplicate Blizzard error IDs with the same displayed text are blocked together.\n"
                            .. "- Cleaned up the UI Errors list so duplicate displayed messages behave as one checkbox.\n"
                            .. "- Verified Auto Unshift for Druids still only suppresses its shapeshift-form error while that feature is enabled.\n\n"
                            .. "|cffd8a84aVersion 1.1.15 - June 7, 2026|r\n"
                            .. "- Fixed Gear Scanner scan filtering so valid gear is not rejected by an overly broad usable-item check.\n"
                            .. "- Changed the ClassCodex dependency guard so Gear Scanner pauses when ClassCodex is unavailable instead of silently turning off the saved toggle.\n\n"
                            .. "|cffd8a84aVersion 1.1.14 - June 7, 2026|r\n"
                            .. "- Adjusted Gear Scanner scoring for leveling so item level and item quality are considered more clearly.\n"
                            .. "- ClassCodex stat priorities remain the primary comparison, with rarity and item level used as supporting score factors.\n\n"
                            .. "|cffd8a84aVersion 1.1.13 - June 7, 2026|r\n"
                            .. "- Fixed Gear Scanner armor filtering so it will not equip the wrong armor type for your class.\n"
                            .. "- Added clearer tooltip feedback when an item is rejected because it is not your class armor type.\n\n"
                            .. "|cffd8a84aVersion 1.1.12 - June 7, 2026|r\n"
                            .. "- Added this in-game Changelog page after Export Strings.\n\n"
                            .. "|cffd8a84aVersion 1.1.11 - June 7, 2026|r\n"
                            .. "- Updated Auto Unshift for Druids tooltip wording.\n"
                            .. "- Updated the Information panel description for Auto Unshift for Druids.\n"
                            .. "- Added clarification that Auto Unshift can be helpful for GSE, also known as Gnome Sequencer.\n"
                            .. "- Added suppression for the shapeshift-form UI error while Auto Unshift for Druids is enabled.\n"
                            .. "- Restored Blizzard's default shapeshift error behavior when Auto Unshift for Druids is disabled.\n\n"
                            .. "|cffd8a84aVersion 1.1.10 - June 7, 2026|r\n"
                            .. "- Expanded Gear Scanner tooltips.\n"
                            .. "- Added clearer Pawn setup instructions.\n"
                            .. "- Improved loot-roll and soulbind confirmation option descriptions.\n"
                            .. "- Fixed the sidebar gradient so it flows from dark purple to white.\n\n"
                            .. "|cffd8a84aVersion 1.1.9 - June 7, 2026|r\n"
                            .. "- Added optional Gear Scanner loot-roll settings.\n"
                            .. "- Added optional soulbind confirmation settings.\n"
                            .. "- Added optional Pawn support and Pawn as an optional dependency.\n"
                            .. "- Added support for showing won't-equip reasons in item tooltips.\n"
                            .. "- Updated the addon banner image.\n\n"
                            .. "|cffd8a84aVersion 1.1.8 - June 7, 2026|r\n"
                            .. "- Added custom Sin's Toolkit banner artwork.\n"
                            .. "- Updated the ElvUI options panel branding.\n"
                            .. "- Added purple and violet visual styling to the options layout.\n\n"
                            .. "|cffd8a84aVersion 1.1.7 - June 7, 2026|r\n"
                            .. "- Redesigned the options layout inside ElvUI.\n"
                            .. "- Changed the options panel from top tabs to left-side navigation.\n"
                            .. "- Added compact section introductions.\n"
                            .. "- Added a branded Sin's Toolkit information area.",
                    },
                },
            },
        },
    }

    UpdateErrorToggles()
end

function Plugin:Initialize()
end

local function OptionsCallback()
    Plugin:InsertOptions()
end

if EP then
    EP:RegisterPlugin(addonName, OptionsCallback)
end

E:RegisterModule(Plugin:GetName())

