local addonName, SinsToolkit = ...
local Core = CreateFrame("Frame")
SinsToolkit.Core = Core
_G.SinsToolkit = SinsToolkit -- Expose for other files

SinsToolkit.UIProvider = "Standalone"
SinsToolkit.UI = SinsToolkit.UI or {}

local DB_VERSION = 8

local DB_DEFAULTS = {
    dbVersion = DB_VERSION,
    gearScannerEnabled = true,
    gearScannerMaxLevelDisable = true,
    gearScannerSilent = false,
    gearScannerLootDelay = 0.5,
    gearScannerScanOnLoot = true,
    gearScannerAllowUnbound = false,
    gearScannerMinimumUpgrade = 0,
    gearScannerScoreTooltips = true,
    gearScannerReasonsTooltips = false,
    gearScannerLockSlots = false,
    gearScannerLockedSlots = {},
    gearScannerAutoRollGreensBopBlues = false,
    gearScannerAutoRollBoeBlues = false,
    gearScannerAutoRollEpics = false,
    gearScannerRollNonGearLoot = false,
    gearScannerNeverAutoNeed = true,
    gearScannerAutoConfirmBindOther = false,
    gearScannerAutoConfirmBindBlues = false,
    gearScannerAutoConfirmBindEpics = false,
    gearScannerUsePawn = true,
    hideOutOfRangeErrors = false,
    keepPanelsOpen = false,
    noAutoClose = {
        defaultPosition = {
            anchor = "TOPLEFT",
            x = 50,
            y = -50,
        },
        respectExcludedFrames = true,
    },
    frameMoverEnabled = false,
    frameMoverRequireModifier = false,
    frameMoverPositionMode = "session",
    frameMoverScaleMode = "session",
    frameMoverCtrlScrollScale = true,
    frameMoverRightClickReset = true,
    frameMoverDisabledFrames = {},
    frameMoverPositions = {},
    frameMoverScales = {},
    frameScales = {},
    automateGossip = false,
    automateGossipModifier = "SHIFT",
    gseAutoUnshift = false,
    druidAutoFlightForm = false,
    professionItemHotkeyEnabled = false,
    professionItemHotkeyModifier = "ALT",
    wowheadSearchEnabled = false,
    wowheadQuickLink = {
        prefix = "",
        suffix = "",
    },
    identifierEnabled = false,
    identifierDetailed = true,
    identifierTooltip = true,
    bugCatcherEnabled = true,
    bugCatcherMinimap = true,
    bugCatcherMinimapOnlyErrors = true,
    bugCatcherMaxErrors = 100,
    bugCatcherSound = "NONE",
    bugCatcherBugSackCompatibility = false,
    bugCatcherErrors = {},
    mainWindowOpacity = 0.98,
    teleportHubEnabled = false,
    teleportHubMinimap = true,
    teleportHubKeybind = "",
    teleportHubShowLocked = false,
    teleportHubShowFavorites = false,
    teleportHubShowRecent = true,
    teleportHubShowDungeonNames = true,
    teleportHubView = "menu",
    teleportHubSelected = "",
    teleportHubPreferredHearthstone = "random",
    teleportHubFavorites = {},
    teleportHubRecent = {},
    teleportHubCollapsed = {},
    teleportHubWindow = {
        width = 760,
        height = 620,
        point = "CENTER",
        relativePoint = "CENTER",
        x = 0,
        y = 0,
    },
    blockedUIErrors = {},
    profileVaultBackups = {},
    minimap = { hide = false },
}

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

