--[[
============================================================================
ETB v10.0 - BlizzardData.lua
============================================================================
Zero-database API layer. Retrieves all item data from Blizzard APIs.
Only drop sources are stored locally.
============================================================================
]]--

local ADDON_NAME, NS = ...
local ETB = NS.ETB

-- v15.6.1: hotfix for namespace-wipe bug introduced in v15.2.0
--
-- The previous code was:
--     ETB.BlizzardData = { cache = ..., itemCache = ..., ... }
-- This is a table-constructor assignment that REPLACES the table — wiping
-- the .Player, .Items, .Diagnostics submodule references that the three
-- submodule files (loaded earlier in the TOC) had attached to it.
--
-- Symptom: ETB.BlizzardData:GetAllStats() and other delegator methods
-- raised "attempt to index field 'Diagnostics' (a nil value)" when called.
-- The bug went largely unnoticed because the most common delegator paths
-- (Player.GetCategoryAppearances et al.) were rarely hit on warm caches,
-- but the MinimapButton tooltip's GetAllStats call exposed it.
--
-- Fix: preserve the existing table (which may already have submodule refs
-- on it) and assign fields individually. This matches the established
-- "ETB.X = ETB.X or {}" pattern used by every other orchestrator.

ETB.BlizzardData = ETB.BlizzardData or {}
ETB.BlizzardData.cache = {
    categories = {},
    sources = {},
}
ETB.BlizzardData.itemCache = nil  -- LRU cache
ETB.BlizzardData.iconCache = nil  -- LRU cache
ETB.BlizzardData.pendingItemInfo = {}

ETB.BlizzardData.stats = {
    categoryQueries = 0,
    sourceQueries = 0,
    cacheHits = 0,
    cacheMisses = 0,
}

ETB.BlizzardData.initialized = false

-- ============================================================================
-- Optional: If a collection-tracking addon is present, use it for instant name resolution
-- when GetItemInfo() hasn't cached an item yet
-- for an instant name rather than showing "Loading..."
-- ============================================================================

local function GetCachedItemName(itemID)
    if not itemID then return nil end
    local name = GetItemInfo(itemID)
    return name
end

local BD = ETB.BlizzardData

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

function BD:Initialize()
    if self.initialized then return true end
    
    ETB:Debug("BlizzardData", "Initializing...")
    
    -- Verify APIs
    if not C_TransmogCollection then
        ETB:PrintError("C_TransmogCollection not available!")
        return false
    end
    
    if not C_TransmogCollection.GetCategoryAppearances then
        ETB:PrintError("GetCategoryAppearances not available!")
        return false
    end
    
    -- Initialize LRU caches (kept small to limit memory — items rebuild from API)
    self.itemCache = ETB:CreateLRUCache(200, "Items")
    self.iconCache = ETB:CreateLRUCache(300, "Icons")
    
    -- DEFERRED: Don't count all appearances at login — do it on first UI open
    -- The old code iterated ALL 27 categories calling GetCategoryAppearances
    -- which caused a 1-2 second freeze at login+1s
    self._statsComputed = false
    
    ETB:Debug("BlizzardData", "Initialized (stats deferred)")
    
    -- Initialize specialized modules
    if ETB.ArmorData and ETB.ArmorData.Initialize then
        ETB.ArmorData:Initialize()
    end
    
    if ETB.WeaponData and ETB.WeaponData.Initialize then
        ETB.WeaponData:Initialize()
    end
    
    self.initialized = true
    return true
end

-- =============================================================================
-- CORE API METHODS
-- =============================================================================

