--[[
============================================================================
ETB - Init.lua
============================================================================
Core initialization.
Includes debug hooks and enhanced logging.
============================================================================
]]--

local ADDON_NAME, NS = ...

-- Create global namespace
NS.ETB = NS.ETB or {}
local ETB = NS.ETB

ETB.version = "15.8.14"
ETB.debug = false  -- Set true for development, false for clean chat
ETB.initialized = false

-- v12.43.0: removed _G.ETB_Addon and _G.ETB_NS globals. These were published
-- to bridge to the ETB_ERB sibling LoadOnDemand addon (v12.39.0–v12.40.2).
-- Since v12.41.0 merged ERB into this addon as a subfolder, all sibling-addon
-- bridging code is dead. Submodules access the namespace via the standard
-- `local ADDON_NAME, NS = ...` pattern.

-- ============================================================================
-- LAZY LOADERS FOR SIBLING ADDONS
-- ============================================================================
--
-- ERB (Enhanced Raid Browser) was merged back into this addon in v12.40.3
-- (previously shipped as the LoadOnDemand sibling ETB_ERB in v12.39.0-v12.40.2).
-- The folder `ERB/` inside this addon contains all ERB files; they load in
-- TOC order after the main Core, so ETB.ERB is populated at addon load time.
--
-- Callers can use either `ETB.ERB` directly or `ETB:GetERB()` for consistency
-- with the previous lazy-load API. Both return the same handle.
-- ============================================================================

function ETB:GetERB()
    -- ERB is now always loaded. The function is preserved for call-site
    -- compatibility with pre-v12.40.3 code that used it as a lazy loader.
    return ETB.ERB
end

-- Binding localization strings (required for keybinding UI)
BINDING_HEADER_ETBBINDING = "Enhanced Transmog Browser"
BINDING_NAME_ETB_TOGGLE_BROWSER = "Toggle ETB Browser"
BINDING_NAME_ETB_TOGGLE_TRANSMOGUI = "Toggle TransmogUI"

-- ============================================================================
-- C_TIMER FALLBACK FOR MOP CLASSIC
-- ============================================================================

if not C_Timer then
    C_Timer = {
        After = function(delay, callback)
            local frame = CreateFrame("Frame")
            frame.elapsed = 0
            frame:SetScript("OnUpdate", function(self, elapsed)
                self.elapsed = self.elapsed + elapsed
                if self.elapsed >= delay then
                    self:SetScript("OnUpdate", nil)
                    callback()
                end
            end)
        end,
        NewTicker = function(interval, callback, iterations)
            local ticker = { cancelled = false, iterations = iterations or -1, count = 0 }
            local frame = CreateFrame("Frame")
            frame.elapsed = 0
            frame:SetScript("OnUpdate", function(self, elapsed)
                if ticker.cancelled then
                    self:SetScript("OnUpdate", nil)
                    return
                end
                self.elapsed = self.elapsed + elapsed
                if self.elapsed >= interval then
                    self.elapsed = 0
                    ticker.count = ticker.count + 1
                    callback(ticker)
                    if ticker.iterations > 0 and ticker.count >= ticker.iterations then
                        ticker.cancelled = true
                        self:SetScript("OnUpdate", nil)
                    end
                end
            end)
            function ticker:Cancel() self.cancelled = true end
            return ticker
        end,
    }
end

-- ============================================================================
-- SAFE TOOLTIP HELPER
-- Prevents errors from third-party tooltip hooks
-- that crash on uncached items. Only calls SetHyperlink when the item
-- data is already cached by the client.
-- ============================================================================

function ETB:SafeSetTooltipItem(tooltip, itemID)
    if not tooltip or not itemID or type(itemID) ~= "number" or itemID <= 0 then
        return false
    end
    -- Only use SetHyperlink if item is fully cached.
    -- GetItemInfo returns nil for uncached items, which causes third-party
    -- tooltip hooks to crash on nil itemID.
    local name, link = GetItemInfo(itemID)
    if name and link then
        -- Item is fully cached — safe to use hyperlink (triggers tooltip hooks safely)
        local ok = pcall(tooltip.SetHyperlink, tooltip, link)
        return ok
    else
        -- Item NOT fully cached — build a manual tooltip from instant data.
        -- On MoP Classic 5.4.8, GetItemInfoInstant returns
        --   (itemID, itemType, itemSubType, equipLoc, icon, classID, subclassID)
        -- The first return is the NUMERIC itemID, not a name. Earlier code
        -- displayed that number as the tooltip "name" (so the tooltip would
        -- show e.g. "12345" instead of an item title). There's no synchronous
        -- way to get the real string name when GetItemInfo hasn't cached it
        -- yet, so we fall back to "Item #N" cleanly. Triggering GetItemInfo
        -- below populates the cache for next hover.
        tooltip:SetText("Item #" .. itemID)
        tooltip:AddLine("|cff888888Hover again for full tooltip|r")
        -- GetItemInfo triggers a background cache request for next hover
        GetItemInfo(itemID)
        return false
    end