local function RunMigrations(db)
    local version = tonumber(db.dbVersion) or 0

    -- Future migrations should follow this shape:
    -- if version < 3 then
    --     migrate only missing or legacy fields here; do not overwrite user choices
    -- end
    if version < 2 then
        -- Previous builds exposed BugSack-compatible globals by default for compatible skinning addons.
        -- Preserve that behavior for existing users while new installs default compatibility off.
        if db.bugCatcherBugSackCompatibility == nil and next(db) ~= nil then
            db.bugCatcherBugSackCompatibility = true
        end
    end
    if version < 3 then
        -- Existing users already had an always-visible Bug Catcher minimap button.
        -- Keep that behavior while new installs only show it when errors exist.
        if db.bugCatcherMinimapOnlyErrors == nil and next(db) ~= nil then
            db.bugCatcherMinimapOnlyErrors = false
        end
    end
    if version < 4 then
        db.frameMoverDisabledFrames = db.frameMoverDisabledFrames or {}
        db.frameMoverPositions = db.frameMoverPositions or {}
        db.frameMoverScales = db.frameMoverScales or {}
    end
    if version < 5 then
        db.frameStylerEnabled = nil
        db.frameStylerUseClassColor = nil
        db.frameStylerGlobalColor = nil
        db.frameStylerCategories = nil
        db.frameStylerSkins = nil
    end
    if version < 6 then
        db.exportStrings = nil
    end
    if version < 7 then
        if db.teleportHubView ~= "grid" and db.teleportHubView ~= "list" and db.teleportHubView ~= "menu" then
            db.teleportHubView = "menu"
        end
        db.teleportHubSelected = db.teleportHubSelected or ""
    end
    if version < 8 then
        db.frameScales = db.frameScales or db.frameMoverScales or {}
        db.frameMoverScales = db.frameScales
    end
    db.frameMoverConflictIgnore = nil
    for key in pairs(db) do
        if type(key) == "string" and key:match("^frameMover.*WarningSuppress$") then
            db[key] = nil
        end
    end
    db.featureOwners = nil

    db.dbVersion = DB_VERSION
end

local function IsAddonLoaded(addon)
    if C_AddOns and C_AddOns.IsAddOnLoaded then
        return C_AddOns.IsAddOnLoaded(addon)
    end
    return IsAddOnLoaded and IsAddOnLoaded(addon)
end

function SinsToolkit.IsClassCodexAvailable()
    return IsAddonLoaded("ClassCodex") and ClassCodexData ~= nil
end

function SinsToolkit.IsPawnAvailable()
    return IsAddonLoaded("Pawn")
        and (PawnIsItemDefinitivelyAnUpgrade or PawnShouldItemLinkHaveUpgradeArrow or (PawnGetItemData and PawnIsItemAnUpgrade)) ~= nil
end

function SinsToolkit.IsGearScannerProviderAvailable()
    return SinsToolkit.IsPawnAvailable() or SinsToolkit.IsClassCodexAvailable()
end

function SinsToolkit.GetGearProviderModeText()
    local pawnActive = SinsToolkit.IsPawnAvailable and SinsToolkit.IsPawnAvailable()
    local classCodexActive = SinsToolkit.IsClassCodexAvailable and SinsToolkit.IsClassCodexAvailable()
    local usePawn = SinsToolkitDB and SinsToolkitDB.gearScannerUsePawn == true

    if usePawn and pawnActive then
        return "Pawn is evaluating upgrades. ClassCodex remains available as a fallback if Pawn cannot score an item."
    end
    if classCodexActive then
        return "ClassCodex fallback is active because Pawn is unavailable or the Pawn option is disabled."
    end
    return "Basic fallback is active. Install Pawn or ClassCodex for stronger upgrade scoring."
end

function SinsToolkit.IsGSEAvailable()
    return IsAddonLoaded("GSE")
end

