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

ETB.BlizzardData = {
    cache = {
        categories = {},
        sources = {},
    },
    itemCache = nil,  -- LRU cache
    iconCache = nil,  -- LRU cache
    pendingItemInfo = {},
    
    stats = {
        categoryQueries = 0,
        sourceQueries = 0,
        cacheHits = 0,
        cacheMisses = 0,
    },
    
    initialized = false,
}

-- ============================================================================
-- ATT Runtime Name Fallback
-- When GetItemInfo() hasn't cached an item yet, check ATT's in-memory database
-- for an instant name rather than showing "Loading..."
-- ============================================================================

local function GetCachedItemName(itemID)
    if not itemID then return nil end
    if AllTheThings and AllTheThings.SearchForFieldContainer then
        local container = AllTheThings.SearchForFieldContainer("itemID")
        if container then
            local entries = container[itemID]
            if entries then
                if entries.name then return entries.name end
                if entries[1] and entries[1].name then return entries[1].name end
                if entries.text then return entries.text end
                if entries[1] and entries[1].text then return entries[1].text end
            end
        end
    end
    return nil
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
    self.itemCache = ETB:CreateLRUCache(500, "Items")
    self.iconCache = ETB:CreateLRUCache(1000, "Icons")
    
    -- Count appearances
    local total, collected = 0, 0
    for categoryID in pairs(ETB.CATEGORY_INFO) do
        local success, appearances = pcall(C_TransmogCollection.GetCategoryAppearances, categoryID)
        if success and appearances then
            total = total + #appearances
            for _, app in ipairs(appearances) do
                if app.isCollected then collected = collected + 1 end
            end
        end
    end
    
    ETB:Debug("BlizzardData", string.format("%s appearances (%s collected)",
        ETB:FormatNumber(total), ETB:FormatNumber(collected)))
    
    -- 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)
    if not categoryID then return {} end
    
    if self.cache.categories[categoryID] then
        self.stats.cacheHits = self.stats.cacheHits + 1
        return self.cache.categories[categoryID]
    end
    
    self.stats.cacheMisses = self.stats.cacheMisses + 1
    self.stats.categoryQueries = self.stats.categoryQueries + 1
    
    local success, appearances = pcall(C_TransmogCollection.GetCategoryAppearances, categoryID)
    if not success then
        ETB:Debug("BlizzardData", string.format("GetCategoryAppearances failed for category %d: %s", categoryID, tostring(appearances)))
        return {}
    end
    
    if not appearances then
        ETB:Debug("BlizzardData", string.format("GetCategoryAppearances returned nil for category %d", categoryID))
        return {}
    end
    
    -- Debug for weapons (categories 12+)
    if self:IsWeaponCategory(categoryID) and #appearances == 0 then
        ETB:Debug("BlizzardData", string.format("Warning: No appearances returned for weapon category %d", categoryID))
    end
    
    self.cache.categories[categoryID] = appearances
    return appearances
end

function BD:GetAppearanceSources(visualID)
    if not visualID then return {} end
    
    if self.cache.sources[visualID] then
        self.stats.cacheHits = self.stats.cacheHits + 1
        return self.cache.sources[visualID]
    end
    
    self.stats.cacheMisses = self.stats.cacheMisses + 1
    self.stats.sourceQueries = self.stats.sourceQueries + 1
    
    local success, sources = pcall(C_TransmogCollection.GetAppearanceSources, visualID)
    if not success or not sources then return {} end
    
    self.cache.sources[visualID] = sources
    return sources
end

function BD:GetItemIcon(itemID)
    if not itemID then return nil end
    
    local cached = self.iconCache:Get(itemID)
    if cached then return cached end
    
    local icon
    if GetItemInfoInstant then
        local _, _, _, _, tex = GetItemInfoInstant(itemID)
        icon = tex
    end
    
    if not icon then
        local _, _, _, _, _, _, _, _, _, tex = GetItemInfo(itemID)
        icon = tex
    end
    
    if icon then
        self.iconCache:Set(itemID, icon)
    end
    
    return icon
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 },
}

-- What armor type a class SHOULD transmog (highest proficiency)
local CLASS_PRIMARY_ARMOR = {
    WARRIOR     = "PLATE",
    PALADIN     = "PLATE",
    DEATHKNIGHT = "PLATE",
    HUNTER      = "MAIL",
    SHAMAN      = "MAIL",
    ROGUE       = "LEATHER",
    DRUID       = "LEATHER",
    MONK        = "LEATHER",
    MAGE        = "CLOTH",
    WARLOCK     = "CLOTH",
    PRIEST      = "CLOTH",
}

-- What weapon types each class can use (MoP Classic)
-- Note: Thrown weapons removed in MoP, ranged/relic slot gone, wands are main-hand
local CLASS_WEAPONS = {
    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 },
}

function BD:GetPlayerArmorTypes()
    -- If ProfileManager is active and viewing different character, use that class
    if ETB.ProfileManager then
        local activeClass = ETB.ProfileManager:GetActiveClassID()
        if activeClass then
            return CLASS_ARMOR[activeClass] or {}
        end
    end
    local _, playerClass = UnitClass("player")
    return CLASS_ARMOR[playerClass] or {}
end

function BD:GetPlayerPrimaryArmor()
    -- If ProfileManager is active and viewing different character, use that class
    if ETB.ProfileManager then
        local activeClass = ETB.ProfileManager:GetActiveClassID()
        if activeClass then
            return CLASS_PRIMARY_ARMOR[activeClass] or "CLOTH"
        end
    end
    local _, playerClass = UnitClass("player")
    return CLASS_PRIMARY_ARMOR[playerClass] or "CLOTH"
