--[[
============================================================================
Core/BlizzardData/Items.lua  (added in v15.2.0 — Audit Batch 4 Part 2)
============================================================================

Heavy item construction + category fetching for the BlizzardData orchestrator.
This file holds the bulk of BlizzardData's logic.

Methods owned here (6):
  BuildItemEntry              — assemble the full item entry structure used
                                throughout the UI (~190 lines, the busiest
                                method in this file)
  UpdateItemInfo              — async update step for items with "Loading..."
                                placeholders; called from GET_ITEM_INFO_RECEIVED
  UpdatePendingItems          — iterate _pendingNames and run UpdateItemInfo
                                for each (bulk refresh after batch load)
  IsWeaponCategory            — true if a categoryID is in the weapon range
  GetItemsForCategory         — primary entry point; dispatches to compact
                                data when available, falls back to Legacy
  GetItemsForCategory_Legacy  — the original C_TransmogCollection-based path

External dependencies pulled from owner (BD):
  owner.CLASS_PRIMARY_ARMOR  — used by BuildItemEntry for class-relevance flag
  owner.GetCachedItemName    — file-local utility for name resolution
  owner._pendingNames        — items waiting for GetItemInfo to cache
  owner._sourceCache         — itemID -> source cache
  owner._categoryCache       — categoryID -> items cache
  owner:GetArmorType, owner:GetWeaponType  — internal delegators
  owner:GetCategoryAppearances, owner:GetAppearanceSources  — internal delegators

Closure / shadowing notes: All synchronous methods, no closures.
============================================================================
]]--
local ADDON_NAME, NS = ...
local ETB = NS.ETB
NS.ETB.BlizzardData = NS.ETB.BlizzardData or {}
NS.ETB.BlizzardData.Items = NS.ETB.BlizzardData.Items or {}
local Items = NS.ETB.BlizzardData.Items

-- =============================================================================
-- METHODS (extracted from Core/BlizzardData.lua in v15.2.0)
-- =============================================================================