function SinsToolkit.EnsureDB()
    SinsToolkitDB = SinsToolkitDB or {}
    RunMigrations(SinsToolkitDB)
    for key, value in pairs(DB_DEFAULTS) do
        if SinsToolkitDB[key] == nil then
            SinsToolkitDB[key] = type(value) == "table" and CopyDefaults(value) or value
        end
    end
    SinsToolkitDB.blockedUIErrors = SinsToolkitDB.blockedUIErrors or {}
    SinsToolkitDB.profileVaultBackups = SinsToolkitDB.profileVaultBackups or {}
    SinsToolkitDB.gearScannerLockedSlots = SinsToolkitDB.gearScannerLockedSlots or {}
    SinsToolkitDB.bugCatcherErrors = SinsToolkitDB.bugCatcherErrors or {}
    SinsToolkitDB.wowheadQuickLink = SinsToolkitDB.wowheadQuickLink or CopyDefaults(DB_DEFAULTS.wowheadQuickLink)
    SinsToolkitDB.wowheadQuickLink.prefix = SinsToolkitDB.wowheadQuickLink.prefix or ""
    SinsToolkitDB.wowheadQuickLink.suffix = SinsToolkitDB.wowheadQuickLink.suffix or ""
    SinsToolkitDB.noAutoClose = SinsToolkitDB.noAutoClose or CopyDefaults(DB_DEFAULTS.noAutoClose)
    SinsToolkitDB.noAutoClose.defaultPosition = SinsToolkitDB.noAutoClose.defaultPosition or CopyDefaults(DB_DEFAULTS.noAutoClose.defaultPosition)
    if SinsToolkitDB.noAutoClose.respectExcludedFrames == nil then SinsToolkitDB.noAutoClose.respectExcludedFrames = true end
    SinsToolkitDB.frameMoverDisabledFrames = SinsToolkitDB.frameMoverDisabledFrames or {}
    SinsToolkitDB.frameMoverPositions = SinsToolkitDB.frameMoverPositions or {}
    SinsToolkitDB.frameScales = SinsToolkitDB.frameScales or SinsToolkitDB.frameMoverScales or {}
    SinsToolkitDB.frameMoverScales = SinsToolkitDB.frameScales
    SinsToolkitDB.teleportHubFavorites = SinsToolkitDB.teleportHubFavorites or {}
    SinsToolkitDB.teleportHubRecent = SinsToolkitDB.teleportHubRecent or {}
    if SinsToolkitDB.teleportHubShowFavorites == nil then SinsToolkitDB.teleportHubShowFavorites = DB_DEFAULTS.teleportHubShowFavorites end
    if SinsToolkitDB.teleportHubShowRecent == nil then SinsToolkitDB.teleportHubShowRecent = DB_DEFAULTS.teleportHubShowRecent end
    SinsToolkitDB.teleportHubCollapsed = SinsToolkitDB.teleportHubCollapsed or {}
    SinsToolkitDB.teleportHubSelected = SinsToolkitDB.teleportHubSelected or ""
    SinsToolkitDB.teleportHubPreferredHearthstone = SinsToolkitDB.teleportHubPreferredHearthstone or "random"
    SinsToolkitDB.minimap = SinsToolkitDB.minimap or CopyDefaults(DB_DEFAULTS.minimap)
    if type(SinsToolkitDB.mainWindowOpacity) ~= "number" then SinsToolkitDB.mainWindowOpacity = DB_DEFAULTS.mainWindowOpacity end
    SinsToolkitDB.mainWindowOpacity = math.min(1, math.max(0.45, SinsToolkitDB.mainWindowOpacity))
end

function SinsToolkit.GetProviderDisplayName()
    return "Standalone"
end

local opacityFrames = {}
local opacityTextures = {}

function SinsToolkit.GetWindowOpacity()
    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
    local value = SinsToolkitDB and SinsToolkitDB.mainWindowOpacity or 0.98
    if type(value) ~= "number" then value = 0.98 end
    return math.min(1, math.max(0.45, value))
end

function SinsToolkit.SetWindowOpacity(value)
    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
    value = math.min(1, math.max(0.45, value or 0.98))
    SinsToolkitDB.mainWindowOpacity = value
    if SinsToolkit.RefreshWindowOpacity then SinsToolkit.RefreshWindowOpacity() end
end

function SinsToolkit.ApplyWindowOpacity(frame)
    if not frame or not frame.SetBackdropColor then return end
    local colors = opacityFrames[frame]
    if not colors then return end
    frame:SetBackdropColor(colors.r, colors.g, colors.b, SinsToolkit.GetWindowOpacity())
    if frame.SetBackdropBorderColor then
        frame:SetBackdropBorderColor(colors.br, colors.bg, colors.bb, colors.ba)
    end
end

function SinsToolkit.RegisterOpacityFrame(frame, r, g, b, br, bg, bb, ba)
    if not frame then return end
    opacityFrames[frame] = {
        r = r or 0.02,
        g = g or 0.02,
        b = b or 0.025,
        br = br or 0.72,
        bg = bg or 0.41,
        bb = bb or 0.88,
        ba = ba or 0.90,
    }
    SinsToolkit.ApplyWindowOpacity(frame)