end

function BD:CanPlayerWearArmorType(armorType)
    if not armorType then return true end  -- Unknown armor type, allow it
    
    -- For transmog filtering, we want to match the PRIMARY armor type
    -- (what the class SHOULD transmog, not what they CAN equip)
    local primaryArmor = self:GetPlayerPrimaryArmor()
    return armorType == primaryArmor
end

-- Check if player can equip any armor type (for general use)
function BD:CanPlayerEquipArmorType(armorType)
    if not armorType then return true end
    local validTypes = self:GetPlayerArmorTypes()
    return validTypes[armorType] == true
end

function BD:GetPlayerWeaponTypes()
    -- If ProfileManager is active and viewing different character, use that class
    if ETB.ProfileManager then
        local activeClass = ETB.ProfileManager:GetActiveClassID()
        if activeClass then
            return CLASS_WEAPONS[activeClass] or {}
        end
    end
    local _, playerClass = UnitClass("player")
    return CLASS_WEAPONS[playerClass] or {}
end

function BD:CanPlayerUseWeaponType(weaponType)
    if not weaponType then return true end  -- Unknown weapon type, allow it
    local validTypes = self:GetPlayerWeaponTypes()
    return validTypes[weaponType] == true
end

-- Get the class name being used for filtering (for UI display)
function BD:GetActiveClassName()
    if ETB.ProfileManager then
        local profile = ETB.ProfileManager:GetActiveProfile()
        if profile then
            return profile.classID, profile.name
        end
    end
    local _, playerClass = UnitClass("player")
    local playerName = UnitName("player")
    return playerClass, playerName
end

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

-- Armor type detection from itemID
function BD:GetArmorType(itemID)
    if not itemID then return nil end
    
    -- Try GetItemInfoInstant first (faster, synchronous)
    if GetItemInfoInstant then
        local _, _, _, _, _, classID, subclassID = GetItemInfoInstant(itemID)
        -- classID 4 = Armor
        if classID == 4 then
            -- subclassID: 1=Cloth, 2=Leather, 3=Mail, 4=Plate, 6=Shield
            if subclassID == 1 then return "CLOTH"
            elseif subclassID == 2 then return "LEATHER"
            elseif subclassID == 3 then return "MAIL"
            elseif subclassID == 4 then return "PLATE"
            elseif subclassID == 6 then return "SHIELD"
            end
        end
    end
    
    -- Fallback to GetItemInfo (works in Classic, but may return nil if not cached)
    -- This is critical for MoP Classic where GetItemInfoInstant may not have data for uncached items
    local name, link, quality, itemLevel, minLevel, itemType, itemSubType = GetItemInfo(itemID)
    if itemType == "Armor" and itemSubType then
        -- Map itemSubType string to armor type
        if itemSubType == "Cloth" then return "CLOTH"
        elseif itemSubType == "Leather" then return "LEATHER"
        elseif itemSubType == "Mail" then return "MAIL"
        elseif itemSubType == "Plate" then return "PLATE"
        elseif itemSubType == "Shields" or itemSubType == "Shield" then return "SHIELD"
        end
    end
    
    return nil
end

-- Weapon type detection from itemID
-- Uses GetItemInfoInstant first (if available), then falls back to GetItemInfo (works in Classic)
function BD:GetWeaponType(itemID)
    if not itemID then return nil, nil end
    
    -- Try GetItemInfoInstant first (faster, synchronous) - may not be available in Classic
    -- GetItemInfoInstant returns: itemID, itemType, itemSubType, equipLoc, icon, classID, subclassID (7 values)
    if GetItemInfoInstant then
        local _, itemType, itemSubType, equipLoc, icon, classID, subclassID = GetItemInfoInstant(itemID)
        -- classID 2 = Weapon
        if classID == 2 and subclassID then
            -- subclassID: 0=One-Handed Axes, 1=Two-Handed Axes, 2=Bows, 3=Guns, 4=One-Handed Maces, 
            -- 5=Two-Handed Maces, 6=Polearms, 7=One-Handed Swords, 8=Two-Handed Swords, 
            -- 9=Warglaives, 10=Staves, 13=Fist Weapons, 15=Daggers, 16=Thrown, 18=Crossbows, 19=Wands
            if subclassID == 0 then return "AXE", false
            elseif subclassID == 1 then return "AXE", true
            elseif subclassID == 2 then return "BOW", true
            elseif subclassID == 3 then return "GUN", false
            elseif subclassID == 4 then return "MACE", false
            elseif subclassID == 5 then return "MACE", true
            elseif subclassID == 6 then return "POLEARM", true
            elseif subclassID == 7 then return "SWORD", false
            elseif subclassID == 8 then return "SWORD", true
            elseif subclassID == 9 then return "WARGLAIVE", false
            elseif subclassID == 10 then return "STAFF", true
            elseif subclassID == 13 then return "FIST", false
            elseif subclassID == 15 then return "DAGGER", false
            elseif subclassID == 16 then return "THROWN", false
            elseif subclassID == 18 then return "CROSSBOW", true
            elseif subclassID == 19 then return "WAND", false
            end
        end
    end
    
    -- Fallback to GetItemInfo (works in Classic, but may return nil if not cached)
    local name, link, quality, itemLevel, minLevel, itemType, itemSubType = GetItemInfo(itemID)
    if itemType == "Weapon" and itemSubType then
        -- Map itemSubType string to weapon type
        if itemSubType == "One-Handed Axes" then return "AXE", false
        elseif itemSubType == "Two-Handed Axes" then return "AXE", true
        elseif itemSubType == "Bows" then return "BOW", true
        elseif itemSubType == "Guns" then return "GUN", false
        elseif itemSubType == "One-Handed Maces" then return "MACE", false
        elseif itemSubType == "Two-Handed Maces" then return "MACE", true
        elseif itemSubType == "Polearms" then return "POLEARM", true
        elseif itemSubType == "One-Handed Swords" then return "SWORD", false
        elseif itemSubType == "Two-Handed Swords" then return "SWORD", true
        elseif itemSubType == "Staves" then return "STAFF", true
        elseif itemSubType == "Fist Weapons" then return "FIST", false
        elseif itemSubType == "Daggers" then return "DAGGER", false
        elseif itemSubType == "Thrown" then return "THROWN", false
        elseif itemSubType == "Crossbows" then return "CROSSBOW", true
        elseif itemSubType == "Wands" then return "WAND", false
        end
    end
    
    return nil, nil
