--[[
============================================================================
ETB BossLootTooltip Module v2
============================================================================
Shows armor/weapon transmog drops when hovering over NPCs/bosses.

Data pipeline:
  1. Reads compact data directly (avoids force-decoding all 55k items)
  2. Builds npcIndex: npcID → { [itemID] = flags }
  3. Builds encounter→NPC lookup from items that have both fields
  4. Resolves encounter-only items to NPCs via lookup
  5. Builds instance→NPCs lookup for boss grouping
  6. For BOSS-typed items with instanceID but no NPC/encounter (tier token
     items, shared loot), groups them under ALL known bosses in that instance
  7. Token items (cost field has {"i",tokenID,count}) flagged for display

Suppression:
  - During combat (PLAYER_REGEN_DISABLED)
  - During PvP (battlegrounds, arenas)
  - When player is dead/ghost
  - For non-creature units (players, objects, etc.)

Commands:
  /etb bossloot       → Toggle on/off
  /etb bossloot stats → Index statistics
  /etb bossloot build → Force rebuild index
============================================================================
]]--

local ADDON_NAME, NS = ...
local ETB = NS.ETB
if not ETB then return end

ETB.BossLootTooltip = ETB.BossLootTooltip or {}
local BLT = ETB.BossLootTooltip

-- ============================================================================
-- STATE
-- ============================================================================

BLT.initialized = false
BLT.npcIndex = nil          -- npcID → { [itemID] = flags }
BLT.indexBuilt = false
BLT.building = false
BLT.lastNPCID = nil         -- Prevent redundant tooltip updates
BLT.enabled = true          -- Master toggle

-- Stats
BLT.stats = {
    directNPC = 0,          -- Items mapped via direct NPC field
    encounterResolved = 0,  -- Items mapped via encounter→NPC resolution
    instanceGrouped = 0,    -- Items mapped via instance boss grouping
    totalNPCs = 0,
    totalItems = 0,
}

-- ============================================================================
-- CONFIGURATION
-- ============================================================================

local MAX_ITEMS_SHOWN = 12      -- Max items to display in tooltip
local HEADER_COLOR = "|cff00ccff"
local COLLECTED_COLOR = "|cff00ff00"
local UNCOLLECTED_COLOR = "|cffff6666"
local SEPARATOR_COLOR = "|cff444444"
local TOKEN_COLOR = "|cffff8000"

-- Armor classID = 4, Weapon classID = 2
local VALID_ITEM_CLASSES = { [2] = true, [4] = true }

-- Non-transmog equip slots to exclude (not visible appearances)
local EXCLUDED_EQUIP_LOCS = {
    INVTYPE_TRINKET = true,
    INVTYPE_FINGER = true,
    INVTYPE_NECK = true,
}

-- Flags for how an item was mapped to an NPC
local FLAG_DIRECT = 1       -- Direct NPC field on item
local FLAG_ENCOUNTER = 2    -- Resolved via encounter→NPC
local FLAG_INSTANCE = 3     -- Grouped via instance boss grouping
local FLAG_TOKEN = 4        -- Has token cost (additive flag)

-- Source type indices (from Compact/Strings.lua ST table)
-- ST = {"ACHIEVEMENT","BOSS","CRAFTED","EVENT","OTHER","PVP","QUEST","RARE_MOB","REMOVED","REPUTATION","SCENARIO","VENDOR","WORLD_DROP"}
local ST_BOSS = 2
local ST_QUEST = 7
local ST_RARE = 8
local ST_VENDOR = 12

-- ============================================================================
-- WORLD BOSS: Encounter Journal encounterID → NPC ID mapping
-- These bosses' loot is often coded as VENDOR (ST=12) in ATT data because
-- players buy gear with tokens/drops from the boss, not direct loot.
-- SourceDB flags them correctly (flags & 0x08 = isWorldBoss).
-- ============================================================================

