--[[
============================================================================
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 = 20      -- 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"
local DIFFICULTY_COLOR = "|cffffcc00"

-- ============================================================================
-- DIFFICULTY DETECTION
-- MoP Classic difficulty IDs → display name + JLoot bitmask
-- JLoot bitmask: 1=Normal, 2=Heroic, 8=Thunderforged, 15=All.
-- Bit 4 (LFR) is intentionally unused: Raid Finder was never introduced in
-- the Cata/MoP Classic timeline, so no difficulty maps to it.
-- ============================================================================

local DIFFICULTY_INFO = {
    -- Dungeons
    [1]  = { name = "Normal",          short = "N",   bitmask = 1,  raid = false },
    [2]  = { name = "Heroic",          short = "H",   bitmask = 2,  raid = false },
    -- Raids: Normal shows diff=1 items only; Heroic shows diff=2 AND diff=8 (heroic-unique models)
    [3]  = { name = "10-Man Normal",   short = "10N", bitmask = 1,  raid = true },
    [4]  = { name = "25-Man Normal",   short = "25N", bitmask = 1,  raid = true },
    [5]  = { name = "10-Man Heroic",   short = "10H", bitmask = 10, raid = true },  -- 2+8: Heroic + Thunderforged/heroic-unique
    [6]  = { name = "25-Man Heroic",   short = "25H", bitmask = 10, raid = true },  -- 2+8: same
    -- No difficultyID 7 (LFR): Raid Finder does not exist in MoP Classic.
    -- Flex (Siege of Orgrimmar) is the lowest raid tier.
    [14] = { name = "Flexible",        short = "Flex", bitmask = 1,  raid = true },
    -- Scenarios
    [11] = { name = "Scenario Heroic", short = "HS",  bitmask = 2,  raid = false },
    [12] = { name = "Scenario Normal", short = "NS",  bitmask = 1,  raid = false },
}

local function GetCurrentDifficulty()
    -- Returns: difficultyInfo table, or nil if not in an instance
    if not IsInInstance or not IsInInstance() then return nil end
    
    local _, instanceType, difficultyID = GetInstanceInfo()
    if not difficultyID or difficultyID == 0 then return nil end
    
    return DIFFICULTY_INFO[difficultyID], difficultyID
end

local function ItemMatchesDifficulty(itemID, diffBitmask)
    -- Check if item drops on the given difficulty
    -- Uses JLoot difficulty data; items without JLoot data are shown (no filter)
    if not diffBitmask or not ETB.JLoot then return true end
    
    local lootData = ETB.JLoot[itemID]
    if not lootData then return true end  -- not in JLoot = show anyway
    
    -- JLoot format: {encounterID, diffBitmask} or {{encID, diff}, {encID, diff}, ...}
    if type(lootData[1]) == "table" then
        -- Multi-entry: check if ANY entry matches
        for _, entry in ipairs(lootData) do
            local itemDiff = entry[2] or 15
            if bit.band(itemDiff, diffBitmask) > 0 then
                return true
            end
        end
        return false
    else
        local itemDiff = lootData[2] or 15
        return bit.band(itemDiff, diffBitmask) > 0
    end
end

-- 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 coded as QUEST (ST=7) in MoP compact data.
-- 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)

-- ============================================================================
-- BLT NAMESPACE EXPOSURES (added v15.1.0 — Audit Batch 4 Part 1)
-- ============================================================================
-- These expose file-locals onto the BLT namespace so submodules can access
-- them via the owner-pattern (e.g. owner.FLAG_DIRECT, owner.VALID_ITEM_CLASSES).
-- This is the same pattern used for TransmogUI/Slots/Sets/Capture/Apply/Blizzard.

BLT.FLAG_DIRECT       = FLAG_DIRECT
BLT.FLAG_ENCOUNTER    = FLAG_ENCOUNTER
BLT.FLAG_INSTANCE     = FLAG_INSTANCE
BLT.FLAG_TOKEN        = FLAG_TOKEN
BLT.FLAG_WORLDBOSS    = FLAG_WORLDBOSS
BLT.FLAG_QUEST        = FLAG_QUEST
BLT.FLAG_RARE         = FLAG_RARE
BLT.VALID_ITEM_CLASSES   = VALID_ITEM_CLASSES
BLT.EXCLUDED_EQUIP_LOCS  = EXCLUDED_EQUIP_LOCS
BLT.WORLD_BOSS_ENC_TO_NPC = WORLD_BOSS_ENC_TO_NPC
BLT.WORLD_BOSS_NPCS      = WORLD_BOSS_NPCS
BLT.WORLD_BOSS_NPC_SET   = WORLD_BOSS_NPC_SET
BLT.DIFFICULTY_INFO   = DIFFICULTY_INFO
-- v15.7.1: GetCurrentDifficulty is a file-local helper that file-locally
-- consumes DIFFICULTY_INFO. The v15.1.0 BLT split moved AddLootToTooltip
-- (the only caller) into Render.lua, but only re-exposed DIFFICULTY_INFO
-- on the owner — not the helper function that reads it. Result: hovering
-- an NPC with loot raised "attempt to call global 'GetCurrentDifficulty'
-- (a nil value)" because the bare call in Render couldn't see the local.
-- Re-expose it on the owner so Render can call owner.GetCurrentDifficulty().
BLT.GetCurrentDifficulty = GetCurrentDifficulty
BLT.MAX_ITEMS_SHOWN   = MAX_ITEMS_SHOWN
BLT.HEADER_COLOR      = HEADER_COLOR
BLT.COLLECTED_COLOR   = COLLECTED_COLOR
BLT.UNCOLLECTED_COLOR = UNCOLLECTED_COLOR
BLT.SEPARATOR_COLOR   = SEPARATOR_COLOR
BLT.TOKEN_COLOR       = TOKEN_COLOR
BLT.DIFFICULTY_COLOR  = DIFFICULTY_COLOR

-- AddToNPCIndex is a file-local function (not a method). Expose for Index.lua.
-- (Declaration appears below — Lua closures pick up the actual value when
-- the assignment line is executed. The assignment line for BLT.AddToNPCIndex
-- thus needs to come AFTER the local function declaration.)

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

-- Now that AddToNPCIndex is declared, expose it on BLT for submodule access.
BLT.AddToNPCIndex = AddToNPCIndex

function BLT:BuildIndex()
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Index
    return NS.ETB.BossLootTooltip.Index:BuildIndex(self)
end

-- v15.196.0: chunked build — spreads the ~400ms+ index pass across frames via the
-- work pump so it never freezes the client (reload / first corpse hover / zone-in).
-- Same code, same result; only the timing differs. Used by the deferred warm and
-- the lazy hover path; the synchronous BuildIndex stays for force-builds/fallback.
function BLT:BuildIndexChunked(onComplete)
    return NS.ETB.BossLootTooltip.Index:BuildIndexChunked(self, onComplete)
end

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

function BLT:GetNPCIDFromGUID(guid)
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Index
    return NS.ETB.BossLootTooltip.Index:GetNPCIDFromGUID(self, guid)
end

-- ============================================================================
-- ITEM COLLECTION CHECK
-- Uses cross-class appearance check for MoP Classic:
--   GetItemInfo(itemID) → appearanceID, sourceID
--   GetAllAppearanceSources(appearanceID) → sourceID[] (CROSS-CLASS, not filtered!)
--   PlayerHasTransmogItemModifiedAppearance(sourceID) → boolean per source
--
-- KEY: GetAppearanceSources is CLASS-FILTERED (returns nil for other armor types!)
--      GetAllAppearanceSources is CROSS-CLASS (returns all sources)
--      GetSourceInfo.isCollected is also CLASS-FILTERED
--      PlayerHasTransmogItemModifiedAppearance is NOT class-filtered
-- ============================================================================

-- Collection status cache: avoids repeated API calls per render
-- Cleared on TRANSMOG_COLLECTION_UPDATED event
BLT._collectionCache = {}

function BLT:ClearCollectionCache()
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Render
    return NS.ETB.BossLootTooltip.Render:ClearCollectionCache(self)
end

-- Returns: true (collected), false (not collected), nil (can't determine yet)
function BLT:IsAppearanceCollected(appearanceID)
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Render
    return NS.ETB.BossLootTooltip.Render:IsAppearanceCollected(self, appearanceID)
end

function BLT:IsItemCollected(itemID)
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Render
    return NS.ETB.BossLootTooltip.Render:IsItemCollected(self, itemID)
end

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

function BLT:CanPlayerUseItem(itemID)
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Render
    return NS.ETB.BossLootTooltip.Render:CanPlayerUseItem(self, itemID)
end

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

-- Status icons (using common UI textures)
local ICON_COLLECTED = "|TInterface\\RaidFrame\\ReadyCheck-Ready:14:14:0:0|t"
local ICON_NOT_COLLECTED = "|TInterface\\RaidFrame\\ReadyCheck-NotReady:14:14:0:0|t"

function BLT:AddLootToTooltip(tooltip, npcID)
    -- v15.36.0: low-memory mode skips the boss-loot index build (materializes
    -- BossLoot ~3.3 MB on first mob hover) and the per-mouseover render work.
    -- v15.190.1: but if the index is ALREADY resident — the player opened ETB,
    -- or the background warm-up ran — the data is in memory anyway, so render the
    -- tooltip. Lite mode only suppresses the heavy *build*, never a free render.
    if ETB.Settings and ETB.Settings:Get("performance.lowMemory") and not self.indexBuilt then
        return
    end
    -- v15.42.0: build lazily on the first real hover if the deferred background
    -- build hasn't run yet. BuildIndex self-guards against double builds, so
    -- this is a cheap no-op once ready — and means nothing heavy runs at login.
    if not self.indexBuilt then
        if not self._lazyBuildQueued then
            self._lazyBuildQueued = true
            local function buildLater()
                if not BLT.indexBuilt and not (InCombatLockdown and InCombatLockdown()) then
                    BLT:BuildIndexChunked()
                end
            end
            if ETB.RunWhenStartupIdle then
                ETB:RunWhenStartupIdle("bossLoot.hoverDeferredBuild", buildLater, { delay = 2, interval = 3, maxWait = 300 })
            elseif ETB.QueueWork then
                ETB:QueueWork("bossLoot.hoverDeferredBuild", buildLater, { pauseInCombat = true, startupIdleOptions = { ignoreQuietWindow = true } })
            end
        end
        return
    end
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Render
    return NS.ETB.BossLootTooltip.Render:AddLootToTooltip(self, tooltip, npcID)
end

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

function BLT:ShouldSuppress()
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Events
    return NS.ETB.BossLootTooltip.Events:ShouldSuppress(self)
end

function BLT:OnMouseoverUnit()
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Events
    return NS.ETB.BossLootTooltip.Events:OnMouseoverUnit(self)
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:RegisterEvent("GET_ITEM_INFO_RECEIVED")
    frame:RegisterEvent("TRANSMOG_COLLECTION_UPDATED")
    
    frame:SetScript("OnEvent", function(_, event, arg1)
        if event == "UPDATE_MOUSEOVER_UNIT" then
            -- Compare to current NPC — Blizzard fires this multiple times per hover
            local guid = UnitGUID("mouseover")
            local currentNPC = guid and BLT:GetNPCIDFromGUID(guid)
            if currentNPC ~= BLT.lastNPCID then
                BLT.lastNPCID = nil
                BLT:OnMouseoverUnit()
            end
        elseif event == "PLAYER_REGEN_DISABLED" then
            BLT.lastNPCID = nil
            if GameTooltip:IsShown() then
                GameTooltip:Hide()
            end
        elseif event == "TRANSMOG_COLLECTION_UPDATED" then
            BLT:ClearCollectionCache()
            -- Re-add our section if hovering a boss (don't reset with SetUnit)
            if UnitExists("mouseover") and GameTooltip:IsShown() and BLT.lastNPCID then
                BLT:AddLootToTooltip(GameTooltip, BLT.lastNPCID)
            end
        elseif event == "GET_ITEM_INFO_RECEIVED" then
            -- Item just cached. If hovering a boss, re-add our loot section.
            -- DON'T call SetUnit — that resets the tooltip and causes revert.
            local itemID = arg1
            if itemID and BLT._pendingItems and BLT._pendingItems[itemID] then
                BLT._pendingItems[itemID] = nil
                if not BLT._refreshThrottle then
                    BLT._refreshThrottle = true
                    C_Timer.After(0.3, function()
                        BLT._refreshThrottle = false
                        if UnitExists("mouseover") and GameTooltip:IsShown() and BLT.lastNPCID then
                            -- Re-add our section without resetting blizzard tooltip
                            BLT:AddLootToTooltip(GameTooltip, BLT.lastNPCID)
                        end
                    end)
                end
            end
        end
    end)
    
    self.frame = frame
    self.initialized = true
    
    -- Hook GameTooltip:OnTooltipSetUnit — Blizzard internally refreshes unit
    -- tooltips (health, buffs, etc) which clears all our AddLine content.
    -- Re-add our loot section on every internal refresh so it persists.
    GameTooltip:HookScript("OnTooltipSetUnit", function(tooltip)
        if BLT.lastNPCID and not BLT:ShouldSuppress() then
            BLT:AddLootToTooltip(tooltip, BLT.lastNPCID)
        end
    end)
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 shared:|r %d items (tier tokens, shared drops)\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" ..
        "  |cffffcc00SourceDB loot:|r %d items (general loot supplement)\n" ..
        "  Pending: %d encounters unresolved",
        s.totalNPCs, s.totalItems,
        s.directNPC,
        s.encounterResolved, s.encToNPCCount,
        s.instSharedLoot or 0,
        s.worldBoss or 0,
        s.questNPC or 0,
        s.rareMob or 0,
        s.sdbLootAdded or 0,
        s.pendingEncounter - s.encounterResolved)
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: pre-warm the loot index during the loading screen, enable hooks
-- shortly after.
-- ============================================================================

local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
initFrame:SetScript("OnEvent", function(self, event)
    self:UnregisterEvent("PLAYER_ENTERING_WORLD")

    -- Pre-warm the loot index while the loading screen is still up.
    -- BuildIndex is a heavy one-shot — it also materializes the boss-loot
    -- tables and the ATT source data. Run lazily it fired on the FIRST mob
    -- hover, i.e. a freeze "after the character starts moving around".
    -- Doing it here hides the cost during load, where lag is expected, and
    -- means the mouseover hooks (enabled below) never trigger a sync build.
    -- v15.42.0: do NOT build during the post-loading-screen crunch. The old
    -- C_Timer.After(0) here landed BuildIndex right as the player started
    -- moving — the exact moment a hitch is noticeable. In lite mode the tooltip
    -- is disabled, so skip entirely. Otherwise warm the index in the background
    -- once settled and out of combat; any NPC hover before then builds it
    -- lazily (see AddLootToTooltip), so tooltips still work immediately.
    -- Mouseover hooks come online after the pre-warm has had time to finish.
    local function initHooks()
        BLT:Initialize()
    end
    local function warmIndex()
        if BLT.indexBuilt or BLT.building then return end
        if ETB.IsLowMemoryMode and ETB:IsLowMemoryMode() then return end
        if InCombatLockdown and InCombatLockdown() then return end

        local ok, err = pcall(BLT.BuildIndexChunked, BLT)
        if not ok then
            BLT.building = false
            ETB:Debug("BossLoot", "warm index error: " .. tostring(err))
        end
    end
    if ETB.RunWhenStartupIdle then
        ETB:RunWhenStartupIdle("bossLoot.hooks", initHooks, { delay = 5, interval = 2, maxWait = 180, ignoreQuietWindow = true })
        ETB:RunWhenStartupIdle("bossLoot.warmIndex", warmIndex, { delay = 20, interval = 5, maxWait = 0 })
    else
        C_Timer.After(5, initHooks)
        C_Timer.After(20, warmIndex)
    end
end)

BLT.enabled = true

-- Build reverse index: encounterID → {itemIDs} for fast JLoot lookup
-- Called once during BuildIndex, avoids O(n) scan on every hover
function BLT:BuildJLootIndex()
    -- v15.1.0: implementation lives in NS.ETB.BossLootTooltip.Index
    return NS.ETB.BossLootTooltip.Index:BuildJLootIndex(self)
end