end

function BD:BuildItemEntry(source, appearance, categoryID)
    local itemID = source.itemID
    
    local cached = self.itemCache:Get(itemID)
    if cached then
        cached.isCollected = source.isCollected or (appearance and appearance.isCollected)
        cached.isUsable = appearance and appearance.isUsable
        -- Update appearanceID if we have it
        if appearance and appearance.appearanceID and not cached.appearanceID then
            cached.appearanceID = appearance.appearanceID
        end
        -- For weapons, try to update weaponType if missing
        -- Request item info first to ensure GetItemInfo has data (needed in Classic)
        if self:IsWeaponCategory(categoryID) and not cached.weaponType then
            -- Request item info to cache it (GetItemInfoInstant may not work in Classic)
            GetItemInfo(itemID)
            local detectedType, isTwoHanded = self:GetWeaponType(itemID)
            if detectedType then
                cached.weaponType = detectedType
                if isTwoHanded ~= nil then
                    cached.isTwoHanded = isTwoHanded
                end
            end
        end
        -- Try to update name if still loading
        if cached.name == "Loading..." or not cached.name or cached.name == "Unknown" then
            self:UpdateItemInfo(itemID)
        end
        return cached
    end
    
    local armorType = self:GetArmorType(itemID)
    
    -- Get item info first (needed for weaponType detection in Classic)
    local name, link, quality, itemLevel, minLevel, itemType, itemSubType, stackCount, equipLoc, texture, sellPrice, classID, subclassID = GetItemInfo(itemID)
    
    -- Detect weaponType after GetItemInfo (GetItemInfoInstant may not work in Classic)
    local weaponType, isTwoHanded = self:GetWeaponType(itemID)
    
    -- If GetItemInfo didn't return name, try GetItemInfoInstant for texture/icon only
    -- NOTE: GetItemInfoInstant returns: itemID, itemType, itemSubType, equipLoc, icon, classID, subclassID
    -- It does NOT return name, link, or quality - only use it for icon!
    if (not name or name == "") and GetItemInfoInstant then
        local _, _, _, instEquipLoc, instTexture = GetItemInfoInstant(itemID)
        if instTexture then
            texture = instTexture
        end
        if instEquipLoc and (not equipLoc or equipLoc == "") then
            equipLoc = instEquipLoc
        end
    end
    
    -- If still no name, mark as pending
    if not name or name == "" then
        self.pendingItemInfo[itemID] = true
        GetItemInfo(itemID)  -- Request async to cache it
    end
    
    -- Ensure quality is a number (0-7), not a string
    if quality and type(quality) ~= "number" then
        quality = nil
    end
    if not quality and source.quality then
        if type(source.quality) == "number" then
            quality = source.quality
        end
    end
    quality = quality or 1  -- Default to Common (white)
    
    -- Cache icon if we got it from GetItemInfoInstant
    if texture and self.iconCache then
        self.iconCache:Set(itemID, texture)
    end

    -- Ensure name is a string, not a number (itemID)
    if name and type(name) ~= "string" then
        name = nil
    end
    if not name or name == "" then
        -- Try to get name from link if available
        if link and type(link) == "string" then
            local linkName = link:match("%[(.-)%]")
            if linkName then
                name = linkName
            end
        end
    end
    
    -- Ensure name is a string, not a number
    local itemName = name or source.name or GetCachedItemName(itemID) or "Loading..."
    if type(itemName) == "number" then
        itemName = "Loading..."
        -- Request item info if we got a number instead of a name
        self.pendingItemInfo[itemID] = true
        GetItemInfo(itemID)
    elseif itemName == "" then
        itemName = "Loading..."
        self.pendingItemInfo[itemID] = true
        GetItemInfo(itemID)
    end
    
    -- Capture all available fields from appearance and source objects
    local item = {
        itemID = itemID,
        sourceID = source.sourceID,
        visualID = appearance and appearance.visualID,
        appearanceID = appearance and appearance.appearanceID,  -- Store appearanceID if available
        name = itemName,
        link = link,  -- Store item link for preview/tooltip use
        quality = quality,
        isCollected = source.isCollected or (appearance and appearance.isCollected) or false,
        isUsable = appearance and appearance.isUsable or false,
        invType = equipLoc or source.invType,
        categoryID = source.categoryID or categoryID,
        icon = texture or self:GetItemIcon(itemID),
        armorType = armorType,
        weaponType = weaponType,
        isTwoHanded = isTwoHanded or false,
        canWear = armorType and self:CanPlayerWearArmorType(armorType) or (weaponType and self:CanPlayerUseWeaponType(weaponType)) or true,
        dropSource = nil,
        sourceType = nil,
        sourceZone = nil,
        -- Store additional source fields if available
        isHideVisual = source.isHideVisual,
        -- Store additional appearance fields if available
        appearanceModID = appearance and appearance.modID,
        -- Store class usability info for weapons (for future sorting/filtering by class)
        usableByClasses = nil,  -- Will be populated if needed for class-based sorting
    }
    
    -- For weapons, store which classes can use this weapon type (for future class-based sorting)
    -- This allows UI to sort/filter weapons by class later
    if weaponType and self:IsWeaponCategory(categoryID) then
        local usableClasses = {}
        -- CLASS_WEAPONS is defined above in this file
        for className, weaponTypes in pairs(CLASS_WEAPONS) do
            if weaponTypes and weaponTypes[weaponType] then
                usableClasses[className] = true
            end
        end
        if next(usableClasses) then  -- Only set if we found any classes
            item.usableByClasses = usableClasses
            -- Debug: Log which classes can use this weapon (only for first few items to avoid spam)
            if itemID and itemID % 100 == 0 then  -- Log every 100th weapon
                local classList = {}
                for className in pairs(usableClasses) do
                    table.insert(classList, className)
                end
                ETB:Debug("BlizzardData", string.format("Weapon %d (%s) usable by: %s", itemID, weaponType, table.concat(classList, ", ")))
            end
        end
    end
    
    -- Add drop source from our data
    if ETB.SourceData then
        item.dropSource = ETB.SourceData:GetSourceFormatted(itemID)
    end
    -- Enrich with structured source data (sourceType, expansion, location)
    if ETB.SourceData_ATT and ETB.SourceData_ATT.GetPrimarySource then
        local src = ETB.SourceData_ATT:GetPrimarySource(itemID)
        if src then
            item.sourceType = src.type    -- "BOSS", "VENDOR", "QUEST", "CRAFTED", "PVP", "WORLD_DROP"
            item.expansion = src.expansion -- "CLASSIC", "TBC", "WRATH", "CATA", "MOP"
            item.isRaid = src.isRaid       -- true for raid boss drops
            if not item.dropSource and src.name then
                item.dropSource = src.name .. (src.location and src.location ~= "" and (" - " .. src.location) or "")
            end
        end
    end
    
    self.itemCache:Set(itemID, item)
    return item