end

function SinsToolkit.RegisterOpacityTexture(texture, r, g, b)
    if not texture or not texture.SetColorTexture then return end
    opacityTextures[texture] = {
        r = r or 0.02,
        g = g or 0.02,
        b = b or 0.025,
    }
    texture:SetColorTexture(opacityTextures[texture].r, opacityTextures[texture].g, opacityTextures[texture].b, SinsToolkit.GetWindowOpacity())
end

function SinsToolkit.RefreshWindowOpacity()
    for frame in pairs(opacityFrames) do
        if frame and frame.SetBackdropColor then
            SinsToolkit.ApplyWindowOpacity(frame)
        else
            opacityFrames[frame] = nil
        end
    end
    for texture, colors in pairs(opacityTextures) do
        if texture and texture.SetColorTexture then
            texture:SetColorTexture(colors.r, colors.g, colors.b, SinsToolkit.GetWindowOpacity())
        else
            opacityTextures[texture] = nil
        end
    end
end

function SinsToolkit.GetTheme()
    local theme = {
        provider = SinsToolkit.UIProvider or "Standalone",
        accentR = 0.72,
        accentG = 0.41,
        accentB = 0.88,
        accentHex = "b868dc",
        bgR = 0.02,
        bgG = 0.02,
        bgB = 0.025,
        bgA = 0.92,
        borderA = 0.90,
        panelScale = 1,
        blizzard = false,
    }

    theme.accentHex = ("%02x%02x%02x"):format(
        math.floor(theme.accentR * 255 + 0.5),
        math.floor(theme.accentG * 255 + 0.5),
        math.floor(theme.accentB * 255 + 0.5)
    )
    return theme
end

function SinsToolkit.ApplyDependencyGuards(printNotice)
    SinsToolkit.EnsureDB()

    if SinsToolkitDB.gearScannerEnabled and not SinsToolkit.IsGearScannerProviderAvailable() then
        if printNotice then
            print("|cff9b59b6[Sin's Toolkit: QOL]|r Gear Scanner is paused because Pawn or ClassCodex is not installed or loaded.")
        end
    end
end

function SinsToolkit.IsFeatureOwnedByToolkit(featureKey)
    return true
end

function SinsToolkit.IsFeatureActive(featureKey)
    SinsToolkit.EnsureDB()
    if featureKey == "gearScannerEnabled" and not SinsToolkit.IsGearScannerProviderAvailable() then return false end
    return SinsToolkitDB[featureKey] == true and SinsToolkit.IsFeatureOwnedByToolkit(featureKey)
end

function SinsToolkit.TriggerUpdate(dbKey)
    if dbKey and dbKey:match("^gearScanner") and SinsToolkit.RefreshGearScannerScores then
        SinsToolkit.RefreshGearScannerScores()
    end
    if dbKey == "gearScannerEnabled" and SinsToolkitDB.gearScannerEnabled and not SinsToolkit.IsGearScannerProviderAvailable() then
        print("|cff9b59b6[Sin's Toolkit: QOL]|r Gear Scanner requires Pawn or ClassCodex.")
        return
    end
    if dbKey == "gseAutoUnshift" and SinsToolkit.ApplyGSEAutoUnshift then SinsToolkit.ApplyGSEAutoUnshift() end
    if dbKey == "druidAutoFlightForm" and SinsToolkit.ApplyDruidAutoFlightForm then SinsToolkit.ApplyDruidAutoFlightForm() end
    if dbKey and dbKey:match("^identifier") and SinsToolkit.ApplyIdentifier then SinsToolkit.ApplyIdentifier() end
    if dbKey and dbKey:match("^bugCatcher") and SinsToolkit.ApplyBugCatcher then SinsToolkit.ApplyBugCatcher() end
    if dbKey and dbKey:match("^teleportHub") and SinsToolkit.ApplyTeleportHub then SinsToolkit.ApplyTeleportHub() end
    if dbKey == "professionItemHotkeyEnabled" and SinsToolkit.ApplyProfessionItemHotkeyEnabled then SinsToolkit.ApplyProfessionItemHotkeyEnabled() end
    if dbKey == "professionItemHotkeyModifier" and SinsToolkit.ApplyProfessionItemHotkeyModifier then
        SinsToolkit.ApplyProfessionItemHotkeyModifier(SinsToolkitDB.professionItemHotkeyModifier)
    end
    if dbKey == "wowheadSearchEnabled" and SinsToolkit.ApplyWowheadQuickLink then SinsToolkit.ApplyWowheadQuickLink() end
    if dbKey == "keepPanelsOpen" then
        if SinsToolkit.ApplyNoAutoClosePanels then SinsToolkit.ApplyNoAutoClosePanels() end
        print("|cff9b59b6[Sin's Toolkit: QOL]|r Keep Panels Open updated. Reload your UI after disabling it.")
    end
    if dbKey and dbKey:match("^frameMover") and SinsToolkit.ApplyFrameMover then SinsToolkit.ApplyFrameMover() end