end

-- Available via NS.ETB:SafeSetTooltipItem(tooltip, itemID)
-- NOTE: No _G global — writing to _G taints secure action paths

-- ============================================================================
-- CATEGORY DEFINITIONS
-- MoP Classic uses categories 1-11 for armor slots, 12-27 for weapon types
-- Discovered via /etb categories command
-- ============================================================================

ETB.CATEGORY_INFO = {
    -- Armor slots (1-11) - confirmed
    [1]  = { name = "Head",        slot = "INVTYPE_HEAD",           icon = "Interface\\Icons\\INV_Helmet_44",           isArmor = true },
    [2]  = { name = "Shoulder",    slot = "INVTYPE_SHOULDER",       icon = "Interface\\Icons\\INV_Shoulder_22",         isArmor = true },
    [3]  = { name = "Back",        slot = "INVTYPE_CLOAK",          icon = "Interface\\Icons\\INV_Misc_Cape_18",        isArmor = true },
    [4]  = { name = "Chest",       slot = "INVTYPE_CHEST",          icon = "Interface\\Icons\\INV_Chest_Chain_05",      isArmor = true },
    [5]  = { name = "Shirt",       slot = "INVTYPE_BODY",           icon = "Interface\\Icons\\INV_Shirt_01",            isArmor = true },
    [6]  = { name = "Tabard",      slot = "INVTYPE_TABARD",         icon = "Interface\\Icons\\INV_Shirt_GuildTabard_01",isArmor = true },
    [7]  = { name = "Wrist",       slot = "INVTYPE_WRIST",          icon = "Interface\\Icons\\INV_Bracer_07",           isArmor = true },
    [8]  = { name = "Hands",       slot = "INVTYPE_HAND",           icon = "Interface\\Icons\\INV_Gauntlets_24",        isArmor = true },
    [9]  = { name = "Waist",       slot = "INVTYPE_WAIST",          icon = "Interface\\Icons\\INV_Belt_13",             isArmor = true },
    [10] = { name = "Legs",        slot = "INVTYPE_LEGS",           icon = "Interface\\Icons\\INV_Pants_06",            isArmor = true },
    [11] = { name = "Feet",        slot = "INVTYPE_FEET",           icon = "Interface\\Icons\\INV_Boots_05",            isArmor = true },
    
    -- Weapon types (12-27) - discovered via /etb categories
    -- One-Handed Melee
    [12] = { name = "Wands",       slot = "INVTYPE_RANGEDRIGHT",    icon = "Interface\\Icons\\INV_Wand_01",             isWeapon = true, weaponType = "WAND", count = 241 },
    [13] = { name = "1H Axes",     slot = "INVTYPE_WEAPON",         icon = "Interface\\Icons\\INV_Axe_01",              isWeapon = true, weaponType = "AXE", count = 256 },
    [14] = { name = "1H Swords",   slot = "INVTYPE_WEAPON",         icon = "Interface\\Icons\\INV_Sword_04",            isWeapon = true, weaponType = "SWORD", count = 399 },
    [15] = { name = "1H Maces",    slot = "INVTYPE_WEAPON",         icon = "Interface\\Icons\\INV_Mace_01",             isWeapon = true, weaponType = "MACE", count = 390 },
    [16] = { name = "Daggers",     slot = "INVTYPE_WEAPON",         icon = "Interface\\Icons\\INV_Weapon_ShortBlade_05",isWeapon = true, weaponType = "DAGGER", count = 452 },
    [17] = { name = "Fist Weapons",slot = "INVTYPE_WEAPON",         icon = "Interface\\Icons\\INV_Gauntlets_04",        isWeapon = true, weaponType = "FIST", count = 148 },
    -- Off-Hand
    [18] = { name = "Shields",     slot = "INVTYPE_SHIELD",         icon = "Interface\\Icons\\INV_Shield_04",           isWeapon = true, weaponType = "SHIELD", count = 368 },
    [19] = { name = "Off-Hands",   slot = "INVTYPE_HOLDABLE",       icon = "Interface\\Icons\\INV_Misc_Orb_01",         isWeapon = true, weaponType = "HOLDABLE", count = 263 },
    -- Two-Handed Melee
    [20] = { name = "2H Axes",     slot = "INVTYPE_2HWEAPON",       icon = "Interface\\Icons\\INV_Axe_09",              isWeapon = true, weaponType = "AXE", isTwoHanded = true, count = 210 },
    [21] = { name = "2H Swords",   slot = "INVTYPE_2HWEAPON",       icon = "Interface\\Icons\\INV_Sword_27",            isWeapon = true, weaponType = "SWORD", isTwoHanded = true, count = 212 },
    [22] = { name = "2H Maces",    slot = "INVTYPE_2HWEAPON",       icon = "Interface\\Icons\\INV_Mace_15",             isWeapon = true, weaponType = "MACE", isTwoHanded = true, count = 200 },
    [23] = { name = "Staves",      slot = "INVTYPE_2HWEAPON",       icon = "Interface\\Icons\\INV_Staff_08",            isWeapon = true, weaponType = "STAFF", isTwoHanded = true, count = 428 },
    [24] = { name = "Polearms",    slot = "INVTYPE_2HWEAPON",       icon = "Interface\\Icons\\INV_Spear_04",            isWeapon = true, weaponType = "POLEARM", isTwoHanded = true, count = 152 },
    -- Ranged
    [25] = { name = "Bows",        slot = "INVTYPE_RANGED",         icon = "Interface\\Icons\\INV_Weapon_Bow_05",       isWeapon = true, weaponType = "BOW", count = 165 },
    [26] = { name = "Guns",        slot = "INVTYPE_RANGEDRIGHT",    icon = "Interface\\Icons\\INV_Weapon_Rifle_01",     isWeapon = true, weaponType = "GUN", count = 150 },
    [27] = { name = "Crossbows",   slot = "INVTYPE_RANGEDRIGHT",    icon = "Interface\\Icons\\INV_Weapon_Crossbow_01",  isWeapon = true, weaponType = "CROSSBOW", count = 87 },
}