end

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


function BD:UpdateItemInfo(itemID)
    if not itemID then return false end
    
    -- Check cache first - if already has a valid name, skip
    local cached = self.itemCache and self.itemCache:Get(itemID)
    if cached and cached.name and cached.name ~= "Loading..." and cached.name ~= "Unknown" and type(cached.name) == "string" then
        return false  -- Already has valid name, no update needed
    end
    
    -- Only GetItemInfo returns name/link/quality - GetItemInfoInstant does NOT
    -- GetItemInfoInstant only returns: itemID, itemType, itemSubType, equipLoc, icon, classID, subclassID
    local name, link, quality, itemLevel, minLevel, itemType, itemSubType, stackCount, equipLoc, texture, sellPrice, classID, subclassID
    name, link, quality, itemLevel, minLevel, itemType, itemSubType, stackCount, equipLoc, texture, sellPrice, classID, subclassID = GetItemInfo(itemID)
    
    if not name then
        -- Try ATT as fallback for item name
        local attName = GetCachedItemName(itemID)
        if attName and cached then
            if cached.name == "Loading..." or not cached.name or cached.name == "Unknown" then
                cached.name = attName
            end
        end
        return false
    end

    if cached then
        if cached.name == "Loading..." or not cached.name or cached.name == "Unknown" or type(cached.name) == "number" then
            cached.name = name
            cached.quality = (type(quality) == "number" and quality) or cached.quality
            cached.invType = equipLoc or cached.invType
            cached.icon = texture or cached.icon or self:GetItemIcon(itemID)
            -- Always try to update weapon/armor type if missing (important for filtering)
            if not cached.armorType and not cached.weaponType then
                cached.armorType = self:GetArmorType(itemID)
                cached.weaponType, cached.isTwoHanded = self:GetWeaponType(itemID)
            elseif not cached.weaponType and self:IsWeaponCategory(cached.categoryID) then
                -- For weapons, request item info to cache it (needed for weaponType detection in Classic)
                GetItemInfo(itemID)
                -- Try to detect weaponType (GetItemInfo may have cached the data)
                cached.weaponType, cached.isTwoHanded = self:GetWeaponType(itemID)
                -- If still no weaponType, request async and it will be updated later
                if not cached.weaponType then
                    self.pendingItemInfo[itemID] = true
                end
            end
            return true
        end
    end
    return false
end

function BD:UpdatePendingItems()
    local updated = false
    for itemID, _ in pairs(self.pendingItemInfo) do
        if self:UpdateItemInfo(itemID) then
            self.pendingItemInfo[itemID] = nil
            updated = true
        end
    end
    return updated
end

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

-- Helper to check if category is a weapon category
function BD:IsWeaponCategory(categoryID)
    if not categoryID then return false end
    -- MoP Classic: Categories 12+ are weapon types (Wands, Axes, Swords, etc.)
    -- Check CATEGORY_INFO for isWeapon flag, or default to 12+ being weapons
    local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[categoryID]
    if catInfo and catInfo.isWeapon then
        return true
    end
    -- Fallback: categories 12+ are weapons if not in CATEGORY_INFO
    return categoryID >= 12
end