end

_G.BINDING_HEADER_SINS_TOOLKIT = "Sin's Toolkit: QOL"
_G.BINDING_NAME_SINS_TOOLKIT_TELEPORT_HUB = "Open Teleport Hub"
function _G.SinsToolkit_ToggleTeleportHub()
    if SinsToolkit and SinsToolkit.TeleportHub and SinsToolkit.TeleportHub.Toggle then
        SinsToolkit.TeleportHub:Toggle()
    end
end

function SinsToolkit.ApplyTeleportHubKeybind()
    return false, "Teleport Hub keybinding from the Toolkit panel is temporarily disabled. Use WoW Key Bindings for now."
end

SLASH_SinsToolkit1 = "/stk"
SlashCmdList.SinsToolkit = function(msg)
    msg = (msg or ""):lower():match("^%s*(.-)%s*$")
    if msg == "scan" then
        if SinsToolkit.ForceGearScan then
            SinsToolkit.ForceGearScan()
        else
            print("|cff9b59b6[Sin's Toolkit: QOL]|r Gear Scanner is unavailable or currently disabled.")
        end
    elseif msg == "status" then
        print("|cff9b59b6Sin's Toolkit: QOL|r status")
        print("  Gear Scanner: " .. (SinsToolkit.IsFeatureActive("gearScannerEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        print("  Gear Mode: " .. (SinsToolkit.GetGearProviderModeText and SinsToolkit.GetGearProviderModeText() or "Unavailable."))
        print("  Keep Panels Open: " .. (SinsToolkit.IsFeatureActive("keepPanelsOpen") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        print("  Frame Mover: " .. (SinsToolkit.IsFeatureActive("frameMoverEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        print("  UI Error Filtering: " .. (SinsToolkit.IsFeatureActive("hideOutOfRangeErrors") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        print("  Automation:")
        print("  Auto Gossip: " .. (SinsToolkit.IsFeatureActive("automateGossip") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r") .. " (" .. (SinsToolkitDB.automateGossipModifier or "SHIFT") .. ")")
        print("  Auto Unshift for Druids: " .. (SinsToolkit.IsFeatureActive("gseAutoUnshift") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        print("  Druid Auto Flight Form: " .. (SinsToolkit.IsFeatureActive("druidAutoFlightForm") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        print("  Identifier Hover Mode: " .. (SinsToolkit.IsFeatureActive("identifierEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        local bugCatcherBlocked = SinsToolkit.BugCatcher and SinsToolkit.BugCatcher.IsBlockedByExternalAddon and SinsToolkit.BugCatcher:IsBlockedByExternalAddon()
        print("  Profession Hotkey: " .. (SinsToolkit.IsFeatureActive("professionItemHotkeyEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r") .. " (" .. (SinsToolkitDB.professionItemHotkeyModifier or "ALT") .. ")")
        print("  Quick Links: " .. (SinsToolkit.IsFeatureActive("wowheadSearchEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        print("  Teleport Hub: " .. (SinsToolkit.IsFeatureActive("teleportHubEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
        print("  Bug Catcher: " .. (bugCatcherBlocked and "|cffff4040Disabled by BugSack|r" or (SinsToolkit.IsFeatureActive("bugCatcherEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r")))
    elseif msg == "" then
        if SinsToolkit.Standalone and SinsToolkit.Standalone.ToggleOptions then SinsToolkit.Standalone:ToggleOptions() end
    else
        print("|cff9b59b6Sin's Toolkit: QOL|r commands")
        print("  |cffd8a84a/stk|r - Open Sin's Toolkit: QOL options.")
        print("  |cffd8a84a/stk status|r - Show Toolkit feature status.")
        print("  |cffd8a84a/stk scan|r - Run a manual gear scan.")
        print("  |cffd8a84a/stkid|r - Identify the UI frame under your cursor.")
        print("  |cffd8a84a/stkbug|r - Show the last captured Lua error.")
        print("  |cffd8a84a/stktp|r - Open the Teleport Hub.")
        print("  |cffd8a84a/stktp view|r - Switch Teleport Hub between grid and list view.")
    end
end

Core:RegisterEvent("ADDON_LOADED")
Core:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" and ... == addonName then
        self:UnregisterEvent("ADDON_LOADED")
        SinsToolkit.EnsureDB()
        SinsToolkit.ApplyDependencyGuards(true)
        ---------------------------------------------------------------------------
        --  Hide UI Error Messages
        ---------------------------------------------------------------------------
        do
            local messageInfoCache = {}

            local function IsBlockedUIError(id, err)
                if not err then return false end
                local blocked = SinsToolkitDB and SinsToolkitDB.blockedUIErrors
                if not blocked then return false end

                local stringID = messageInfoCache[id]
                if stringID == nil then
                    stringID = (GetGameMessageInfo and id and GetGameMessageInfo(id)) or false
                    messageInfoCache[id] = stringID
                end

                if stringID and blocked[stringID] then return true end
                for blockedStringID, isBlocked in pairs(blocked) do
                    if isBlocked and _G[blockedStringID] == err then
                        return true
                    end
                end
                if ERR_TOO_FAR_TO_INTERACT and err == ERR_TOO_FAR_TO_INTERACT then
                    return blocked.ERR_TOO_FAR_TO_INTERACT == true
                end
                return err == "You need to be closer to interact with that target."
                    and blocked.ERR_TOO_FAR_TO_INTERACT == true
            end

            local function IsAutoUnshiftUIError(id, err)
                if not err then return false end
                if not (SinsToolkit.IsFeatureActive and SinsToolkit.IsFeatureActive("gseAutoUnshift")) then return false end
                if SinsToolkit.IsAutoUnshiftFormActive and not SinsToolkit.IsAutoUnshiftFormActive() then return false end

                local stringID = messageInfoCache[id]
                if stringID == nil then
                    stringID = (GetGameMessageInfo and id and GetGameMessageInfo(id)) or false
                    messageInfoCache[id] = stringID
                end

                return stringID == "SPELL_FAILED_NOT_SHAPESHIFT"
                    or (SPELL_FAILED_NOT_SHAPESHIFT and err == SPELL_FAILED_NOT_SHAPESHIFT)
                    or err == "You are in shapeshift form."
            end

            if UIErrorsFrame and not SinsToolkit.UIErrorFilterInstalled then
                SinsToolkit.UIErrorFilterInstalled = true

                -- Forward only unfiltered UI error events through Blizzard's display path.
                -- This preserves normal UIErrorsFrame throttling and sound for messages
                -- the user has not selected while allowing selected messages to vanish.
                UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE")
                local errorFilterFrame = CreateFrame("Frame")
                errorFilterFrame:RegisterEvent("UI_ERROR_MESSAGE")
                errorFilterFrame:SetScript("OnEvent", function(_, _, id, err)
                    if not err then return end
                    if IsAutoUnshiftUIError(id, err) then return end
                    if SinsToolkit.IsFeatureActive
                        and SinsToolkit.IsFeatureActive("hideOutOfRangeErrors")
                        and IsBlockedUIError(id, err) then
                        return
                    end
                    if UIErrorsFrame.TryDisplayMessage then
                        UIErrorsFrame:TryDisplayMessage(id, err, RED_FONT_COLOR:GetRGB())
                    else
                        UIErrorsFrame:AddMessage(err, 1.0, 0.1, 0.1, 1.0)
                    end
                end)
                SinsToolkit.UIErrorFilterFrame = errorFilterFrame
            end
        end

        ---------------------------------------------------------------------------
        --  Auto Unshift for Druids
        ---------------------------------------------------------------------------
        -- Implemented in SinsToolkit_AutoUnshift.lua.
        ---------------------------------------------------------------------------
        --  Keep Panels Open
        ---------------------------------------------------------------------------
        -- Implemented in SinsToolkit_NoAutoClose.lua.
        ---------------------------------------------------------------------------
        --  Automate Gossip
        ---------------------------------------------------------------------------
        do
            local f = CreateFrame("Frame")
            f:RegisterEvent("GOSSIP_SHOW")

            local AUTO_GOSSIP_ALLOWED_NPCS = {
                ["104201"] = true, -- Katy Stampwhistle
                ["132969"] = true, -- Katy Stampwhistle
            }

            local AUTO_GOSSIP_BLOCKED_TEXT = {
                "auction",
                "bank",
                "barber",
                "directions",
                "flight master",
                "guild master",
                "inn",
                "mailbox",
                "profession",
                "stable",
                "story",
                "tale",
                "tell me",
                "trainer",
                "transmogr",
                "void storage",
                "where can",
                "where is",
            }

            local function IsSecretValue(value)
                return issecretvalue and issecretvalue(value) == true
            end

            local function GetNpcID()
                if not UnitGUID then return nil end
                local ok, guid = pcall(UnitGUID, "npc")
                if not ok or not guid then return nil end
                if IsSecretValue(guid) then return nil end
                local _, _, _, _, _, npcID = strsplit("-", guid)
                return npcID
            end

            local function IsBlockedGossipText(text)
                if IsSecretValue(text) then return true end
                text = text and text:lower()
                if not text or text == "" then return false end
                for _, pattern in ipairs(AUTO_GOSSIP_BLOCKED_TEXT) do
                    if text:find(pattern, 1, true) then return true end
                end
                return false
            end

            local function SelectGossipOption(option)
                if option and option.gossipOptionID then
                    C_GossipInfo.SelectOption(option.gossipOptionID)
                elseif GossipFrame and GossipFrame:IsShown() and GossipFrame.SelectGossipOption then
                    GossipFrame:SelectGossipOption(1)
                end
            end

            local function GetGossipQuestCount(getTableFunc, getCountFunc)
                if getTableFunc then
                    local quests = getTableFunc()
                    return type(quests) == "table" and #quests or 0
                end
                if getCountFunc then
                    return getCountFunc() or 0
                end
                return 0
            end

            f:SetScript("OnEvent", function()
                if not (SinsToolkit.IsFeatureActive and SinsToolkit.IsFeatureActive("automateGossip")) then return end

                local modifier = SinsToolkitDB.automateGossipModifier or "SHIFT"
                if modifier == "SHIFT" and IsShiftKeyDown() then return end
                if modifier == "CTRL" and IsControlKeyDown() then return end
                if modifier == "ALT" and IsAltKeyDown() then return end

                local options = C_GossipInfo.GetOptions()
                if not options or not options[1] then return end

                local npcID = GetNpcID()
                if npcID and AUTO_GOSSIP_ALLOWED_NPCS[npcID] then
                    SelectGossipOption(options[1])
                    return
                end

                if #options == 1
                    and GetGossipQuestCount(C_GossipInfo.GetAvailableQuests, C_GossipInfo.GetNumAvailableQuests) == 0
                    and GetGossipQuestCount(C_GossipInfo.GetActiveQuests, C_GossipInfo.GetNumActiveQuests) == 0
                    and not IsBlockedGossipText(options[1].name)
                then
                    SelectGossipOption(options[1])
                end
            end)
        end

        ---------------------------------------------------------------------------
        --  Wowhead Quick Link
        ---------------------------------------------------------------------------
        -- Implemented in SinsToolkit_WowheadQuickLink*.lua.
    end
end)
