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

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

if IsProviderAddonLoaded("ElvUI") then
    SinsToolkit.UIProvider = "ElvUI"
elseif IsProviderAddonLoaded("EllesmereUI") then
    SinsToolkit.UIProvider = "EllesmereUI"
else
    SinsToolkit.UIProvider = "Standalone"
end

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 = {},
    frameMoverConflictIgnore = false,
    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 = {},
    teleportHubEnabled = false,
    teleportHubMinimap = true,
    teleportHubKeybind = "",
    teleportHubShowLocked = false,
    teleportHubShowDungeonNames = true,
    teleportHubView = "menu",
    teleportHubSelected = "",
    teleportHubFavorites = {},
    teleportHubRecent = {},
    teleportHubCollapsed = {},
    teleportHubWindow = {
        width = 760,
        height = 620,
        point = "CENTER",
        relativePoint = "CENTER",
        x = 0,
        y = 0,
    },
    blockedUIErrors = {},
    profileVaultBackups = {},
    featureOwners = {},
    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 WindTools skinning.
        -- 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
        if db.featureOwners then
            db.featureOwners.frameStylerEnabled = nil
            db.featureOwners.frameStylerAddonSkins = nil
        end
    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
        if db.frameMoverConflictIgnore == nil then
            db.frameMoverConflictIgnore = false
        end
    end

    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.featureOwners = SinsToolkitDB.featureOwners 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 {}
    SinsToolkitDB.teleportHubCollapsed = SinsToolkitDB.teleportHubCollapsed or {}
    SinsToolkitDB.teleportHubSelected = SinsToolkitDB.teleportHubSelected or ""
    SinsToolkitDB.minimap = SinsToolkitDB.minimap or CopyDefaults(DB_DEFAULTS.minimap)
end

function SinsToolkit.GetProviderDisplayName()
    if SinsToolkit.UIProvider == "ElvUI" then return "ElvUI Plugin" end
    if SinsToolkit.UIProvider == "EllesmereUI" then return "EllesmereUI-compatible" end
    return "Standalone"
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,
    }

    if SinsToolkit.UIProvider == "EllesmereUI" and _G.EllesmereUI then
        local r, g, b
        if EllesmereUI.ResolveActiveAccent then
            r, g, b = EllesmereUI.ResolveActiveAccent()
        elseif EllesmereUI.ELLESMERE_GREEN then
            r, g, b = EllesmereUI.ELLESMERE_GREEN.r, EllesmereUI.ELLESMERE_GREEN.g, EllesmereUI.ELLESMERE_GREEN.b
        elseif EllesmereUI.ResolveThemeColor and EllesmereUI.GetActiveTheme then
            r, g, b = EllesmereUI.ResolveThemeColor(EllesmereUI.GetActiveTheme())
        end
        if type(r) == "number" and type(g) == "number" and type(b) == "number" then
            theme.accentR, theme.accentG, theme.accentB = r, g, b
        end
        theme.bgR, theme.bgG, theme.bgB, theme.bgA = 0.015, 0.018, 0.022, 0.90
        theme.panelScale = (EllesmereUIDB and tonumber(EllesmereUIDB.panelScale)) or 1
    end

    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.GetFeatureOwner(featureKey)
    SinsToolkit.EnsureDB()
    return SinsToolkitDB.featureOwners[featureKey] or "SINS"
end

function SinsToolkit.SetFeatureOwner(featureKey, owner)
    SinsToolkit.EnsureDB()
    SinsToolkitDB.featureOwners[featureKey] = owner == "SINS" and nil or owner
end

local FEATURE_CONFLICTS = {
    keepPanelsOpen = { addon = "NoAutoClose", label = "NoAutoClose" },
    frameMoverEnabled = {
        addons = {
            { addon = "EnhanceQoL", label = "Enhance QoL Core" },
            { addon = "BlizzMove", label = "BlizzMove" },
        },
    },
}

local function GetLoadedFeatureConflictAddons(featureKey)
    local conflict = FEATURE_CONFLICTS[featureKey]
    if not conflict then return {}, {} end

    local entries = conflict.addons or { conflict }
    local loaded, known = {}, {}
    for _, entry in ipairs(entries) do
        known[#known + 1] = entry.label
        if IsAddonLoaded(entry.addon) then
            loaded[#loaded + 1] = entry.label
        end
    end
    return loaded, known
end

local function JoinLabels(labels)
    if #labels == 0 then return "" end
    if #labels == 1 then return labels[1] end
    return table.concat(labels, " / ")
end

function SinsToolkit.IsFeatureOwnedByToolkit(featureKey)
    local owner = SinsToolkit.GetFeatureOwner(featureKey)
    if owner == "SINS" then return true end

    local loaded = GetLoadedFeatureConflictAddons(featureKey)
    if #loaded == 0 then
        return true
    end

    return false
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.GetFeatureConflictInfo(featureKey)
    local info = FEATURE_CONFLICTS[featureKey]
    if not info then return nil end
    local loadedAddons, knownAddons = GetLoadedFeatureConflictAddons(featureKey)
    return {
        addon = info.addon,
        label = JoinLabels(loadedAddons) ~= "" and JoinLabels(loadedAddons) or JoinLabels(knownAddons),
        loaded = #loadedAddons > 0,
        loadedAddons = loadedAddons,
        knownAddons = knownAddons,
        owner = SinsToolkit.GetFeatureOwner(featureKey),
    }
end

function SinsToolkit.GetFeatureConflictText(featureKey)
    local info = SinsToolkit.GetFeatureConflictInfo(featureKey)
    if not info then return "" end
    if not info.loaded then
        return "No matching external addon loaded. Sin's Toolkit owns this feature. Checked for: " .. (JoinLabels(info.knownAddons) or "")
    end
    if info.owner == "SINS" then
        return info.label .. " is loaded, but Sin's Toolkit is selected as the active owner. Disable the other addon's matching mover feature, or disable that addon and reload, to avoid duplicate frame-moving behavior."
    end
    return info.label .. " is loaded and currently owns this feature. Sin's Toolkit will disable its matching behavior."
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 No Auto Close Panels 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("  No Auto Close Panels: " .. (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.UIProvider == "ElvUI" then
            if _G.ElvUI and _G.ElvUI[1] and _G.ElvUI[1].ToggleConfig then _G.ElvUI[1]:ToggleConfig("sinsToolkit") end
        else
            if SinsToolkit.Standalone and SinsToolkit.Standalone.ToggleOptions then SinsToolkit.Standalone:ToggleOptions() end
        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.")
        if SinsToolkit.UIProvider == "ElvUI" then print("  |cffd8a84a/ec|r - Open ElvUI and select the Sin's Toolkit: QOL plugin tab.") end
    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

                -- Use a dedicated forwarding frame instead of replacing UIErrorsFrame's OnEvent script.
                -- This keeps Sin's Toolkit from breaking other addons that also hook Blizzard's error frame.
                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
                    UIErrorsFrame:AddMessage(err, 1.0, 0.1, 0.1, 1.0)
                end)
            end
        end

        ---------------------------------------------------------------------------
        --  Auto Unshift for Druids
        ---------------------------------------------------------------------------
        -- Implemented in SinsToolkit_AutoUnshift.lua.
        ---------------------------------------------------------------------------
        --  No Auto Close Panels
        ---------------------------------------------------------------------------
        -- 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)