function BD:GetItemsForCategory(categoryID, options)
    if not categoryID then return {} end
    -- Route to specialized modules based on category type
    if self:IsWeaponCategory(categoryID) then
        -- Weapons: Use WeaponData module (categories 12+)
        if ETB.WeaponData and ETB.WeaponData.GetItemsForCategory then
            return ETB.WeaponData:GetItemsForCategory(categoryID, options)
        else
            ETB:PrintError("WeaponData module not loaded!")
            return {}
        end
    elseif categoryID >= 1 and categoryID <= 11 then
        -- Armor: Use ArmorData module (categories 1-11)
        if ETB.ArmorData and ETB.ArmorData.GetItemsForCategory then
            return ETB.ArmorData:GetItemsForCategory(categoryID, options)
        else
            ETB:PrintError("ArmorData module not loaded!")
            return {}
        end
    else
        ETB:PrintError(string.format("Unknown category ID: %d", categoryID))
        return {}
    end
end

-- Legacy implementation (kept for reference, but routes to modules above)
function BD:GetItemsForCategory_Legacy(categoryID, options)
    options = options or {}
    local limit = options.limit or 100000  -- Increased default limit to ensure we get all data
    local onlyCollected = options.onlyCollected
    local onlyUncollected = options.onlyUncollected
    local onlyUsable = options.onlyUsable  -- Filter by class usability (item-level)
    
    local items = {}
    local itemsByVisualID = {}  -- visualID -> { items = {...}, selectedIndex = 1 }
    local appearances = self:GetCategoryAppearances(categoryID)
    
    -- Debug: Log appearance count
    ETB:Debug("BlizzardData", string.format("Category %d: Found %d appearances from API", categoryID, #appearances))
    
    -- Debug: Check if we got appearances (especially for weapons)
    if #appearances == 0 then
        if categoryID == 12 or categoryID == 13 then
            ETB:Print("|cffff0000[BlizzardData]|r ERROR: No appearances found for weapon category %d! Check API availability.", categoryID)
        else
            ETB:Debug("BlizzardData", string.format("Warning: No appearances found for category %d", categoryID))
        end
    end
    
    local playerPrimaryArmor = self:GetPlayerPrimaryArmor()
    local totalSourcesProcessed = 0
    local totalSourcesSkipped = 0
    
    for _, appearance in ipairs(appearances) do
        if #items >= limit then break end
        
        -- Pre-filter by collection at appearance level (only if explicitly requested)
        -- By default, include BOTH collected and uncollected to match Appearance tab
        local skipAppearance = false
        if onlyCollected and not appearance.isCollected then
            skipAppearance = true
        elseif onlyUncollected and appearance.isCollected then
            skipAppearance = true
        end
        
        if not skipAppearance then
            -- IMPORTANT: Also check appearance.isUsable for class-specific items (tier sets)
            -- This catches class-locked items that armor type alone would miss
            -- Only filter by isUsable if explicitly requested (onlyUsable = true)
            local skipByUsability = false
            if onlyUsable and appearance.isUsable == false then
                -- Skip appearances that the API says we can't use
                -- (This catches tier sets, class-specific items, etc.)
                skipByUsability = true
            end
            
            if not skipByUsability then
                local sources = self:GetAppearanceSources(appearance.visualID)
                totalSourcesProcessed = totalSourcesProcessed + #sources
                
                -- Debug: Log if we have sources
                if #sources == 0 then
                    ETB:Debug("BlizzardData", string.format("Warning: Appearance visualID %d has no sources", appearance.visualID))
                end
                
                for _, source in ipairs(sources) do
                    if #items >= limit then break end
                    
                    -- Verify source has required fields
                    if not source.itemID then
                        ETB:Debug("BlizzardData", string.format("Warning: Source missing itemID for visualID %d", appearance.visualID))
                        totalSourcesSkipped = totalSourcesSkipped + 1
                    else
                        -- Build item entry first
                        local item = self:BuildItemEntry(source, appearance, categoryID)
                        
                        -- Light validation: Only filter out obvious mismatches for armor
                        -- For weapons (12, 13), completely trust the API - no validation
                        -- IMPORTANT: We trust the Blizzard API's categoryID assignment
                        local shouldSkip = false
                        
                        -- For weapons, always trust the API - no validation
                        if categoryID == 12 or categoryID == 13 then
                            -- Trust the API completely for weapons
                            shouldSkip = false
                        else
                            -- For armor slots, only filter obvious mismatches
                            -- IMPORTANT: Only validate if source.categoryID matches AND we have invType
                            -- If source.categoryID doesn't match, trust the API (might be cross-category appearance)
                            local catInfo = ETB.CATEGORY_INFO[categoryID]
                            if catInfo and item.invType and source.categoryID and source.categoryID == categoryID then
                                local expectedSlot = catInfo.slot
                                -- Only skip if it's a completely different armor type (e.g., cloak in non-cloak slot)
                                if expectedSlot == "INVTYPE_CLOAK" and item.invType ~= "INVTYPE_CLOAK" then
                                    shouldSkip = true  -- Cloak category should only have cloaks
                                elseif expectedSlot ~= "INVTYPE_CLOAK" and item.invType == "INVTYPE_CLOAK" then
                                    shouldSkip = true  -- Non-cloak category shouldn't have cloaks
                                elseif expectedSlot == "INVTYPE_SHOULDER" and item.invType == "INVTYPE_CLOAK" then
                                    shouldSkip = true  -- Shoulder slot shouldn't have cloaks
                                elseif expectedSlot == "INVTYPE_CLOAK" and item.invType == "INVTYPE_SHOULDER" then
                                    shouldSkip = true  -- Cloak slot shouldn't have shoulders
                                end
                                -- For other armor types, be lenient - trust the API
                            end
                            -- If source.categoryID doesn't match or is missing, trust the API
                        end
                        
                        -- Process item (trust API categoryID if validation passed)
                        if not shouldSkip then
                            -- Additional filter by class usability (armor or weapons)
                            local shouldInclude = true
                            if onlyUsable then
                                -- For armor categories (1-11), filter by armor type
                                if categoryID >= 1 and categoryID <= 11 then
                                    -- Only include items of the player's PRIMARY armor type
                                    -- (Plate for Warriors, Cloth for Mages, etc.)
                                    -- Note: nil armor type is allowed (cosmetic items)
                                    if item.armorType and item.armorType ~= playerPrimaryArmor then
                                        shouldInclude = false
                                    end
                                -- For weapon categories (12-13), filter by weapon type
                                elseif categoryID == 12 or categoryID == 13 then
                                    -- Only include weapons the player can use
                                    -- Note: If weaponType is nil (unknown), allow it (might be a special weapon)
                                    -- IMPORTANT: Be very lenient with weapons - only filter if we're CERTAIN it's unusable
                                    if item.weaponType and not self:CanPlayerUseWeaponType(item.weaponType) then
                                        -- Only filter if weaponType is explicitly set and player can't use it
                                        shouldInclude = false
                                    end
                                    -- Don't filter out weapons without a weaponType - they might be usable
                                    -- Don't filter out weapons where canWear might be false due to detection issues
                                end
                                
                                -- Also check the item's canWear flag (set during BuildItemEntry)
                                -- But for weapons, be more lenient - only filter if explicitly false
                                if shouldInclude then
                                    if categoryID == 12 or categoryID == 13 then
                                        -- For weapons, only filter if canWear is explicitly false AND we have weaponType
                                        if item.canWear == false and item.weaponType then
                                            shouldInclude = false
                                        end
                                    else
                                        -- For armor, use canWear flag as normal
                                        if item.canWear == false then
                                            shouldInclude = false
                                        end
                                    end
                                end
                            end
                            
                            if shouldInclude then
                                local visualID = item.visualID or appearance.visualID
                                if not itemsByVisualID[visualID] then
                                    itemsByVisualID[visualID] = { items = {}, selectedIndex = 1 }
                                end
                                table.insert(itemsByVisualID[visualID].items, item)
                            else
                                totalSourcesSkipped = totalSourcesSkipped + 1
                            end
                        else
                            totalSourcesSkipped = totalSourcesSkipped + 1
                        end
                    end
                end
            end
        end
    end
    
    -- Debug: Log data completeness
    local uniqueVisualCount = 0
    for _ in pairs(itemsByVisualID) do
        uniqueVisualCount = uniqueVisualCount + 1
    end
    ETB:Debug("BlizzardData", string.format("Category %d: Processed %d sources, skipped %d, unique visuals: %d", 
        categoryID, totalSourcesProcessed, totalSourcesSkipped, uniqueVisualCount))
    
    -- Now create unique items list (one per visualID)
    -- IMPORTANT: This ensures we show one item per visual appearance (like Blizzard's Appearance tab)
    -- But we store all alternatives for each visualID
    local uniqueVisualCount = 0
    for visualID, data in pairs(itemsByVisualID) do
        if #items >= limit then break end
        uniqueVisualCount = uniqueVisualCount + 1
        
        -- Select the best item to show (prefer collected, then highest quality, then first)
        local selectedItem = nil
        local bestIndex = 1
        
        -- Prefer collected items
        for i, item in ipairs(data.items) do
            if item.isCollected then
                selectedItem = item
                bestIndex = i
                break
            end
        end
        
        -- If no collected, prefer highest quality
        if not selectedItem then
            local bestQuality = -1
            for i, item in ipairs(data.items) do
                local q = tonumber(item.quality) or 0
                if q > bestQuality then
                    bestQuality = q
                    selectedItem = item
                    bestIndex = i
                end
            end
        end
        
        -- Fallback to first item
        if not selectedItem and #data.items > 0 then
            selectedItem = data.items[1]
            bestIndex = 1
        end
        
        if selectedItem then
            -- Store alternatives and current selection
            selectedItem.alternatives = data.items
            selectedItem.selectedAlternativeIndex = bestIndex
            selectedItem.hasAlternatives = #data.items > 1
            
            -- Queue all alternatives for name loading too
            for _, altItem in ipairs(data.items) do
                if altItem.itemID and (not altItem.name or altItem.name == "Loading...") then
                    self.pendingItemInfo[altItem.itemID] = true
                end
            end
            
            items[#items + 1] = selectedItem
        end
    end
    
    return items
end

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

-- Diagnostic function to verify data completeness against Blizzard's Appearance tab
function BD:VerifyDataCompleteness(categoryID, sampleSize)
    sampleSize = sampleSize or 10  -- Check first 10 appearances by default
    
    ETB:Print("|cff00ffff[BlizzardData]|r Verifying data completeness for category " .. tostring(categoryID) .. "...")
    
    local appearances = self:GetCategoryAppearances(categoryID)
    if #appearances == 0 then
        ETB:Print("|cffff0000[BlizzardData]|r No appearances found for category " .. tostring(categoryID))
        return false
    end
    
    local checked = 0
    local missingFields = {}
    local fieldCounts = {}
    
    for i, appearance in ipairs(appearances) do
        if checked >= sampleSize then break end
        
        -- Validate appearance is a table
        if type(appearance) == "table" then
            -- Check appearance object fields
            local appearanceFields = {}
            for k, v in pairs(appearance) do
                appearanceFields[k] = true
                fieldCounts["appearance." .. k] = (fieldCounts["appearance." .. k] or 0) + 1
            end
            
            -- Check if we're capturing all appearance fields
            if appearance.visualID and not appearanceFields.visualID then
                table.insert(missingFields, "appearance.visualID")
            end
            if appearance.appearanceID and not appearanceFields.appearanceID then
                table.insert(missingFields, "appearance.appearanceID")
            end
            if appearance.isCollected ~= nil and not appearanceFields.isCollected then
                table.insert(missingFields, "appearance.isCollected")
            end
            if appearance.isUsable ~= nil and not appearanceFields.isUsable then
                table.insert(missingFields, "appearance.isUsable")
            end
            
            -- Check sources
            local sources = self:GetAppearanceSources(appearance.visualID)
            if #sources == 0 then
                ETB:Print(string.format("|cffff0000[BlizzardData]|r Warning: Appearance visualID %d has no sources", appearance.visualID))
            else
                for _, source in ipairs(sources) do
                    -- Validate source is a table
                    if type(source) == "table" then
                        -- Check source object fields
                        local sourceFields = {}
                        for k, v in pairs(source) do
                            sourceFields[k] = true
                            fieldCounts["source." .. k] = (fieldCounts["source." .. k] or 0) + 1
                        end
                        
                        -- Check if we're capturing all source fields
                        if source.itemID and not sourceFields.itemID then
                            table.insert(missingFields, "source.itemID")
                        end
                        if source.sourceID and not sourceFields.sourceID then
                            table.insert(missingFields, "source.sourceID")
                        end
                        if source.categoryID and not sourceFields.categoryID then
                            table.insert(missingFields, "source.categoryID")
                        end
                        if source.invType and not sourceFields.invType then
                            table.insert(missingFields, "source.invType")
                        end
                        if source.isCollected ~= nil and not sourceFields.isCollected then
                            table.insert(missingFields, "source.isCollected")
                        end
                        if source.isHideVisual ~= nil and not sourceFields.isHideVisual then
                            table.insert(missingFields, "source.isHideVisual")
                        end
                        
                        -- Try GetAppearanceSourceInfo if available
                        if C_TransmogCollection and C_TransmogCollection.GetAppearanceSourceInfo and source.sourceID then
                            local success, sourceInfo = pcall(C_TransmogCollection.GetAppearanceSourceInfo, source.sourceID)
                            if success and sourceInfo and type(sourceInfo) == "table" then
                                for k, v in pairs(sourceInfo) do
                                    fieldCounts["sourceInfo." .. k] = (fieldCounts["sourceInfo." .. k] or 0) + 1
                                end
                            end
                        end
                        
                        -- Build item entry and check what we store
                        local item = self:BuildItemEntry(source, appearance, categoryID)
                        if type(item) == "table" then
                            local itemFields = {}
                            for k, v in pairs(item) do
                                itemFields[k] = true
                                fieldCounts["item." .. k] = (fieldCounts["item." .. k] or 0) + 1
                            end
                            
                            -- Verify critical fields are present
                            if not item.itemID then
                                table.insert(missingFields, "item.itemID")
                            end
                            if not item.visualID and appearance.visualID then
                                table.insert(missingFields, "item.visualID")
                            end
                            if not item.name or item.name == "Loading..." then
                                ETB:Print(string.format("|cffffff00[BlizzardData]|r Item %d name not loaded", item.itemID))
                            end
                        end
                    else
                        ETB:Print(string.format("|cffff0000[BlizzardData]|r Warning: Source is not a table (type: %s)", type(source)))
                    end
                end
            end
        else
            ETB:Print(string.format("|cffff0000[BlizzardData]|r Warning: Appearance at index %d is not a table (type: %s)", i, type(appearance)))
        end
        
        checked = checked + 1
    end
    
    -- Print summary
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Checked %d appearances", checked))
    ETB:Print("|cff00ffff[BlizzardData]|r Field frequency:")
    for field, count in pairs(fieldCounts) do
        if count == checked then
            ETB:Print(string.format("  |cff00ff00✓|r %s: %d/%d", field, count, checked))
        else
            ETB:Print(string.format("  |cffffff00⚠|r %s: %d/%d", field, count, checked))
        end
    end
    
    if #missingFields > 0 then
        ETB:Print("|cffff0000[BlizzardData]|r Missing fields detected:")
        for _, field in ipairs(missingFields) do
            ETB:Print("  - " .. field)
        end
        return false
    else
        ETB:Print("|cff00ff00[BlizzardData]|r All critical fields present!")
        return true
    end
end

-- Verify data pipeline completeness for a category
function BD:VerifyDataPipeline(categoryID)
    ETB:Print("|cff00ffff[BlizzardData]|r Verifying data pipeline for category " .. tostring(categoryID) .. "...")
    
    -- Step 1: Get raw appearances from API
    local appearances = self:GetCategoryAppearances(categoryID)
    local totalAppearances = #appearances
    local collectedAppearances = 0
    local uncollectedAppearances = 0
    
    for _, app in ipairs(appearances) do
        if app.isCollected then
            collectedAppearances = collectedAppearances + 1
        else
            uncollectedAppearances = uncollectedAppearances + 1
        end
    end
    
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Step 1 - API Appearances: %d total (%d collected, %d uncollected)", 
        totalAppearances, collectedAppearances, uncollectedAppearances))
    
    -- Step 2: Get all sources (no filtering)
    local totalSources = 0
    local sourcesByCollection = { collected = 0, uncollected = 0 }
    
    for _, app in ipairs(appearances) do
        local sources = self:GetAppearanceSources(app.visualID)
        totalSources = totalSources + #sources
        for _, source in ipairs(sources) do
            if source.isCollected then
                sourcesByCollection.collected = sourcesByCollection.collected + 1
            else
                sourcesByCollection.uncollected = sourcesByCollection.uncollected + 1
            end
        end
    end
    
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Step 2 - Total Sources: %d (%d collected, %d uncollected)", 
        totalSources, sourcesByCollection.collected, sourcesByCollection.uncollected))
    
    -- Step 3: Get items with NO filtering (all data)
    local allItems = self:GetItemsForCategory(categoryID, { limit = 100000 })
    local allItemsCollected = 0
    local allItemsUncollected = 0
    
    for _, item in ipairs(allItems) do
        if item.isCollected then
            allItemsCollected = allItemsCollected + 1
        else
            allItemsUncollected = allItemsUncollected + 1
        end
    end
    
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Step 3 - Items (no filters): %d total (%d collected, %d uncollected)", 
        #allItems, allItemsCollected, allItemsUncollected))
    
    -- Step 4: Verify categorization
    local miscategorized = 0
    local catInfo = ETB.CATEGORY_INFO[categoryID]
    if catInfo then
        for _, item in ipairs(allItems) do
            -- Check if item's categoryID matches
            if item.categoryID and item.categoryID ~= categoryID then
                miscategorized = miscategorized + 1
                ETB:Print(string.format("|cffffff00[BlizzardData]|r Warning: Item %d has categoryID %d but was requested for category %d", 
                    item.itemID, item.categoryID, categoryID))
            end
        end
    end
    
    if miscategorized > 0 then
        ETB:Print(string.format("|cffff0000[BlizzardData]|r Warning: %d items may be miscategorized", miscategorized))
    else
        ETB:Print("|cff00ff00[BlizzardData]|r All items properly categorized!")
    end
    
    -- Step 5: Compare with Blizzard's count
    local coverage = totalAppearances > 0 and (#allItems / totalAppearances * 100) or 0
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Coverage: %.1f%% (%d items / %d appearances)", 
        coverage, #allItems, totalAppearances))
    
    if coverage < 95 then
        ETB:Print("|cffff0000[BlizzardData]|r Warning: Coverage below 95% - may be missing data!")
        return false
    else
        ETB:Print("|cff00ff00[BlizzardData]|r Data pipeline verification complete!")
        return true
    end
end

-- Compare our item count with Blizzard's for a category
function BD:CompareItemCounts(categoryID)
    ETB:Print("|cff00ffff[BlizzardData]|r Comparing item counts for category " .. tostring(categoryID) .. "...")
    
    local appearances = self:GetCategoryAppearances(categoryID)
    local blizzardCount = #appearances
    
    -- Count unique visualIDs
    local uniqueVisuals = {}
    for _, app in ipairs(appearances) do
        if app.visualID then
            uniqueVisuals[app.visualID] = true
        end
    end
    local uniqueVisualCount = 0
    for _ in pairs(uniqueVisuals) do
        uniqueVisualCount = uniqueVisualCount + 1
    end
    
    -- Count total sources
    local totalSources = 0
    for _, app in ipairs(appearances) do
        local sources = self:GetAppearanceSources(app.visualID)
        totalSources = totalSources + #sources
    end
    
    -- Get our filtered count
    local ourItems = self:GetItemsForCategory(categoryID, { limit = 100000 })
    local ourCount = #ourItems
    
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Blizzard API:"))
    ETB:Print(string.format("  Appearances: %d", blizzardCount))
    ETB:Print(string.format("  Unique Visuals: %d", uniqueVisualCount))
    ETB:Print(string.format("  Total Sources: %d", totalSources))
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Our Addon:"))
    ETB:Print(string.format("  Items: %d", ourCount))
    
    local percentage = totalSources > 0 and (ourCount / totalSources * 100) or 0
    ETB:Print(string.format("|cff00ffff[BlizzardData]|r Coverage: %.1f%% (%d/%d)", percentage, ourCount, totalSources))
    
    if ourCount < totalSources * 0.95 then
        ETB:Print("|cffff0000[BlizzardData]|r Warning: We're showing less than 95% of available sources!")
        return false
    else
        ETB:Print("|cff00ff00[BlizzardData]|r Coverage looks good!")
        return true
    end
end

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

function BD:GetCollectionStats(categoryID)
    local appearances = self:GetCategoryAppearances(categoryID)
    local total, collected, usable = #appearances, 0, 0
    
    for _, app in ipairs(appearances) do
        if app.isCollected then collected = collected + 1 end
        if app.isUsable then usable = usable + 1 end
    end
    
    return {
        total = total,
        collected = collected,
        usable = usable,
        percent = total > 0 and (collected / total * 100) or 0,
    }
end

function BD:GetAllStats()
    local total, collected = 0, 0
    local byCategory = {}
    
    for categoryID in pairs(ETB.CATEGORY_INFO) do
        local stats = self:GetCollectionStats(categoryID)
        byCategory[categoryID] = stats
        total = total + stats.total
        collected = collected + stats.collected
    end
    
    return {
        total = total,
        collected = collected,
        percent = total > 0 and (collected / total * 100) or 0,
        byCategory = byCategory,
    }
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

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()
    ETB:Print("=== BLIZZARD DATA STATS ===")
    ETB:Print(string.format("Category queries: %d", self.stats.categoryQueries))
    ETB:Print(string.format("Source queries: %d", self.stats.sourceQueries))
    ETB:Print(string.format("Cache hits/misses: %d/%d", self.stats.cacheHits, self.stats.cacheMisses))
    
    if self.itemCache then
        local s = self.itemCache:GetStats()
        ETB:Print(string.format("Item cache: %d/%d (%.1f%% hit rate)", s.size, s.maxSize, s.hitRate))
    end
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")