function BD:GetCategoryAppearances(categoryID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetCategoryAppearances(self, categoryID)
end

function BD:GetAppearanceSources(visualID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetAppearanceSources(self, visualID)
end

function BD:GetItemIcon(itemID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetItemIcon(self, itemID)
end

-- =============================================================================
-- CLASS ARMOR PROFICIENCY
-- =============================================================================

-- What armor types each class can wear (at max level)
local CLASS_ARMOR = {
    WARRIOR     = { PLATE = true, MAIL = true, LEATHER = true, CLOTH = true },
    PALADIN     = { PLATE = true, MAIL = true, LEATHER = true, CLOTH = true },
    DEATHKNIGHT = { PLATE = true, MAIL = true, LEATHER = true, CLOTH = true },
    HUNTER      = { MAIL = true, LEATHER = true, CLOTH = true },
    SHAMAN      = { MAIL = true, LEATHER = true, CLOTH = true },
    ROGUE       = { LEATHER = true, CLOTH = true },
    DRUID       = { LEATHER = true, CLOTH = true },
    MONK        = { LEATHER = true, CLOTH = true },
    MAGE        = { CLOTH = true },
    WARLOCK     = { CLOTH = true },
    PRIEST      = { CLOTH = true },
}

-- v14.8.0: tables consolidated to Data/Categories/ClassData.lua. The locals
-- below either reference the canonical table or fall back to inline values
-- if ClassData failed to load.

local _CD = ETB.ClassData
local CLASS_PRIMARY_ARMOR = _CD and _CD.PRIMARY_ARMOR or {
    WARRIOR     = "PLATE",
    PALADIN     = "PLATE",
    DEATHKNIGHT = "PLATE",
    HUNTER      = "MAIL",
    SHAMAN      = "MAIL",
    ROGUE       = "LEATHER",
    DRUID       = "LEATHER",
    MONK        = "LEATHER",
    MAGE        = "CLOTH",
    WARLOCK     = "CLOTH",
    PRIEST      = "CLOTH",
}

local CLASS_WEAPONS = _CD and _CD.WEAPONS or {
    WARRIOR     = { AXE = true, SWORD = true, MACE = true, POLEARM = true, STAFF = true, DAGGER = true, FIST = true, SHIELD = true },
    PALADIN     = { AXE = true, SWORD = true, MACE = true, POLEARM = true, SHIELD = true },
    DEATHKNIGHT = { AXE = true, SWORD = true, MACE = true, POLEARM = true },
    HUNTER      = { AXE = true, SWORD = true, POLEARM = true, STAFF = true, DAGGER = true, FIST = true, BOW = true, GUN = true, CROSSBOW = true },
    SHAMAN      = { AXE = true, MACE = true, STAFF = true, DAGGER = true, FIST = true, SHIELD = true },
    ROGUE       = { AXE = true, SWORD = true, MACE = true, DAGGER = true, FIST = true },
    DRUID       = { MACE = true, POLEARM = true, STAFF = true, DAGGER = true, FIST = true },
    MONK        = { AXE = true, SWORD = true, MACE = true, POLEARM = true, STAFF = true, FIST = true },
    MAGE        = { SWORD = true, STAFF = true, DAGGER = true, WAND = true },
    WARLOCK     = { SWORD = true, STAFF = true, DAGGER = true, WAND = true },
    PRIEST      = { MACE = true, STAFF = true, DAGGER = true, WAND = true },
}

-- ============================================================================
-- BD NAMESPACE EXPOSURES (added v15.2.0 — Audit Batch 4 Part 2)
-- ============================================================================
-- Expose file-locals onto the BD namespace so submodules can access them via
-- the owner-pattern (owner.CLASS_ARMOR, owner.GetCachedItemName). Same pattern
-- used in TransmogUI v14.9+ and BossLootTooltip v15.1.
BD.CLASS_ARMOR         = CLASS_ARMOR
BD.CLASS_PRIMARY_ARMOR = CLASS_PRIMARY_ARMOR
BD.CLASS_WEAPONS       = CLASS_WEAPONS
BD.GetCachedItemName   = GetCachedItemName  -- the file-local utility function

function BD:GetPlayerArmorTypes()
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetPlayerArmorTypes(self)
end

function BD:GetPlayerPrimaryArmor()
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetPlayerPrimaryArmor(self)
end

function BD:CanPlayerWearArmorType(armorType)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:CanPlayerWearArmorType(self, armorType)
end

-- Check if player can equip any armor type (for general use)
function BD:CanPlayerEquipArmorType(armorType)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:CanPlayerEquipArmorType(self, armorType)
end

function BD:GetPlayerWeaponTypes()
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetPlayerWeaponTypes(self)
end

function BD:CanPlayerUseWeaponType(weaponType)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:CanPlayerUseWeaponType(self, weaponType)
end

-- Get the class name being used for filtering (for UI display)
function BD:GetActiveClassName()
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetActiveClassName(self)
end

-- =============================================================================
-- BUILD ITEM ENTRY
-- =============================================================================

-- Armor type detection from itemID
--
-- v14.8.0 audit note: NOT consolidated with OutfitCompatibility:GetItemArmorType
-- despite their similar appearance. The two have different semantics:
--   - BD:GetArmorType returns 5 values (CLOTH/LEATHER/MAIL/PLATE/SHIELD).
--     SHIELD is included because callers ask "what kind of equipment is this".
--   - OC:GetItemArmorType returns 4 values, treating shields separately as a
--     weapon-type for class-proficiency checks.
--   - BD has a GetItemInfo string-name fallback (Classic-friendly); OC doesn't.
-- They each have their own cache because they answer different questions.
-- If they ever need to share, introduce a shared classifier returning
-- a richer struct, not a delegate-and-translate layer.
function BD:GetArmorType(itemID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetArmorType(self, itemID)
end

-- Weapon type detection from itemID
-- Uses GetItemInfoInstant first (if available), then falls back to GetItemInfo (works in Classic)
function BD:GetWeaponType(itemID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Player
    return NS.ETB.BlizzardData.Player:GetWeaponType(self, itemID)
end

function BD:BuildItemEntry(source, appearance, categoryID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Items
    return NS.ETB.BlizzardData.Items:BuildItemEntry(self, source, appearance, categoryID)
end

-- =============================================================================
-- ITEM INFO (ASYNC)
-- =============================================================================


function BD:UpdateItemInfo(itemID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Items
    return NS.ETB.BlizzardData.Items:UpdateItemInfo(self, itemID)
end

function BD:UpdatePendingItems()
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Items
    return NS.ETB.BlizzardData.Items:UpdatePendingItems(self)
end

-- =============================================================================
-- GET ITEMS FOR CATEGORY
-- =============================================================================

-- Helper to check if category is a weapon category
function BD:IsWeaponCategory(categoryID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Items
    return NS.ETB.BlizzardData.Items:IsWeaponCategory(self, categoryID)
end

function BD:GetItemsForCategory(categoryID, options)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Items
    return NS.ETB.BlizzardData.Items:GetItemsForCategory(self, categoryID, options)
end

-- Legacy implementation (kept for reference, but routes to modules above)
function BD:GetItemsForCategory_Legacy(categoryID, options)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Items
    return NS.ETB.BlizzardData.Items:GetItemsForCategory_Legacy(self, categoryID, options)
end

-- =============================================================================
-- DATA VERIFICATION & DIAGNOSTICS
-- =============================================================================

-- Diagnostic function to verify data completeness against Blizzard's Appearance tab
function BD:VerifyDataCompleteness(categoryID, sampleSize)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Diagnostics
    return NS.ETB.BlizzardData.Diagnostics:VerifyDataCompleteness(self, categoryID, sampleSize)
end

-- Verify data pipeline completeness for a category
function BD:VerifyDataPipeline(categoryID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Diagnostics
    return NS.ETB.BlizzardData.Diagnostics:VerifyDataPipeline(self, categoryID)
end

-- Compare our item count with Blizzard's for a category
function BD:CompareItemCounts(categoryID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Diagnostics
    return NS.ETB.BlizzardData.Diagnostics:CompareItemCounts(self, categoryID)
end

-- =============================================================================
-- COLLECTION STATS
-- =============================================================================

function BD:GetCollectionStats(categoryID)
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Diagnostics
    return NS.ETB.BlizzardData.Diagnostics:GetCollectionStats(self, categoryID)
end

function BD:GetAllStats()
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Diagnostics
    return NS.ETB.BlizzardData.Diagnostics:GetAllStats(self)
end

-- =============================================================================
-- CACHE MANAGEMENT
-- =============================================================================

function BD:InvalidateCollectionCache()
    wipe(self.cache.categories)
    ETB:Debug("BlizzardData", "Collection cache invalidated")
end

function BD:ClearCache()
    wipe(self.cache.categories)
    wipe(self.cache.sources)
    if self.itemCache then self.itemCache:Clear() end
    if self.iconCache then self.iconCache:Clear() end
    ETB:Print("BlizzardData cache cleared")
end

-- Trim source cache to prevent unbounded growth during long sessions
-- Called periodically when closing the main frame or switching categories
function BD:TrimSourceCache()
    local count = 0
    for _ in pairs(self.cache.sources) do count = count + 1 end
    
    if count > 500 then
        wipe(self.cache.sources)
        ETB:Debug("BlizzardData", string.format("Trimmed source cache (was %d entries)", count))
    end
end

function BD:GetCacheStats()
    return {
        categories = 0,
        sources = 0,
        items = self.itemCache and self.itemCache:GetStats() or {},
        icons = self.iconCache and self.iconCache:GetStats() or {},
        hits = self.stats.cacheHits,
        misses = self.stats.cacheMisses,
    }
end

function BD:PrintStats()
    -- v15.2.0: implementation lives in NS.ETB.BlizzardData.Diagnostics
    return NS.ETB.BlizzardData.Diagnostics:PrintStats(self)
end

-- =============================================================================
-- SLASH COMMANDS FOR DIAGNOSTICS
-- =============================================================================

-- Register slash commands for data verification
SLASH_ETBDATAVERIFY1 = "/etbverify"
SLASH_ETBDATAVERIFY2 = "/etbv"
SlashCmdList["ETBDATAVERIFY"] = function(msg)
    local args = {}
    for arg in string.gmatch(msg, "%S+") do
        table.insert(args, arg)
    end
    
    if not ETB.BlizzardData or not ETB.BlizzardData.initialized then
        ETB:Print("|cffff0000[ETB]|r BlizzardData not initialized yet!")
        return
    end
    
    local cmd = args[1] and string.lower(args[1]) or "help"
    
    if cmd == "help" then
        ETB:Print("|cff00ffff[ETB Data Verify]|r Commands:")
        ETB:Print("  |cffffffff/etbverify pipeline <categoryID>|r - Verify complete data pipeline (recommended)")
        ETB:Print("  |cffffffff/etbverify completeness <categoryID>|r - Verify data completeness for a category")
        ETB:Print("  |cffffffff/etbverify count <categoryID>|r - Compare item counts with Blizzard's API")
        ETB:Print("  |cffffffff/etbverify categories|r - List all categories from Blizzard API")
        ETB:Print("  |cffffffff/etbverify all|r - Run all verification checks")
        ETB:Print("  |cffffffff/etbverify stats|r - Show data retrieval statistics")
        ETB:Print("")
        ETB:Print("Category IDs: 1=Head, 2=Shoulders, 3=Back, 4=Chest, 5=Wrist, 6=Hands, 7=Waist, 8=Legs, 9=Feet, 10=MainHand, 11=OffHand, 12=MainHand, 13=OffHand")
    elseif cmd == "completeness" then
        local categoryID = tonumber(args[2])
        if not categoryID then
            ETB:Print("|cffff0000[ETB]|r Usage: /etbverify completeness <categoryID>")
            return
        end
        ETB.BlizzardData:VerifyDataCompleteness(categoryID, 20)
    elseif cmd == "count" then
        local categoryID = tonumber(args[2])
        if not categoryID then
            ETB:Print("|cffff0000[ETB]|r Usage: /etbverify count <categoryID>")
            return
        end
        ETB.BlizzardData:CompareItemCounts(categoryID)
    elseif cmd == "all" then
        ETB:Print("|cff00ffff[ETB Data Verify]|r Running comprehensive verification...")
        -- Test a few key categories
        local testCategories = {1, 4, 12, 13}  -- Head, Chest, MainHand, OffHand
        for _, catID in ipairs(testCategories) do
            ETB:Print("")
            ETB:Print("|cff00ffff=== Category " .. catID .. " ===")
            ETB.BlizzardData:VerifyDataPipeline(catID)
            ETB.BlizzardData:VerifyDataCompleteness(catID, 10)
            ETB.BlizzardData:CompareItemCounts(catID)
        end
        ETB:Print("")
        ETB:Print("|cff00ffff[ETB Data Verify]|r Verification complete!")
    elseif cmd == "pipeline" then
        local categoryID = tonumber(args[2])
        if not categoryID then
            ETB:Print("|cffff0000[ETB]|r Usage: /etbverify pipeline <categoryID>")
            return
        end
        ETB.BlizzardData:VerifyDataPipeline(categoryID)
    elseif cmd == "stats" then
        ETB.BlizzardData:PrintStats()
    elseif cmd == "categories" then
        -- Diagnostic: List all available categories from Blizzard API
        ETB:Print("|cff00ffff[ETB Data Verify]|r Checking Blizzard API categories...")
        if not C_TransmogCollection or not C_TransmogCollection.GetCategories then
            ETB:Print("|cffff0000[ETB]|r GetCategories not available!")
            return
        end
        
        local success, categories = pcall(C_TransmogCollection.GetCategories)
        if not success or not categories then
            ETB:Print("|cffff0000[ETB]|r Failed to get categories from API")
            return
        end
        
        ETB:Print(string.format("|cff00ffff[ETB]|r Found %d categories from Blizzard API:", #categories))
        ETB:Print("|cff00ffff[ETB]|r Format: CategoryID | Name | Appearances | Collected")
        
        for _, cat in ipairs(categories) do
            if cat and cat.categoryID then
                local appearances = ETB.BlizzardData:GetCategoryAppearances(cat.categoryID)
                local collected = 0
                for _, app in ipairs(appearances) do
                    if app.isCollected then collected = collected + 1 end
                end
                ETB:Print(string.format("  |cffffffff%d|r | %s | %d | %d", 
                    cat.categoryID, 
                    cat.name or "Unknown",
                    #appearances,
                    collected))
            end
        end
        
        -- Also check our expected categories
        ETB:Print("|cff00ffff[ETB]|r Our expected categories:")
        for catID, info in pairs(ETB.CATEGORY_INFO) do
            local appearances = ETB.BlizzardData:GetCategoryAppearances(catID)
            ETB:Print(string.format("  |cffffffff%d|r | %s | %d appearances", catID, info.name, #appearances))
        end
    else
        ETB:Print("|cffff0000[ETB]|r Unknown command. Type /etbverify help for usage.")
    end
end

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