ETB.SLOT_TO_CATEGORY = {}
for categoryID, info in pairs(ETB.CATEGORY_INFO) do
    ETB.SLOT_TO_CATEGORY[info.slot] = categoryID
end

-- ============================================================================
-- SOURCE TYPES
-- ============================================================================

ETB.SOURCE_TYPES = {
    { id = "all",        name = "All Sources",   color = "|cffffffff" },
    { id = "boss",       name = "Boss (any)",    color = "|cffff7f3f" },
    { id = "raid",       name = "Raid",          color = "|cffa335ee" },
    { id = "dungeon",    name = "Dungeon",       color = "|cff0070dd" },
    { id = "quest",      name = "Quest",         color = "|cffffff00" },
    { id = "vendor",     name = "Vendor",        color = "|cff1eff00" },
    { id = "crafted",    name = "Crafted",       color = "|cffff8000" },
    { id = "pvp",        name = "PvP",           color = "|cffff0000" },
    { id = "world_drop", name = "World Drop",    color = "|cff9d9d9d" },
}

-- ============================================================================
-- EXPANSION DATA
-- ============================================================================

ETB.EXPANSIONS = {
    { id = "all",     name = "All Expansions" },
    { id = "classic", name = "Classic" },
    { id = "tbc",     name = "Burning Crusade" },
    { id = "wrath",   name = "Wrath of the Lich King" },
    { id = "cata",    name = "Cataclysm" },
    { id = "mop",     name = "Mists of Pandaria" },
}

-- ============================================================================
-- QUALITY DATA
-- ============================================================================

