--[[
============================================================================
ETB Loot Audit Tool — Internal module with full namespace access
============================================================================
Commands (via /etb audit):
  /etb audit           — Full audit
  /etb audit multi     — Multi-NPC encounter check
  /etb audit orphans   — Items with no NPC mapping
  /etb audit compare   — compact vs JLoot vs SourceDB coverage
  /etb audit item 1234 — Deep lookup for one item
  /etb audit boss 71865— All items for one NPC
  /etb audit drops     — World drop / trash drop analysis
============================================================================
]]--

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

ETB.LootAudit = {}
local A = ETB.LootAudit

local VALID_CLASSES = { [2] = true, [4] = true }
local EXCLUDED_LOCS = { INVTYPE_TRINKET=true, INVTYPE_FINGER=true, INVTYPE_NECK=true }

local ST_NAMES = NS.Compact and NS.Compact.ST or {}

-- ============================================================================
-- MULTI-NPC ENCOUNTERS
-- ============================================================================
function A:AuditMultiNPC()
    ETB:Print("|cffffcc00=== Multi-NPC Encounter Audit ===|r")
    
    if ETB._BossLootLoader then ETB._BossLootLoader(); ETB._BossLootLoader = nil end
    if not ETB.JEncToNpcs then
        ETB:Print("|cffff4444JEncToNpcs not loaded|r")
        return
    end
    
    local total = 0
    local multi = 0
    for encID, npcData in pairs(ETB.JEncToNpcs) do
        total = total + 1
        if type(npcData) == "table" then
            multi = multi + 1
            local enc = ETB.JEncounters and ETB.JEncounters[encID]
            local name = enc and enc[1] or ("Enc " .. encID)
            local instID = enc and enc[2]
            local inst = instID and ETB.JInstances and ETB.JInstances[instID]
            local instName = inst and inst[1] or "?"
            
            -- Check tooltip mapping
            local blt = ETB.BossLootTooltip
            local mapped, unmapped = 0, {}
            if blt and blt.npcIndex then
                for _, npcID in ipairs(npcData) do
                    if blt.npcIndex[npcID] then
                        mapped = mapped + 1
                    else
                        unmapped[#unmapped + 1] = npcID
                    end
                end
            end
            
            local status = mapped == #npcData and "|cff00ff00ALL MAPPED|r" or
                string.format("|cffff4444%d/%d|r", mapped, #npcData)
            ETB:Print(string.format("  [%d] %s (%s): %d NPCs — %s",
                encID, name, instName, #npcData, status))
            for _, npc in ipairs(unmapped) do
                ETB:Print(string.format("    |cffff8800Missing: npcID %d|r", npc))
            end
        end
    end
    
    ETB:Print(string.format("  Total encounters: %d | Multi-NPC: %d", total, multi))
end

-- ============================================================================
-- ORPHAN ITEMS
-- ============================================================================
function A:AuditOrphans()
    ETB:Print("|cffffcc00=== Orphan Item Audit ===|r")
    
    local compact = NS.Compact
    if not compact then ETB:Print("|cffff4444Compact data not loaded|r"); return end
    
    local blt = ETB.BossLootTooltip
    local npcIndex = blt and blt.npcIndex or {}
    local instanceLoot = blt and blt.instanceLoot or {}
    
    local total_boss = 0
    local total_boss_transmog = 0
    local skipped_nontransmog = 0
    local instance_bucketed = 0
    local no_locator = 0
    local mapped = 0
    local expansions = { "Classic", "TBC", "Wrath", "Cata", "MoP" }
    
    -- Build fast lookup: itemID → true for all instance-bucketed items
    local instBucketed = {}
    for _, items in pairs(instanceLoot) do
        for itemID in pairs(items) do
            instBucketed[itemID] = true
        end
    end
    
    for _, expName in ipairs(expansions) do
        local data = compact[expName]
        if data and data.I then
            for itemID, rawV in pairs(data.I) do
                local raw = NS.Compact.Head(rawV)
                if raw then
                    local st = raw[3] or 0
                    if st == 2 then -- BOSS
                        total_boss = total_boss + 1
                        
                        -- Filter: only count transmog-eligible items
                        -- (Same filter BossLootTooltip uses at display time)
                        local _, _, _, equipLoc, _, classID = GetItemInfoInstant(itemID)
                        if not classID or not VALID_CLASSES[classID] or (equipLoc and EXCLUDED_LOCS[equipLoc]) then
                            skipped_nontransmog = skipped_nontransmog + 1
                        else
                        
                        total_boss_transmog = total_boss_transmog + 1
                        local npcID = raw[2] or 0
                        local encID = raw[4] or 0
                        local instID = raw[5] or 0
                        
                        -- Check if in tooltip NPC index
                        local isMapped = false
                        for _, items in pairs(npcIndex) do
                            if items[itemID] then isMapped = true; break end
                        end
                        
                        if isMapped then
                            mapped = mapped + 1
                        elseif instBucketed[itemID] then
                            instance_bucketed = instance_bucketed + 1
                        elseif npcID == 0 and encID == 0 and instID == 0 then
                            no_locator = no_locator + 1
                        end
                        
                        end -- transmog filter
                    end
                end
            end
        end
    end
    
    local accounted = mapped + instance_bucketed
    ETB:Print(string.format("  Boss items:         |cffffffff%d|r", total_boss))
    ETB:Print(string.format("  Non-transmog:       |cff888888%d|r (necks, trinkets, rings, non-equipment)", skipped_nontransmog))
    ETB:Print(string.format("  Transmog-eligible:  |cffffffff%d|r", total_boss_transmog))
    ETB:Print(string.format("  Mapped to NPC:      |cff00ff00%d|r (%.0f%%)", mapped, total_boss_transmog > 0 and 100*mapped/total_boss_transmog or 0))
    ETB:Print(string.format("  Instance shared:    |cff00ccff%d|r (tier tokens, shared drops)", instance_bucketed))
    ETB:Print(string.format("  No locator:         |cffff4444%d|r (no NPC, encounter, or instance)", no_locator))
    ETB:Print(string.format("  Total accounted:    |cff00ff00%d|r (%.0f%%)", accounted, total_boss_transmog > 0 and 100*accounted/total_boss_transmog or 0))
end

-- ============================================================================
-- COVERAGE COMPARISON
-- ============================================================================
function A:AuditCoverage()
    ETB:Print("|cffffcc00=== Data Source Coverage ===|r")
    
    local compact = NS.Compact
    local jloot = ETB.JLoot
    
    local compactBoss = {}
    local compactAll = {}
    if compact then
        for _, exp in ipairs({"Classic","TBC","Wrath","Cata","MoP"}) do
            local data = compact[exp]
            if data and data.I then
                for itemID, rawV in pairs(data.I) do
                    local raw = NS.Compact.Head(rawV)
                    if raw then
                        compactAll[itemID] = true
                        if raw[3] == 2 or raw[3] == 8 then
                            compactBoss[itemID] = true
                        end
                    end
                end
            end
        end
    end
    
    local jlootItems = {}
    if jloot then
        for itemID in pairs(jloot) do jlootItems[itemID] = true end
    end
    
    -- Count SourceDB (uses public API — NS["Source_"..exp] doesn't exist)
    local sdbItems = {}
    if ETB.SourceDB and ETB.SourceDB.GetAllItemIDs then
        sdbItems = ETB.SourceDB:GetAllItemIDs()
    end
    
    local totalC = 0; for _ in pairs(compactAll) do totalC = totalC + 1 end
    local totalCB = 0; for _ in pairs(compactBoss) do totalCB = totalCB + 1 end
    local totalJ = 0; for _ in pairs(jlootItems) do totalJ = totalJ + 1 end
    local totalS = 0; for _ in pairs(sdbItems) do totalS = totalS + 1 end
    
    -- SourceDB items NOT in compact or JLoot
    local sdbOnly = 0
    for id in pairs(sdbItems) do
        if not compactAll[id] and not jlootItems[id] then sdbOnly = sdbOnly + 1 end
    end
    -- JLoot items NOT in compact
    local jlootOnly = 0
    for id in pairs(jlootItems) do
        if not compactAll[id] then jlootOnly = jlootOnly + 1 end
    end
    
    ETB:Print(string.format("  Compact (all):      |cff00ff00%d|r items", totalC))
    ETB:Print(string.format("  Compact (boss+rare): |cff00ff00%d|r items", totalCB))
    ETB:Print(string.format("  JLoot (DB2):         |cff00ccff%d|r items", totalJ))
    ETB:Print(string.format("  SourceDB:           |cffffcc00%d|r items", totalS))
    ETB:Print("")
    ETB:Print(string.format("  JLoot not in compact:    |cff00ccff%d|r (supplemented via Pass 6)", jlootOnly))
    ETB:Print(string.format("  SourceDB not in either:  |cffff8800%d|r (supplemented via Pass 1.6 where encounterID exists)", sdbOnly))
end

-- ============================================================================
-- WORLD DROP / TRASH ANALYSIS
-- ============================================================================
function A:AuditDrops()
    ETB:Print("|cffffcc00=== World Drop & Trash Analysis ===|r")
    
    local compact = NS.Compact
    if not compact then ETB:Print("|cffff4444Compact not loaded|r"); return end
    
    local world_total = 0
    local world_with_zone = 0
    local world_with_npc = 0
    local boss_total = 0
    local vendor_total = 0
    local quest_total = 0
    local craft_total = 0
    local pvp_total = 0
    
    local ZN = NS.Compact.ZN or {}
    
    for _, exp in ipairs({"Classic","TBC","Wrath","Cata","MoP"}) do
        local data = compact[exp]
        if data and data.I then
            for itemID, rawV in pairs(data.I) do
                local raw = NS.Compact.Head(rawV)
                if raw then
                    local st = raw[3] or 0
                    if st == 13 then -- WORLD_DROP
                        world_total = world_total + 1
                        local npcID = raw[2] or 0
                        local zoneIdx = raw[16] or 0
                        if zoneIdx > 0 then world_with_zone = world_with_zone + 1 end
                        if npcID > 0 then world_with_npc = world_with_npc + 1 end
                    elseif st == 2 then boss_total = boss_total + 1
                    elseif st == 12 then vendor_total = vendor_total + 1
                    elseif st == 7 then quest_total = quest_total + 1
                    elseif st == 3 then craft_total = craft_total + 1
                    elseif st == 6 then pvp_total = pvp_total + 1
                    end
                end
            end
        end
    end
    
    -- Check SourceDB trash flags (uses public API — NS["Source_"..exp] doesn't exist)
    local sdb_trash = 0
    local sdb_raid_trash = 0
    local sdb_dungeon_trash = 0
    if ETB.SourceDB and ETB.SourceDB.GetAllItemIDs then
        local allSdbIDs = ETB.SourceDB:GetAllItemIDs()
        for itemID in pairs(allSdbIDs) do
            local rec = ETB.SourceDB:Get(itemID)
            if rec then
                if rec.isTrash then
                    sdb_trash = sdb_trash + 1
                    if rec.isRaid then sdb_raid_trash = sdb_raid_trash + 1 end
                    if rec.isDungeon then sdb_dungeon_trash = sdb_dungeon_trash + 1 end
                end
            end
        end
    end
    
    ETB:Print(string.format("  |cffffffffSource type breakdown:|r"))
    ETB:Print(string.format("    Boss drops:   %d", boss_total))
    ETB:Print(string.format("    Vendors:      %d", vendor_total))
    ETB:Print(string.format("    Quests:       %d", quest_total))
    ETB:Print(string.format("    World drops:  %d", world_total))
    ETB:Print(string.format("    Crafted:      %d", craft_total))
    ETB:Print(string.format("    PVP:          %d", pvp_total))
    ETB:Print("")
    ETB:Print(string.format("  |cffffffffWorld drops detail:|r"))
    ETB:Print(string.format("    With zone:    |cff00ff00%d|r (%.0f%%)", world_with_zone, 100*world_with_zone/math.max(1,world_total)))
    ETB:Print(string.format("    With NPC:     |cff00ff00%d|r (specific creature)", world_with_npc))
    ETB:Print(string.format("    No zone data: |cffff4444%d|r (%.0f%%)", world_total - world_with_zone, 100*(world_total-world_with_zone)/math.max(1,world_total)))
    ETB:Print("")
    ETB:Print(string.format("  |cffffffffSourceDB trash drops:|r"))
    ETB:Print(string.format("    Dungeon trash: |cffff8800%d|r", sdb_dungeon_trash))
    ETB:Print(string.format("    Raid trash:    |cffff8800%d|r", sdb_raid_trash))
    ETB:Print(string.format("    Total trash:   |cffff8800%d|r", sdb_trash))
end

-- ============================================================================
-- ITEM LOOKUP
-- ============================================================================
function A:LookupItem(itemID)
    itemID = tonumber(itemID)
    if not itemID then ETB:Print("Usage: /etb audit item <itemID>"); return end
    
    local name = GetItemInfo(itemID) or "Loading..."
    ETB:Print(string.format("|cffffcc00=== Item %d: %s ===|r", itemID, name))
    
    -- Compact data
    local compact = NS.Compact
    if compact then
        for _, exp in ipairs({"Classic","TBC","Wrath","Cata","MoP"}) do
            local data = compact[exp]
            if data and data.I and data.I[itemID] then
                local raw = NS.Compact.Head(data.I[itemID])
                if raw then
                    local st = raw[3] or 0
                    local stName = (ST_NAMES[st] or ("ST=" .. st))
                    ETB:Print(string.format("  |cff00ff00Compact (%s):|r npc=%s st=%s enc=%s inst=%s zone=%s",
                        exp, tostring(raw[2]), stName, tostring(raw[4]), tostring(raw[5]), tostring(raw[16])))
                end
            end
        end
    end
    
    -- JLoot
    if ETB.JLoot and ETB.JLoot[itemID] then
        local ld = ETB.JLoot[itemID]
        local entries = type(ld[1]) == "table" and ld or { ld }
        for i, entry in ipairs(entries) do
            local encID = entry[1]
            local enc = ETB.JEncounters and ETB.JEncounters[encID]
            local encName = enc and enc[1] or "?"
            ETB:Print(string.format("  |cff00ccffJLoot[%d]:|r enc=%d (%s) diff=%d", i, encID, encName, entry[2] or 0))
        end
    else
        ETB:Print("  |cff888888JLoot: not found|r")
    end
    
    -- SourceDB
    if ETB.SourceDB and ETB.SourceDB.Get then
        local rec = ETB.SourceDB:Get(itemID)
        if rec then
            ETB:Print(string.format("  |cffffcc00SourceDB:|r boss=%s inst=%s trash=%s raid=%s wb=%s",
                tostring(rec.bossName), tostring(rec.instanceName),
                tostring(rec.isTrash), tostring(rec.isRaid), tostring(rec.isWorldBoss)))
        else
            ETB:Print("  |cff888888SourceDB: not found|r")
        end
    end
    
    -- SourceLookup
    if ETB.SourceLookup then
        local src = ETB.SourceLookup:GetSourceText(itemID)
        ETB:Print(string.format("  |cffff8800SourceLookup:|r %s", src or "nil"))
    end
    
    -- Tooltip index
    local blt = ETB.BossLootTooltip
    if blt and blt.npcIndex then
        local found = false
        for npcID, items in pairs(blt.npcIndex) do
            if items[itemID] then
                ETB:Print(string.format("  |cffff00ffTooltip:|r npcID=%d flag=%d", npcID, items[itemID]))
                found = true
            end
        end
        if not found then
            ETB:Print("  |cffff4444Tooltip: NOT MAPPED to any NPC|r")
        end
    end
end

-- ============================================================================
-- BOSS LOOKUP
-- ============================================================================
function A:LookupBoss(npcID)
    npcID = tonumber(npcID)
    if not npcID then ETB:Print("Usage: /etb audit boss <npcID>"); return end
    
    local encID = ETB.JNpcToEnc and ETB.JNpcToEnc[npcID]
    local enc = encID and ETB.JEncounters and ETB.JEncounters[encID]
    local encName = enc and enc[1] or "Unknown"
    
    ETB:Print(string.format("|cffffcc00=== NPC %d -> Enc %s: %s ===|r", npcID, tostring(encID), encName))
    
    local blt = ETB.BossLootTooltip
    if blt and blt.npcIndex and blt.npcIndex[npcID] then
        local count = 0
        for itemID, flag in pairs(blt.npcIndex[npcID]) do
            count = count + 1
            if count <= 25 then
                local iname = GetItemInfo(itemID)
                local ok, collected = pcall(C_TransmogCollection.PlayerHasTransmog, itemID)
                local status = (ok and collected) and "|cff00ff00Y|r" or "|cffff4444N|r"
                ETB:Print(string.format("  %s [%d] %s (f=%d)", status, itemID, iname or "Loading...", flag))
            end
        end
        ETB:Print(string.format("  Total: %d items", count))
    else
        ETB:Print("  |cffff4444No items in tooltip index|r")
    end
end

-- ============================================================================
-- CLASS / ARMOR METADATA COVERAGE
-- Proactively flags loot-flagged ARMOR items that have no class/armor
-- restriction data. These are the items that silently "show for every class"
-- in boss tooltips (the Ordos / Timeless-Isle bug class). Run after hovering a
-- boss so the item cache is warm.
-- ============================================================================
function A:AuditClassData()
    ETB:Print("|cffffcc00=== Class/Armor Metadata Coverage ===|r")

    local CR = ETB.ClassRestrictions
    if not CR then
        ETB:Print("|cffff4444ClassRestrictions module not loaded|r")
        return
    end

    -- Gather candidate loot items: world-boss + general loot from SourceDB.
    local candidates = {}
    if ETB.SourceDB then
        if ETB.SourceDB.GetAllWorldBossItems then
            for _, e in ipairs(ETB.SourceDB:GetAllWorldBossItems()) do
                candidates[e.itemID] = "worldboss"
            end
        end
        if ETB.SourceDB.GetAllLootItems then
            for _, e in ipairs(ETB.SourceDB:GetAllLootItems()) do
                if not candidates[e.itemID] then candidates[e.itemID] = "loot" end
            end
        end
    end

    local armorTotal, armorCovered = 0, 0
    local gapsWB, gapsOther = {}, {}
    for itemID, kind in pairs(candidates) do
        local classID, subclassID
        if GetItemInfoInstant then
            local _, _, _, _, _, c, sc = GetItemInfoInstant(itemID)
            classID, subclassID = c, sc
        end
        -- Only armor with a real armor type (1=Cloth 2=Leather 3=Mail 4=Plate).
        if classID == 4 and subclassID and subclassID >= 1 and subclassID <= 4 then
            armorTotal = armorTotal + 1
            local r = CR:GetItemClassRestrictions(itemID)
            if r then
                armorCovered = armorCovered + 1
            else
                local bucket = (kind == "worldboss") and gapsWB or gapsOther
                bucket[#bucket + 1] = itemID
            end
        end
    end

    local function sample(t, n)
        local out = {}
        for i = 1, math.min(n, #t) do out[i] = t[i] end
        return table.concat(out, ", ")
    end

    ETB:Print(string.format("  Armor loot items checked: |cff00ff00%d|r", armorTotal))
    ETB:Print(string.format("  With class/armor data:    |cff00ff00%d|r", armorCovered))
    ETB:Print(string.format("  World-boss gaps:          |cffff8800%d|r%s",
        #gapsWB, #gapsWB > 0 and ("  e.g. " .. sample(gapsWB, 8)) or ""))
    ETB:Print(string.format("  Other loot gaps:          |cffffcc00%d|r%s",
        #gapsOther, #gapsOther > 0 and ("  e.g. " .. sample(gapsOther, 8)) or ""))
    if #gapsWB == 0 and #gapsOther == 0 then
        ETB:Print("  |cff00ff00No gaps — all armor loot filters by class.|r")
    else
        ETB:Print("  |cff888888Gaps show for every class. Bake masks into|r")
        ETB:Print("  |cff888888WorldBossArmorClass.lua (armor type -> class).|r")
    end
end

-- ============================================================================
-- FULL AUDIT
-- ============================================================================
function A:RunFull()
    ETB:Print("|cff00ffff======== FULL LOOT AUDIT ========|r")
    
    self:AuditMultiNPC()
    ETB:Print("")
    self:AuditOrphans()
    ETB:Print("")
    self:AuditCoverage()
    ETB:Print("")
    self:AuditClassData()
    ETB:Print("")
    self:AuditDrops()
    
    -- Tooltip stats
    local blt = ETB.BossLootTooltip
    if blt and blt.stats then
        local s = blt.stats
        ETB:Print("")
        ETB:Print("|cffffcc00=== Tooltip Index Stats ===|r")
        ETB:Print(string.format("  NPCs: %d | Items: %d", s.totalNPCs or 0, s.totalItems or 0))
        ETB:Print(string.format("  Direct:%d Enc:%d InstShared:%d WBoss:%d Quest:%d Rare:%d JLoot:%d SDBLoot:%d",
            s.directNPC or 0, s.encounterResolved or 0, s.instSharedLoot or 0, s.worldBoss or 0,
            s.questNPC or 0, s.rareMob or 0, s.jlootAdded or 0, s.sdbLootAdded or 0))
    else
        ETB:Print("")
        ETB:Print("|cffff8800Tooltip index not built yet (hover a boss first)|r")
    end
    
    ETB:Print("")
    ETB:Print("|cff00ffff======== AUDIT COMPLETE ========|r")
end

ETB:Debug("LootAudit", "Loot audit module loaded")
