local addonName, SinsToolkit = ...
local Core = CreateFrame("Frame")
SinsToolkit.Core = Core

local DB_VERSION = 2

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,
        },
    },
    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,
    bugCatcherMaxErrors = 100,
    bugCatcherSound = "NONE",
    bugCatcherBugSackCompatibility = false,
    bugCatcherErrors = {},
    teleportHubEnabled = false,
    teleportHubMinimap = true,
    teleportHubShowLocked = false,
    teleportHubShowDungeonNames = true,
    teleportHubView = "grid",
    teleportHubWindow = {
        width = 760,
        height = 620,
        point = "CENTER",
        relativePoint = "CENTER",
        x = 0,
        y = 0,
    },
    blockedUIErrors = {},
    exportStrings = {},
    profileVaultBackups = {},
    featureOwners = {},
}

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

    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 (PawnShouldItemLinkHaveUpgradeArrow or (PawnGetItemData and PawnIsItemAnUpgrade)) ~= nil
end

function SinsToolkit.IsGearScannerProviderAvailable()
    return SinsToolkit.IsPawnAvailable() or SinsToolkit.IsClassCodexAvailable()
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.exportStrings = SinsToolkitDB.exportStrings 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)
end

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

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

    if SinsToolkitDB.gseAutoUnshift and not SinsToolkit.IsGSEAvailable() then
        SinsToolkitDB.gseAutoUnshift = false
        if printNotice then
            print("|cff9b59b6[Sin's Toolkit]|r Auto Unshift for Druids disabled because GSE 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

function SinsToolkit.IsFeatureOwnedByToolkit(featureKey)
    return SinsToolkit.GetFeatureOwner(featureKey) == "SINS"
end

function SinsToolkit.IsFeatureActive(featureKey)
    SinsToolkit.EnsureDB()
    if featureKey == "gearScannerEnabled" and not SinsToolkit.IsGearScannerProviderAvailable() then return false end
    if featureKey == "gseAutoUnshift" and not SinsToolkit.IsGSEAvailable() 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]|r Gear Scanner requires Pawn or ClassCodex.")
        return
    end
    if dbKey == "gseAutoUnshift" and SinsToolkitDB.gseAutoUnshift and not SinsToolkit.IsGSEAvailable() then
        SinsToolkitDB.gseAutoUnshift = false
        print("|cff9b59b6[Sin's Toolkit]|r Auto Unshift for Druids requires GSE, also known as Gnome Sequencer.")
        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 == "keepPanelsOpen" then
        if SinsToolkit.ApplyNoAutoClosePanels then SinsToolkit.ApplyNoAutoClosePanels() end
        print("|cff9b59b6[Sin's Toolkit]|r No Auto Close Panels updated. Reload your UI after disabling it.")
    end
end

Core:RegisterEvent("PLAYER_LOGIN")
Core:SetScript("OnEvent", function(self, event, ...)
    if event == "PLAYER_LOGIN" then
        self:UnregisterEvent("PLAYER_LOGIN")
        SinsToolkit.EnsureDB()
        SinsToolkit.ApplyDependencyGuards(true)

        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]|r Gear Scanner is unavailable or currently disabled.")
                end
            elseif msg == "status" then
                print("|cff9b59b6Sin's Toolkit|r status")
                print("  Gear Scanner: " .. (SinsToolkit.IsFeatureActive("gearScannerEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
                print("  Auto Gossip: " .. (SinsToolkit.IsFeatureActive("automateGossip") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r") .. " (" .. (SinsToolkitDB.automateGossipModifier or "SHIFT") .. ")")
                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("  Bug Catcher: " .. (bugCatcherBlocked and "|cffff4040Disabled by BugSack|r" or (SinsToolkit.IsFeatureActive("bugCatcherEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r")))
                print("  Teleport Hub: " .. (SinsToolkit.IsFeatureActive("teleportHubEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
                print("  Profession Hotkey: " .. (SinsToolkit.IsFeatureActive("professionItemHotkeyEnabled") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r") .. " (" .. (SinsToolkitDB.professionItemHotkeyModifier or "ALT") .. ")")
                print("  Auto Unshift for Druids: " .. (SinsToolkit.IsFeatureActive("gseAutoUnshift") and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))
            else
                print("|cff9b59b6Sin's Toolkit|r commands")
                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.")
                print("  |cffd8a84a/ec|r - Open ElvUI and select the Sin's Toolkit plugin tab.")
            end
        end

        ---------------------------------------------------------------------------
        --  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

                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 GetNpcID()
                if not UnitGUID then return nil end
                local ok, guid = pcall(UnitGUID, "npc")
                if not ok or not guid then return nil end
                local _, _, _, _, _, npcID = strsplit("-", guid)
                return npcID
            end

            local function IsBlockedGossipText(text)
                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)