local WORLD_BOSS_ENC_TO_NPC = {
    -- MoP World Bosses
    [691]  = 60143,   -- Sha of Anger → NPC 60143
    [725]  = 62346,   -- Galleon (Salyis's Warband) → NPC 62346
    [814]  = 69099,   -- Nalak, The Storm Lord → NPC 69099
    [826]  = 69161,   -- Oondasta → NPC 69161
    [857]  = 71953,   -- Chi-Ji (August Celestials) → NPC 71953
    [858]  = 71954,   -- Niuzao (August Celestials) → NPC 71954
    [859]  = 71955,   -- Xuen (August Celestials) → NPC 71955
    [860]  = 71952,   -- Yu'lon (August Celestials) → NPC 71952
    [861]  = 72057,   -- Ordos, Fire-God of the Yaungol → NPC 72057
    -- Classic World Bosses
    [12397] = 12397,  -- Azuregos
    [6109]  = 6109,   -- Lord Kazzak
}

-- NPC IDs that are world bosses (for tooltip labeling)
local WORLD_BOSS_NPCS = {
    [60143] = "Sha of Anger",
    [62346] = "Galleon",
    [69099] = "Nalak",
    [69161] = "Oondasta",
    [71953] = "Chi-Ji",
    [71954] = "Niuzao",
    [71955] = "Xuen",
    [71952] = "Yu'lon",
    [72057] = "Ordos",
    [60491] = "Celestials", -- Celestial challenge NPC
}

-- Set version for fast lookups during indexing
local WORLD_BOSS_NPC_SET = {}
for npcID in pairs(WORLD_BOSS_NPCS) do
    WORLD_BOSS_NPC_SET[npcID] = true
end

local FLAG_WORLDBOSS = 8  -- Additive flag for world boss items
local FLAG_QUEST = 16     -- Additive flag for quest reward items
local FLAG_RARE = 32      -- Additive flag for rare mob items (from RareMobLoot data)

-- ============================================================================
-- INDEX BUILDER
-- ============================================================================

local function AddToNPCIndex(index, npcID, itemID, flag)
    if not npcID or npcID == 0 or not itemID then return false end
    if not index[npcID] then
        index[npcID] = {}
    end
    -- Flags: base type (1-3) in low bits + additive flags (TOKEN=4, WORLDBOSS=8)
    local existing = index[npcID][itemID]
    if not existing then
        index[npcID][itemID] = flag
        return true
    end
    -- Keep best base flag (lower = better: DIRECT=1 > ENCOUNTER=2 > INSTANCE=3)
    -- Merge additive flags (TOKEN, WORLDBOSS) from both
    local existBase = bit.band(existing, 3)
    local newBase = bit.band(flag, 3)
    local mergedAdditive = bit.bor(bit.band(existing, 0xFC), bit.band(flag, 0xFC))
    local bestBase = math.min(existBase, newBase)
    if bestBase > 0 then
        index[npcID][itemID] = bit.bor(bestBase, mergedAdditive)
    end
    return newBase < existBase
end

function BLT:BuildIndex()
    if self.indexBuilt or self.building then return end
    self.building = true
    
    local SDA = ETB.SourceData_ATT
    if not SDA then
        ETB:Debug("BossLoot", "SourceData_ATT not available")
        self.building = false
        return
    end
    
    -- Ensure MapperBridge name table is loaded (needed for NPC names)
    if SDA.EnsureLoaded then
        SDA:EnsureLoaded()
    end
    
    local compact = NS.Compact
    if not compact then
        ETB:Debug("BossLoot", "Compact data not available")
        self.building = false
        return
    end
    
    local npcIndex = {}             -- npcID → { [itemID] = flag }
    local encToNPC = {}             -- encounterID → npcID
    local instanceNPCs = {}         -- instanceID → { [npcID] = true }
    local pendingEncounter = {}     -- items with encounter but no NPC
    local pendingInstance = {}      -- BOSS items with instance but no NPC/encounter
    
    local stats = { directNPC = 0, encounterResolved = 0, instanceGrouped = 0,
                    worldBoss = 0, questNPC = 0, rareMob = 0 }
    
    local expansions = { "Classic", "TBC", "Wrath", "Cata", "MoP" }
    
    -- ========================================================================
    -- PRE-SEED: World boss encounter→NPC mappings
    -- These are known statically; compact data often lacks NPC IDs for them.
    -- ========================================================================
    
    for encID, npcID in pairs(WORLD_BOSS_ENC_TO_NPC) do
        encToNPC[encID] = npcID
    end
    
    -- ========================================================================
    -- PASS 1: Build direct NPC index + encounter→NPC lookup + collect pending
    -- ONLY index BOSS (2) and RARE_MOB (8) source types — skip vendors,
    -- quest givers, world drops, crafted, PVP, etc. Those aren't "loot."
    -- ========================================================================
    
    for _, expName in ipairs(expansions) do
        local data = compact[expName]
        if data and data.I then
            for itemID, raw in pairs(data.I) do
                if type(raw) == "table" then
                    -- Synchronous class check (no async)
                    local _, _, _, equipLoc, _, classID = GetItemInfoInstant(itemID)
                    
                    -- Skip non-armor/weapon and non-visible slots (trinkets, rings, necks)
                    if classID and VALID_ITEM_CLASSES[classID] and not (equipLoc and EXCLUDED_EQUIP_LOCS[equipLoc]) then
                        local npcID = raw[2]
                        local st = raw[3]       -- source type index
                        local eID = raw[4]      -- encounter ID
                        local iID = raw[5]      -- instance ID
                        
                        -- ONLY process BOSS and RARE_MOB items for the loot tooltip
                        -- Vendor (12), Quest (7), World Drop (13), Crafted (3), PVP (6),
                        -- Event (4), Achievement (1), etc. are NOT boss loot.
                        -- EXCEPTION: QUEST items from known world boss NPCs (coded as
                        -- quest rewards in ATT but actually world boss drops)
                        local isWorldBossQuest = (st == ST_QUEST and npcID and WORLD_BOSS_NPC_SET[npcID])
                        if st ~= ST_BOSS and st ~= ST_RARE and not isWorldBossQuest then
                            -- skip entirely
                        else
                        
                        -- Normalize zeros
                        if npcID == 0 then npcID = nil end
                        if eID == 0 then eID = nil end
                        if iID == 0 then iID = nil end
                        
                        -- Detect token cost in field 27
                        local hasToken = false
                        local costField = raw[27]
                        if costField and type(costField) == "table" then
                            for _, c in ipairs(costField) do
                                if type(c) == "table" and c[1] == "i" then
                                    hasToken = true
                                    break
                                end
                            end
                        end
                        
                        if npcID then
                            -- Direct NPC mapping
                            local flag = hasToken and (FLAG_DIRECT + FLAG_TOKEN) or FLAG_DIRECT
                            AddToNPCIndex(npcIndex, npcID, itemID, flag)
                            stats.directNPC = stats.directNPC + 1
                            
                            -- Build encounter→NPC and instance→NPCs lookups
                            if eID then encToNPC[eID] = npcID end
                            if iID then
                                if not instanceNPCs[iID] then instanceNPCs[iID] = {} end
                                instanceNPCs[iID][npcID] = true
                            end
                            
                        elseif eID then
                            -- Has encounter but no NPC - queue for pass 2
                            pendingEncounter[#pendingEncounter + 1] = {
                                itemID = itemID, eID = eID, iID = iID, hasToken = hasToken
                            }
                            
                        elseif iID then
                            -- BOSS items with instance but no NPC/encounter
                            -- Instance grouping assigns them to ALL known bosses
                            -- in that instance. Token items get priority flag.
                            pendingInstance[#pendingInstance + 1] = {
                                itemID = itemID, iID = iID, hasToken = hasToken
                            }
                        end
                        
                        -- Process additional sources (field 26)
                        -- Only if they are BOSS-type sources
                        local srcs = raw[26]
                        if srcs and type(srcs) == "table" then
                            for _, src in ipairs(srcs) do
                                if type(src) == "table" then
                                    local srcST = src[4] or 0
                                    -- Only process BOSS or RARE additional sources
                                    if srcST == ST_BOSS or srcST == ST_RARE or srcST == 0 then
                                        local srcEID = (src[1] and src[1] ~= 0) and src[1] or nil
                                        local srcIID = (src[2] and src[2] ~= 0) and src[2] or nil
                                        local srcNPC = (src[3] and src[3] ~= 0) and src[3] or nil
                                        
                                        if srcNPC then
                                            AddToNPCIndex(npcIndex, srcNPC, itemID, FLAG_DIRECT)
                                            if srcEID then encToNPC[srcEID] = srcNPC end
                                            if srcIID then
                                                if not instanceNPCs[srcIID] then instanceNPCs[srcIID] = {} end
                                                instanceNPCs[srcIID][srcNPC] = true
                                            end
                                        elseif srcEID then
                                            pendingEncounter[#pendingEncounter + 1] = {
                                                itemID = itemID, eID = srcEID, hasToken = hasToken
                                            }
                                        end
                                    end
                                end
                            end
                        end
                        
                        end -- end st == BOSS or RARE check
                    end
                end
            end
        end
    end
    
    -- ========================================================================
    -- PASS 1.5: SourceDB world boss recovery
    -- World boss items (Sha of Anger, Galleon, Nalak, Oondasta, Celestials,
    -- Ordos) are often coded as VENDOR (ST=12) in compact data because
    -- players buy gear with tokens from these bosses. SourceDB correctly
    -- flags them with isWorldBoss=true and has encounter IDs.
    -- ========================================================================
    
    if ETB.SourceDB and ETB.SourceDB.GetAllWorldBossItems then
        local wbItems = ETB.SourceDB:GetAllWorldBossItems()
        if wbItems then
            for _, wbEntry in ipairs(wbItems) do
                local itemID = wbEntry.itemID
                local encID = wbEntry.encounterID
                if itemID and encID then
                    -- Check transmoggability
                    local _, _, _, equipLoc, _, classID = GetItemInfoInstant(itemID)
                    if classID and VALID_ITEM_CLASSES[classID] and not (equipLoc and EXCLUDED_EQUIP_LOCS[equipLoc]) then
                        local resolvedNPC = encToNPC[encID] or WORLD_BOSS_ENC_TO_NPC[encID]
                        if resolvedNPC then
                            if AddToNPCIndex(npcIndex, resolvedNPC, itemID, FLAG_DIRECT + FLAG_WORLDBOSS) then
                                stats.worldBoss = stats.worldBoss + 1
                            end
                        else
                            -- Queue for encounter resolution in PASS 2
                            pendingEncounter[#pendingEncounter + 1] = {
                                itemID = itemID, eID = encID, hasToken = false
                            }
                        end
                    end
                end
            end
        end
    end
    
    -- Fallback: scan compact data for VENDOR items that SourceDB identifies as world bosses
    -- This catches items where SourceDB:GetAllWorldBossItems may not exist yet
    if not (ETB.SourceDB and ETB.SourceDB.GetAllWorldBossItems) and ETB.SourceDB and ETB.SourceDB.Get then
        for _, expName in ipairs(expansions) do
            local data = compact[expName]
            if data and data.I then
                for itemID, raw in pairs(data.I) do
                    if type(raw) == "table" then
                        local st = raw[3]
                        -- Only check VENDOR items (world boss loot is coded as VENDOR)
                        if st == ST_VENDOR then
                            local sdbRec = ETB.SourceDB:Get(itemID)
                            if sdbRec and sdbRec.isWorldBoss then
                                local _, _, _, equipLoc, _, classID = GetItemInfoInstant(itemID)
                                if classID and VALID_ITEM_CLASSES[classID] and not (equipLoc and EXCLUDED_EQUIP_LOCS[equipLoc]) then
                                    local encID = sdbRec.encounterID
                                    local resolvedNPC = encID and (encToNPC[encID] or WORLD_BOSS_ENC_TO_NPC[encID])
                                    if resolvedNPC then
                                        if AddToNPCIndex(npcIndex, resolvedNPC, itemID, FLAG_DIRECT + FLAG_WORLDBOSS) then
                                            stats.worldBoss = stats.worldBoss + 1
                                        end
                                    elseif encID then
                                        pendingEncounter[#pendingEncounter + 1] = {
                                            itemID = itemID, eID = encID, hasToken = false
                                        }
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    
    -- ========================================================================
    -- PASS 2: Resolve encounter-only items via encounter→NPC lookup
    -- Uses: static encToNPC table, world boss table, and EJ API fallback.
    -- ========================================================================
    
    for _, pending in ipairs(pendingEncounter) do
        local resolvedNPC = encToNPC[pending.eID]
        
        -- Fallback 1: world boss static table
        if not resolvedNPC then
            resolvedNPC = WORLD_BOSS_ENC_TO_NPC[pending.eID]
        end
        
        -- Fallback 2: Encounter Journal API (MoP Classic has EJ)
        -- EJ_GetCreatureInfo(index, encounterID) returns: id, name, description, displayInfo, iconImage
        -- where id = npcID of the creature for that encounter
        if not resolvedNPC and EJ_GetCreatureInfo then
            local ok, creatureID = pcall(EJ_GetCreatureInfo, 1, pending.eID)
            if ok and creatureID and type(creatureID) == "number" and creatureID > 0 then
                resolvedNPC = creatureID
                -- Cache for future lookups
                encToNPC[pending.eID] = resolvedNPC
            end
        end
        
        if resolvedNPC then
            local flag = pending.hasToken and (FLAG_ENCOUNTER + FLAG_TOKEN) or FLAG_ENCOUNTER
            if AddToNPCIndex(npcIndex, resolvedNPC, pending.itemID, flag) then
                stats.encounterResolved = stats.encounterResolved + 1
            end
            -- Also track instance membership
            if pending.iID then
                if not instanceNPCs[pending.iID] then instanceNPCs[pending.iID] = {} end
                instanceNPCs[pending.iID][resolvedNPC] = true
            end
        end
    end
    
    -- ========================================================================
    -- PASS 3: REMOVED — Instance boss grouping was assigning items with only
    -- instanceID (no NPC/encounter) to ALL bosses in that instance.
    -- This caused massive pollution: e.g., 230 items spread to all 14 bosses.
    -- Items without a specific NPC or encounter are now simply not indexed.
    -- They can still be found via the main browser's source data display.
    -- ========================================================================
    
    -- (pendingInstance items are intentionally dropped)
    
    -- ========================================================================
    -- PASS 4: Quest NPC items (non-world-boss quest givers with transmog)
    -- These are actual quest givers where the reward is transmoggable gear.
    -- Scans QUEST items that have NPC IDs but aren't world bosses
    -- (world boss quest items were already caught in PASS 1).
    -- ========================================================================
    
    for _, expName in ipairs(expansions) do
        local data = compact[expName]
        if data and data.I then
            for itemID, raw in pairs(data.I) do
                if type(raw) == "table" then
                    local npcID = raw[2]
                    local st = raw[3]
                    if st == ST_QUEST and npcID and npcID ~= 0 and not WORLD_BOSS_NPC_SET[npcID] then
                        local _, _, _, equipLoc, _, classID = GetItemInfoInstant(itemID)
                        if classID and VALID_ITEM_CLASSES[classID] and not (equipLoc and EXCLUDED_EQUIP_LOCS[equipLoc]) then
                            if AddToNPCIndex(npcIndex, npcID, itemID, FLAG_DIRECT + FLAG_QUEST) then
                                stats.questNPC = stats.questNPC + 1
                            end
                        end
                    end
                end
            end
        end
    end
    
    -- ========================================================================
    -- PASS 5: Rare mob loot from RareMobLoot supplemental data
    -- Source: RareScanner addon database - 529 rare NPCs with transmog items.
    -- These are open-world rare spawns that drop BoE greens/blues.
    -- Only items that exist in our compact data are included.
    -- ========================================================================
    
    local rareMobData = NS.RareMobLoot
    if rareMobData then
        for npcID, itemList in pairs(rareMobData) do
            for _, itemID in ipairs(itemList) do
                local _, _, _, equipLoc, _, classID = GetItemInfoInstant(itemID)
                if classID and VALID_ITEM_CLASSES[classID] and not (equipLoc and EXCLUDED_EQUIP_LOCS[equipLoc]) then
                    if AddToNPCIndex(npcIndex, npcID, itemID, FLAG_DIRECT + FLAG_RARE) then
                        stats.rareMob = stats.rareMob + 1
                    end
                end
            end
        end
    end
    
    -- ========================================================================
    -- FINALIZE
    -- ========================================================================
    
    local totalNPCs, totalItems = 0, 0
    for _, items in pairs(npcIndex) do
        totalNPCs = totalNPCs + 1
        for _ in pairs(items) do
            totalItems = totalItems + 1
        end
    end
    
    self.npcIndex = npcIndex
    self.stats = {
        directNPC = stats.directNPC,
        encounterResolved = stats.encounterResolved,
        instanceGrouped = stats.instanceGrouped,
        worldBoss = stats.worldBoss,
        questNPC = stats.questNPC,
        rareMob = stats.rareMob,
        totalNPCs = totalNPCs,
        totalItems = totalItems,
        pendingEncounter = #pendingEncounter,
        pendingInstance = #pendingInstance,
        encToNPCCount = 0,
        instanceNPCCount = 0,
    }
    for _ in pairs(encToNPC) do self.stats.encToNPCCount = self.stats.encToNPCCount + 1 end
    for _ in pairs(instanceNPCs) do self.stats.instanceNPCCount = self.stats.instanceNPCCount + 1 end
    
    self.indexBuilt = true
    self.building = false
    
    ETB:Debug("BossLoot", string.format(
        "Index built: %d NPCs, %d items (direct:%d, enc:%d, inst:%d, wBoss:%d, quest:%d, rare:%d)",
        totalNPCs, totalItems, stats.directNPC, stats.encounterResolved,
        stats.instanceGrouped, stats.worldBoss, stats.questNPC, stats.rareMob))
end

-- ============================================================================
-- NPC ID EXTRACTION FROM GUID
-- ============================================================================

function BLT:GetNPCIDFromGUID(guid)
    if not guid then return nil end
    local unitType, _, _, _, _, npcID = strsplit("-", guid)
    if unitType == "Creature" or unitType == "Vehicle" then
        return tonumber(npcID)
    end
    return nil
end

-- ============================================================================
-- ITEM COLLECTION CHECK
-- ============================================================================

function BLT:IsItemCollected(itemID)
    if not itemID or not C_TransmogCollection then return false end
    
    if C_TransmogCollection.GetItemInfo then
        local appearanceID, sourceID = C_TransmogCollection.GetItemInfo(itemID)
        if sourceID then
            local info = C_TransmogCollection.GetSourceInfo(sourceID)
            if info then return info.isCollected end
        end
        if appearanceID and C_TransmogCollection.GetAppearanceSources then
            local sources = C_TransmogCollection.GetAppearanceSources(appearanceID)
            if sources then
                for _, src in ipairs(sources) do
                    if src.isCollected then return true end
                end
            end
        end
    end
    return false
end

-- ============================================================================
-- CLASS USABILITY CHECK
-- ============================================================================

function BLT:CanPlayerUseItem(itemID)
    if not itemID then return false end
    local _, _, _, equipLoc, _, classID, subclassID = GetItemInfoInstant(itemID)
    if not classID or not VALID_ITEM_CLASSES[classID] then return false end
    
    -- Exclude non-visible slots (trinkets, rings, necklaces)
    if equipLoc and EXCLUDED_EQUIP_LOCS[equipLoc] then return false end
    
    if classID == 4 and ETB.BlizzardData then -- Armor
        if subclassID == 6 then
            -- Shields: check weapon proficiency (only Warrior/Paladin/Shaman)
            if not ETB.BlizzardData:CanPlayerUseWeaponType("SHIELD") then
                return false
            end
        else
            -- Regular armor: check primary armor type
            local ARMOR_MAP = { [1]="CLOTH", [2]="LEATHER", [3]="MAIL", [4]="PLATE" }
            local armorType = ARMOR_MAP[subclassID]
            -- Misc armor (subclass 0: cloaks, shirts, tabards) skip armor check — all classes use
            if armorType and not ETB.BlizzardData:CanPlayerWearArmorType(armorType) then
                return false
            end
        end
    elseif classID == 2 and ETB.BlizzardData then -- Weapon
        local weaponType = ETB.BlizzardData:GetWeaponType(itemID)
        if weaponType and not ETB.BlizzardData:CanPlayerUseWeaponType(weaponType) then
            return false
        end
    end
    
    -- Check ATT class restrictions (e.g., Warrior-only tier items)
    if ETB.ClassRestrictions then
        if not ETB.ClassRestrictions:IsItemUsableByPlayer(itemID) then
            return false
        end
    end
    
    return true
end

-- ============================================================================
-- TOOLTIP POPULATION
-- ============================================================================

function BLT:AddLootToTooltip(tooltip, npcID)
    if not tooltip or not npcID then return end
    if not self.indexBuilt then return end
    
    local npcItems = self.npcIndex[npcID]
    if not npcItems then return end
    
    -- Build filtered: class-usable only, count collected vs uncollected
    local uncollectedItems = {}
    local collectedCount = 0
    local totalCount = 0
    
    for itemID, flag in pairs(npcItems) do
        if self:CanPlayerUseItem(itemID) then
            local isCollected = self:IsItemCollected(itemID)
            totalCount = totalCount + 1
            if isCollected then
                collectedCount = collectedCount + 1
            else
                -- Flags are additive: base (1-3) + TOKEN (4) + WORLDBOSS (8) + QUEST (16) + RARE (32)
                local isToken = bit.band(flag, FLAG_TOKEN) > 0
                local isWorldBoss = bit.band(flag, FLAG_WORLDBOSS) > 0
                local isQuest = bit.band(flag, FLAG_QUEST) > 0
                local isRare = bit.band(flag, FLAG_RARE) > 0
                local baseFlag = bit.band(flag, 3)  -- Extract base mapping type (1-3)
                uncollectedItems[#uncollectedItems + 1] = {
                    itemID = itemID,
                    isToken = isToken,
                    isWorldBoss = isWorldBoss,
                    isQuest = isQuest,
                    isRare = isRare,
                    mappingType = baseFlag,
                }
            end
        end
    end
    
    if totalCount == 0 then return end
    
    local uncollectedCount = #uncollectedItems
    local isWB = WORLD_BOSS_NPCS[npcID]
    
    -- Determine NPC type: check if ALL items are quest rewards (quest giver)
    local questCount = 0
    local rareCount = 0
    for _, entry in ipairs(uncollectedItems) do
        if entry.isQuest then questCount = questCount + 1 end
        if entry.isRare then rareCount = rareCount + 1 end
    end
    local isQuestNPC = (questCount == uncollectedCount) and not isWB
    local isRareNPC = (rareCount == uncollectedCount) and not isWB and not isQuestNPC
    
    -- Header
    tooltip:AddLine(" ")
    local headerPrefix
    if isWB then
        headerPrefix = "|cffff00ffETB World Boss:|r "
    elseif isQuestNPC then
        headerPrefix = "|cffffff00ETB Quest Rewards:|r "
    elseif isRareNPC then
        headerPrefix = "|cff00ffffETB Rare Mob:|r "
    else
        headerPrefix = HEADER_COLOR .. "ETB Transmog:|r "
    end
    
    if uncollectedCount == 0 then
        tooltip:AddLine(headerPrefix ..
            COLLECTED_COLOR .. "All " .. totalCount .. " collected!|r")
        tooltip:Show()
        return
    end
    
    tooltip:AddLine(headerPrefix ..
        string.format("%s%d|r/%d collected  (%s%d uncollected|r)",
            COLLECTED_COLOR, collectedCount, totalCount,
            UNCOLLECTED_COLOR, uncollectedCount))
    
    -- Sort uncollected by mapping quality then itemID
    table.sort(uncollectedItems, function(a, b)
        if a.mappingType ~= b.mappingType then
            return a.mappingType < b.mappingType
        end
        return a.itemID < b.itemID
    end)
    
    -- Show only uncollected items
    local shown = 0
    for _, entry in ipairs(uncollectedItems) do
        if shown >= MAX_ITEMS_SHOWN then
            local remaining = uncollectedCount - shown
            if remaining > 0 then
                tooltip:AddLine(SEPARATOR_COLOR .. "  +" .. remaining .. " more...|r")
            end
            break
        end
        
        local itemName, _, quality = GetItemInfo(entry.itemID)
        if itemName then
            local qualityColor = "|cffffffff"
            if quality then
                local r, g, b = GetItemQualityColor(quality)
                qualityColor = string.format("|cff%02x%02x%02x", r*255, g*255, b*255)
            end
            
            -- Get slot/type info for display
            local slotInfo = ""
            local _, _, _, equipLoc, _, cID, scID = GetItemInfoInstant(entry.itemID)
            if cID == 4 then  -- Armor
                local SLOT_SHORT = {
                    INVTYPE_HEAD = "Head", INVTYPE_SHOULDER = "Shoulder",
                    INVTYPE_CHEST = "Chest", INVTYPE_ROBE = "Chest",
                    INVTYPE_WAIST = "Waist", INVTYPE_LEGS = "Legs",
                    INVTYPE_FEET = "Feet", INVTYPE_WRIST = "Wrist",
                    INVTYPE_HAND = "Hands", INVTYPE_CLOAK = "Back",
                    INVTYPE_BODY = "Shirt", INVTYPE_TABARD = "Tabard",
                    INVTYPE_SHIELD = "Shield", INVTYPE_HOLDABLE = "Off-Hand",
                }
                slotInfo = equipLoc and SLOT_SHORT[equipLoc] or ""
            elseif cID == 2 then  -- Weapon
                local WPN_SHORT = {
                    INVTYPE_WEAPON = "1H", INVTYPE_WEAPONMAINHAND = "MH",
                    INVTYPE_WEAPONOFFHAND = "OH", INVTYPE_2HWEAPON = "2H",
                    INVTYPE_RANGED = "Ranged", INVTYPE_RANGEDRIGHT = "Ranged",
                }
                slotInfo = equipLoc and WPN_SHORT[equipLoc] or ""
            end
            
            local suffix = ""
            if entry.isToken then
                suffix = TOKEN_COLOR .. " (token)|r"
            elseif entry.isQuest then
                suffix = "|cffffff00 (quest)|r"
            elseif entry.isRare then
                suffix = "|cff00ffff (rare)|r"
            end
            
            local slotDisplay = slotInfo ~= "" and (SEPARATOR_COLOR .. " [" .. slotInfo .. "]|r") or ""
            tooltip:AddLine(UNCOLLECTED_COLOR .. "  \226\156\151 " .. qualityColor .. itemName .. "|r" .. slotDisplay .. suffix)
        else
            GetItemInfo(entry.itemID)
            tooltip:AddLine(UNCOLLECTED_COLOR .. "  \226\156\151 |cff888888Loading...|r")
        end
        shown = shown + 1
    end
    
    tooltip:Show()
end

-- ============================================================================
-- EVENT HANDLING
-- ============================================================================

function BLT:ShouldSuppress()
    if UnitAffectingCombat("player") then return true end
    if C_PvP and C_PvP.IsBattleground and C_PvP.IsBattleground() then return true end
    if C_PvP and C_PvP.IsArena and C_PvP.IsArena() then return true end
    if UnitIsDeadOrGhost("player") then return true end
    return false
end

function BLT:OnMouseoverUnit()
    if not self.enabled then return end
    if self:ShouldSuppress() then return end
    
    -- Lazy init: build index on first NPC hover
    if not self.indexBuilt then
        self:BuildIndex()
        if not self.indexBuilt then return end
    end
    
    local guid = UnitGUID("mouseover")
    if not guid then return end
    
    local npcID = self:GetNPCIDFromGUID(guid)
    if not npcID then return end
    
    -- Skip if same NPC (tooltip already populated)
    if npcID == self.lastNPCID then return end
    self.lastNPCID = npcID
    
    if not self.npcIndex[npcID] then return end
    
    self:AddLootToTooltip(GameTooltip, npcID)
end

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

function BLT:Initialize()
    if self.initialized then return end
    
    local frame = CreateFrame("Frame")
    frame:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
    frame:RegisterEvent("PLAYER_REGEN_DISABLED")
    frame:RegisterEvent("PLAYER_REGEN_ENABLED")
    
    frame:SetScript("OnEvent", function(_, event)
        if event == "UPDATE_MOUSEOVER_UNIT" then
            BLT.lastNPCID = nil
            BLT:OnMouseoverUnit()
        elseif event == "PLAYER_REGEN_DISABLED" then
            BLT.lastNPCID = nil
        end
    end)
    
    self.frame = frame
    self.initialized = true
    
    ETB:Debug("BossLoot", "BossLootTooltip v2 initialized")
end

-- ============================================================================
-- SLASH COMMAND INTEGRATION
-- ============================================================================

function BLT:GetStats()
    if not self.indexBuilt then
        return "Index not built yet (builds on first NPC hover)"
    end
    local s = self.stats
    return string.format(
        "%d NPCs, %d total items\n" ..
        "  |cff00ff00Direct NPC:|r %d items\n" ..
        "  |cff00ccffEncounter resolved:|r %d items (from %d encounter-NPC mappings)\n" ..
        "  |cffff8000Instance grouped:|r %d items (from %d instances with known bosses)\n" ..
        "  |cffff00ffWorld boss:|r %d items (SourceDB world boss recovery)\n" ..
        "  |cffffff00Quest NPC:|r %d items (quest givers with transmog rewards)\n" ..
        "  |cff00ffffRare mob:|r %d items (open-world rare spawns)\n" ..
        "  Pending: %d encounters unresolved, %d instance items without bosses",
        s.totalNPCs, s.totalItems,
        s.directNPC,
        s.encounterResolved, s.encToNPCCount,
        s.instanceGrouped, s.instanceNPCCount,
        s.worldBoss or 0,
        s.questNPC or 0,
        s.rareMob or 0,
        s.pendingEncounter - s.encounterResolved,
        s.pendingInstance - s.instanceGrouped)
end

function BLT:Toggle()
    self.enabled = not self.enabled
    local status = self.enabled and "|cff00ff00enabled|r" or "|cffff0000disabled|r"
    ETB:Debug("BossLoot", "Boss loot tooltips " .. status)
end

-- ============================================================================
-- AUTO-INIT DISABLED — BossLootTooltip needs NPC mapping improvements
-- before shipping. Will be re-enabled in a future version.
-- The module code remains intact for /etb bossloot manual testing.
-- ============================================================================

--[[ 
local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
initFrame:SetScript("OnEvent", function(self, event)
    C_Timer.After(5, function()
        BLT:Initialize()
    end)
    self:UnregisterEvent("PLAYER_ENTERING_WORLD")
end)
]]--

BLT.enabled = false  -- Disabled by default