ETB.QUALITY_COLORS = {
    [0] = { r = 0.62, g = 0.62, b = 0.62, hex = "|cff9d9d9d", name = "Poor" },
    [1] = { r = 1.00, g = 1.00, b = 1.00, hex = "|cffffffff", name = "Common" },
    [2] = { r = 0.12, g = 1.00, b = 0.00, hex = "|cff1eff00", name = "Uncommon" },
    [3] = { r = 0.00, g = 0.44, b = 0.87, hex = "|cff0070dd", name = "Rare" },
    [4] = { r = 0.64, g = 0.21, b = 0.93, hex = "|cffa335ee", name = "Epic" },
    [5] = { r = 1.00, g = 0.50, b = 0.00, hex = "|cffff8000", name = "Legendary" },
    [6] = { r = 0.90, g = 0.80, b = 0.50, hex = "|cffe6cc80", name = "Artifact" },
    [7] = { r = 0.00, g = 0.80, b = 1.00, hex = "|cff00ccff", name = "Heirloom" },
}

-- ============================================================================
-- UTILITY FUNCTIONS
-- ============================================================================

function ETB:Print(msg)
    -- User-facing output — ALWAYS prints
    DEFAULT_CHAT_FRAME:AddMessage("|cff00ccff[ETB]|r " .. tostring(msg))
end

function ETB:PrintError(msg)
    DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[ETB Error]|r " .. tostring(msg))
end

function ETB:PrintWarning(msg)
    DEFAULT_CHAT_FRAME:AddMessage("|cffffff00[ETB Warning]|r " .. tostring(msg))
end

function ETB:Debug(module, msg)
    if self.debug then
        print("|cff00ffff[ETB:" .. tostring(module) .. "]|r " .. tostring(msg))
    end
end

function ETB:GetQualityColor(quality)
    -- Ensure quality is a number
    if quality and type(quality) ~= "number" then
        quality = 1  -- Default to Common
    end
    local color = self.QUALITY_COLORS[quality or 1]
    if not color then
        color = self.QUALITY_COLORS[1]  -- Fallback to Common
    end
    return color and color.hex or "|cffffffff"
end

function ETB:GetQualityRGB(quality)
    -- Ensure quality is a number
    if quality and type(quality) ~= "number" then
        quality = 1  -- Default to Common
    end
    local color = self.QUALITY_COLORS[quality or 1]
    if not color then
        color = self.QUALITY_COLORS[1]  -- Fallback to Common
    end
    return color.r, color.g, color.b
end

function ETB:GetQualityName(quality)
    local color = self.QUALITY_COLORS[quality or 1]
    return color and color.name or "Unknown"
end

function ETB:GetMemoryUsage()
    UpdateAddOnMemoryUsage()
    return GetAddOnMemoryUsage(ADDON_NAME)
end

function ETB:FormatMemory(kb)
    if kb >= 1024 then
        return string.format("%.1f MB", kb / 1024)
    else
        return string.format("%.0f KB", kb)
    end
end

function ETB:FormatNumber(num)
    if not num then return "0" end
    local formatted = tostring(math.floor(num))
    while true do
        formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
        if k == 0 then break end
    end
    return formatted
end

function ETB:FormatPercent(value, decimals)
    decimals = decimals or 1
    return string.format("%." .. decimals .. "f%%", value or 0)
end

-- Get current player info
function ETB:GetPlayerInfo()
    local _, class = UnitClass("player")
    local race = select(2, UnitRace("player"))
    local level = UnitLevel("player")
    local name = UnitName("player")
    local realm = GetRealmName()
    
    return {
        name = name,
        realm = realm,
        class = class,
        race = race,
        level = level,
        key = name .. "-" .. realm,
    }
end

-- ============================================================================
-- INITIALIZATION
-- ============================================================================

function ETB:OnAddonLoaded()
    -- Initialize SavedVariables
    ETB_Settings = ETB_Settings or {}
    ETB_Favorites = ETB_Favorites or {}
    ETB_MinimapSettings = ETB_MinimapSettings or { show = true, position = 195 }
    ETB_CharDB = ETB_CharDB or {}
    ETB_Debug = ETB_Debug or { enabled = true, logLevel = 3 }
    ETB_RuntimeNPCNames = ETB_RuntimeNPCNames or {}
    
    -- Apply settings
    if ETB.Settings then
        ETB.Settings:Load()
    end
    
    -- Version banner
    -- Startup banner stripped for clean chat
end