function Items:BuildItemEntry(owner, source, appearance, categoryID)
    local itemID = source.itemID
    
    local cached = owner.itemCache:Get(itemID)
    if cached then
        cached.isCollected = source.isCollected or (appearance and appearance.isCollected)
        cached.isUsable = appearance and appearance.isUsable
        -- Always update visualID from appearance (critical for color filtering)
        if appearance and appearance.visualID then
            cached.visualID = appearance.visualID
        end
        -- 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 owner:IsWeaponCategory(categoryID) and not cached.weaponType then
            -- Request item info to cache it (GetItemInfoInstant may not work in Classic)
            GetItemInfo(itemID)
            local detectedType, isTwoHanded = owner: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
            owner:UpdateItemInfo(itemID)
        end
        return cached
    end
    
    local armorType = owner: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 = owner: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
        owner.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 owner.iconCache then
        owner.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 owner.GetCachedItemName(itemID) or "Loading..."
    if type(itemName) == "number" then
        itemName = "Loading..."
        -- Request item info if we got a number instead of a name
        owner.pendingItemInfo[itemID] = true
        GetItemInfo(itemID)
    elseif itemName == "" then
        itemName = "Loading..."
        owner.pendingItemInfo[itemID] = true
        GetItemInfo(itemID)
    end
    
    -- Capture all available fields from appearance and source objects
    -- Lean item object — only set fields that have values
    -- Each removed nil field saves ~40 bytes in Lua 5.1 hash tables
    local item = {
        itemID = itemID,
        sourceID = source.sourceID,
        visualID = appearance and appearance.visualID,
        name = itemName,
        quality = quality,
        isCollected = source.isCollected or (appearance and appearance.isCollected) or false,
        invType = equipLoc or source.invType,
        categoryID = source.categoryID or categoryID,
        icon = texture or owner:GetItemIcon(itemID),
    }
    
    -- Only set optional fields when they have real values
    if appearance and appearance.appearanceID then item.appearanceID = appearance.appearanceID end
    if link then item.link = link end
    if armorType then item.armorType = armorType end
    if weaponType then item.weaponType = weaponType end
    if isTwoHanded then item.isTwoHanded = true end
    if appearance and appearance.isUsable == false then item.isUsable = false end
    if source.isHideVisual then item.isHideVisual = true end
    if appearance and appearance.modID then item.appearanceModID = appearance.modID end
    
    -- 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 owner:IsWeaponCategory(categoryID) then
        local usableClasses = {}
        -- owner.CLASS_WEAPONS is defined above in this file
        for className, weaponTypes in pairs(owner.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 — priority: BossLoot DB2 > compact data > SourceData
    -- BossLoot (from JournalEncounter DB2 — most authoritative for boss drops)
    if ETB.SourceLookup then
        local srcText = ETB.SourceLookup:GetSourceText(itemID)
        if srcText then
            item.dropSource = srcText
            -- Also set structured fields from BossLoot
            local sources = ETB.SourceLookup:GetItemSources(itemID)
            if sources and #sources > 0 then
                local s = sources[1]
                item.sourceType = "BOSS"
                item.expansion = s.expansion
                item.isRaid = s.isRaid
            end
        end
    end
    -- Compact data fallback (for items not in JournalEncounter: vendors, quests, world drops)
    if not item.dropSource then
        if ETB.SourceData then
            item.dropSource = ETB.SourceData:GetSourceFormatted(itemID)
        end
        if ETB.SourceData_ATT and ETB.SourceData_ATT.GetPrimarySource then
            local src = ETB.SourceData_ATT:GetPrimarySource(itemID)
            if src then
                if not item.sourceType then item.sourceType = src.type end
                if not item.expansion then item.expansion = src.expansion end
                if not item.isRaid then item.isRaid = src.isRaid end
                if not item.dropSource and src.name then
                    item.dropSource = src.name .. (src.location and src.location ~= "" and (" - " .. src.location) or "")
                end
            end
        end
    end
    
    owner.itemCache:Set(itemID, item)
    return item
end


function Items:UpdateItemInfo(owner, itemID)
    if not itemID then return false end
    
    -- Check cache first - if already has a valid name, skip
    local cached = owner.itemCache and owner.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 external addon as fallback for item name
        local attName = owner.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 owner: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 = owner:GetArmorType(itemID)
                cached.weaponType, cached.isTwoHanded = owner:GetWeaponType(itemID)
            elseif not cached.weaponType and owner: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 = owner:GetWeaponType(itemID)
                -- If still no weaponType, request async and it will be updated later
                if not cached.weaponType then
                    owner.pendingItemInfo[itemID] = true
                end
            end
            return true
        end
    end
    return false
end

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


function Items:IsWeaponCategory(owner, 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 Items:GetItemsForCategory(owner, categoryID, options)
    if not categoryID then return {} end
    -- Route to specialized modules based on category type
    if owner: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 Items:GetItemsForCategory_Legacy(owner, 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 = owner: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 = owner: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 = owner: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 = owner: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 owner: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 (cap at 5 to save memory — each item table is ~1.2KB)
            local MAX_ALTERNATIVES = 5
            if #data.items > MAX_ALTERNATIVES then
                local capped = {}
                -- Always include the selected item
                capped[1] = selectedItem
                local added = 1
                for _, altItem in ipairs(data.items) do
                    if altItem ~= selectedItem and added < MAX_ALTERNATIVES then
                        added = added + 1
                        capped[added] = altItem
                    end
                end
                selectedItem.alternatives = capped
            else
                selectedItem.alternatives = data.items
            end
            selectedItem.selectedAlternativeIndex = 1
            selectedItem.hasAlternatives = #selectedItem.alternatives > 1
            
            -- Queue alternatives for name loading (only the capped set)
            for _, altItem in ipairs(selectedItem.alternatives) do
                if altItem.itemID and (not altItem.name or altItem.name == "Loading...") then
                    owner.pendingItemInfo[altItem.itemID] = true
                end
            end
            
            items[#items + 1] = selectedItem
        end
    end
    
    return items
end