function ETB:OnPlayerLogin()
    -- Delayed initialization
    -- STAGGERED initialization to prevent login freeze
    -- Phase 1 (login+1s): lightweight modules only
    C_Timer.After(1, function()
        if ETB.BlizzardData then
            ETB.BlizzardData:Initialize()
        end
        
        if ETB.MinimapButton then
            ETB.MinimapButton:Initialize()
        end
        
        if ETB.TooltipEnhancer then
            ETB.TooltipEnhancer:Initialize()
        end
        
        -- v15.6.0: B2 bag overlay markers
        if ETB.BagOverlay then
            ETB.BagOverlay:Initialize()
        end
        
        if ETB.Wishlist then
            ETB.Wishlist:Initialize()
        end
        
        if ETB.CollectionTooltip then
            ETB.CollectionTooltip:Initialize()
        end
        
        if ETB.RuntimeNPCNames then
            ETB.RuntimeNPCNames:Initialize()
        end

        if ETB.CollectionHistory then
            ETB.CollectionHistory:Initialize()
        end

        -- v14.6.0 Tier 2 modules
        if ETB.HiddenAppearances then
            ETB.HiddenAppearances:Initialize()
        end
        if ETB.SavedSearches then
            ETB.SavedSearches:Initialize()
        end
        if ETB.CollectionToast then
            ETB.CollectionToast:Initialize()
        end

        if ETB.ChatAutocomplete then
            ETB.ChatAutocomplete:Initialize()
        end

        ETB:Debug("Init", "Phase 1 complete (lightweight modules)")
    end)
    
    -- Phase 2 (login+3s): heavy data loading
    C_Timer.After(3, function()
        -- v12.42.0: SourceData_ATT is no longer eagerly initialized here.
        -- Its EnsureLoaded() gets called lazily on first access via public
        -- methods (GetPrimarySource, HasSource, GetCount, etc.). Combined
        -- with lazy-loaded Data/Compact/*.lua files, this defers ~45 MB of
        -- Compact data population until the first tooltip, browser query,
        -- or boss mouseover that actually needs it.
        --
        -- Users who never interact with ETB features pay zero cost for
        -- Compact item data. First user interaction triggers a ~140ms
        -- one-time load (visible as a brief first-hover hitch).

        if ETB.SourceMapper then
            ETB.SourceMapper:Initialize()
        end
        
        if ETB.Sets then
            ETB.Sets:Initialize()
        end

        -- v15.8.14: Pre-warm a DressUpModel at login.
        --
        -- User report: first transmog NPC interaction after /reload causes
        -- a full client-wide texture flush (world textures, UI, NPCs, player
        -- characters all re-load simultaneously). The flush DOESN'T recur in
        -- the same session — only on first access.
        --
        -- Diagnosis: this is a client-level behavior in MoP 5.4.8 where the
        -- first DressUpModel:SetUnit("player") call after /reload streams a
        -- large amount of player-armor texture data into VRAM, exceeding
        -- whatever budget the engine has reserved. The eviction cascade then
        -- forces every other texture to re-stream from disk. Affects ALL
        -- transmog UIs — Blizzard's wardrobe shows the same blank-character
        -- behavior on first open (visible in user screenshots), confirming
        -- this isn't ETB-specific.
        --
        -- Mitigation: create a hidden DressUpModel during login init and
        -- pre-load the player asset NOW. The expensive eviction still
        -- happens, but it happens during login (when texture loading is
        -- already happening and the player expects loading lag), not at the
        -- NPC interaction. The user-perceived effect is "no flash at NPC."
        --
        -- The dummy model is parented to UIParent but kept hidden. It costs
        -- ~one DressUpModel's worth of memory (small) and stays alive for
        -- the session.
        if not ETB._dressUpPrewarmModel then
            local m = CreateFrame("DressUpModel", nil, UIParent)
            m:SetSize(1, 1)
            m:SetPoint("CENTER", UIParent, "CENTER", -10000, 0)  -- offscreen
            m:Hide()
            pcall(m.SetUnit, m, "player")
            ETB._dressUpPrewarmModel = m
            ETB:Debug("Init", "DressUpModel pre-warm scheduled")
        end

        ETB.initialized = true
        ETB.EventBus:Fire("ETB_INITIALIZED")

        ETB:Debug("Init", "Phase 2 complete (data modules)")
    end)
    
    -- Phase 3 (login+5s): GC to reclaim parser temporaries
    C_Timer.After(5, function()
        collectgarbage("collect")
        collectgarbage("collect")
        local mem = ETB:GetMemoryUsage()
        ETB:Debug("Init", "GC complete: " .. ETB:FormatMemory(mem))
    end)
    -- BossLootTooltip index builds lazily on first NPC mouseover (not at login)
    -- BuildIndex is synchronous so calling it here would just freeze at login+3s
end

function ETB:OnCollectionUpdated()
    if ETB.BlizzardData then
        ETB.BlizzardData:InvalidateCollectionCache()
    end
    
    -- Clear visualID cache so newly collected items resolve fresh
    ETB:ClearVisualIDCache()
    
    if ETB.MainFrame and ETB.MainFrame:IsShown() then
        ETB.MainFrame:RefreshItems()
    end
    
    ETB.EventBus:Fire("ETB_COLLECTION_UPDATED")
end

-- ============================================================================
-- SHARED VISUAL ID RESOLVER
-- ============================================================================
-- Items from Blizzard's GetCategoryAppearances have visualID pre-set.
-- Items from other paths (sets, tooltips, right-clicks) may not.
-- This resolves visualID at runtime via C_TransmogCollection.GetItemInfo().
-- All files should use ETB:ResolveVisualID(item) instead of checking
-- item.visualID directly.
-- ============================================================================

local visualIDCache = {}

function ETB:ResolveVisualID(item)
    if not item then return nil end
    
    -- 1. Already has visualID from Blizzard enumeration
    if item.visualID and item.visualID > 0 then
        return item.visualID
    end
    
    -- Need itemID for runtime lookup
    if not item.itemID then return nil end
    
    -- 2. Check runtime cache
    if visualIDCache[item.itemID] then
        item.visualID = visualIDCache[item.itemID]
        return item.visualID
    end
    
    -- 3. Runtime lookup via C_TransmogCollection.GetItemInfo
    if C_TransmogCollection and C_TransmogCollection.GetItemInfo then
        local ok, appID = pcall(C_TransmogCollection.GetItemInfo, item.itemID)
        if ok and appID and appID > 0 then
            visualIDCache[item.itemID] = appID
            item.visualID = appID
            return appID
        end
    end
    
    return nil
end

-- Resolve from just an itemID (no item table needed)
function ETB:ResolveVisualIDFromItemID(itemID)
    if not itemID then return nil end
    if visualIDCache[itemID] then return visualIDCache[itemID] end
    if C_TransmogCollection and C_TransmogCollection.GetItemInfo then
        local ok, appID = pcall(C_TransmogCollection.GetItemInfo, itemID)
        if ok and appID and appID > 0 then
            visualIDCache[itemID] = appID
            return appID
        end
    end
    return nil
end

-- Clear cache (on collection update, some items may gain new data)
function ETB:ClearVisualIDCache()
    wipe(visualIDCache)
end

-- ============================================================================
-- EVENT REGISTRATION
-- ============================================================================

ETB.EventBus:Register("ADDON_LOADED", function(event, addon)
    if addon == ADDON_NAME then
        ETB:OnAddonLoaded()
    end
end, ETB.EventBus.Priority.FIRST)

ETB.EventBus:Register("PLAYER_LOGIN", function()
    ETB:OnPlayerLogin()
end, ETB.EventBus.Priority.EARLY)

ETB.EventBus:Register("TRANSMOG_COLLECTION_UPDATED", function()
    ETB:OnCollectionUpdated()
end, ETB.EventBus.Priority.NORMAL)

-- Capture async item info responses and refresh visible UI
local pendingRefreshTimer = nil
ETB.EventBus:Register("GET_ITEM_INFO_RECEIVED", function(event, itemID, success)
    if not itemID then return end
    
    -- Update BlizzardData cache
    if ETB.BlizzardData and ETB.BlizzardData.pendingItemInfo and ETB.BlizzardData.pendingItemInfo[itemID] then
        ETB.BlizzardData:UpdateItemInfo(itemID)
        ETB.BlizzardData.pendingItemInfo[itemID] = nil
    end
    
    -- Debounced refresh of the main browser (0.2s delay to batch multiple callbacks)
    if ETB.MainFrame and ETB.MainFrame.frame and ETB.MainFrame.frame:IsShown() then
        if pendingRefreshTimer then
            -- Timer already running, it will pick up all updates when it fires
        else
            pendingRefreshTimer = C_Timer.After(0.2, function()
                pendingRefreshTimer = nil
                if ETB.MainFrame and ETB.MainFrame.frame and ETB.MainFrame.frame:IsShown() then
                    ETB.MainFrame:UpdateDisplay()
                end
            end)
        end
    end
end, ETB.EventBus.Priority.EARLY)

ETB:Debug("Init", "Init.lua loaded")
