--[[
============================================================================
Modules/BossLootTooltip/Render.lua  (added in v15.1.0 — Audit Batch 4 Part 1)
============================================================================

Tooltip-population logic and collection-state queries for the BossLootTooltip
orchestrator. Owner-pattern: each method takes `owner` as first argument.

Methods owned here (5):
  ClearCollectionCache   — wipe owner.collectionCache (called on transmog
                           collection-change events)
  IsAppearanceCollected  — check if any source of a transmog appearance is
                           collected on this character (cached)
  IsItemCollected        — check if a specific item's appearance is collected
                           (handles the visualID -> sourceID resolution)
  CanPlayerUseItem       — class-proficiency check; returns true for unknowns
                           (don't filter out un-cached items)
  AddLootToTooltip       — the main rendering routine (~400 lines). Builds
                           collected/uncollected lists, sorts, paginates,
                           applies difficulty grouping, writes to the tooltip.

External dependencies pulled from owner:
  owner.npcIndex         — built by Index:BuildIndex
  owner.collectionCache  — appearance/item collection-state cache
  owner.VALID_ITEM_CLASSES / EXCLUDED_EQUIP_LOCS — filter tables
  owner.WORLD_BOSS_NPCS  — world boss display names
  owner.DIFFICULTY_INFO  — difficulty name + bitmask lookup
  owner.MAX_ITEMS_SHOWN, owner.HEADER_COLOR, owner.COLLECTED_COLOR,
  owner.UNCOLLECTED_COLOR, owner.SEPARATOR_COLOR, owner.TOKEN_COLOR,
  owner.DIFFICULTY_COLOR — display constants
  owner.FLAG_* — used to interpret index flag values

Closure / shadowing notes: No closures, no async. Pure synchronous rendering.
============================================================================
]]--
local ADDON_NAME, NS = ...
local ETB = NS.ETB
NS.ETB.BossLootTooltip = NS.ETB.BossLootTooltip or {}
NS.ETB.BossLootTooltip.Render = NS.ETB.BossLootTooltip.Render or {}
local Render = NS.ETB.BossLootTooltip.Render

-- =============================================================================
-- METHODS (extracted from Modules/BossLootTooltip.lua in v15.1.0)
-- =============================================================================

function Render:ClearCollectionCache(owner)
    -- v15.5.0: cache now lives in ETB.CollectionAPI, but we keep a stub on
    -- owner._collectionCache for any external code that still pokes at it.
    -- Forward the clear to CollectionAPI so the next render gets fresh state.
    owner._collectionCache = {}
    if ETB.CollectionAPI then ETB.CollectionAPI:ClearCache() end
end

-- Returns: true (collected/known-from-other), false (not collected),
--          nil (can't determine yet — item not loaded).
-- Treats COLLECTED and KNOWN_OTHER as the same thing for BLT's display
-- purposes: a checkmark means "you have this appearance somewhere".
function Render:IsAppearanceCollected(owner, appearanceID)
    -- v15.5.0: CollectionAPI is keyed by itemID, not appearanceID, so this
    -- function keeps its own minimal implementation. It's only used as a
    -- fallback path from IsItemCollected — see that method for the primary
    -- delegation. Kept the API surface unchanged so any future caller of
    -- Render:IsAppearanceCollected still works.
    if not appearanceID or not C_TransmogCollection then return nil end

    if C_TransmogCollection.GetAllAppearanceSources then
        local ok, allSources = pcall(C_TransmogCollection.GetAllAppearanceSources, appearanceID)
        if ok and allSources and #allSources > 0 and C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance then
            for _, sid in ipairs(allSources) do
                local hasOK, hasIt = pcall(C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance, sid)
                if hasOK and hasIt then return true end
            end
            return false
        end
    end

    if C_TransmogCollection.GetAppearanceSources then
        local ok, sources = pcall(C_TransmogCollection.GetAppearanceSources, appearanceID)
        if ok and sources then
            for _, src in ipairs(sources) do
                if type(src) == "table" and src.isCollected then return true end
                local sid = type(src) == "table" and src.sourceID or src
                if sid and C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance then
                    local hasOK, hasIt = pcall(C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance, sid)
                    if hasOK and hasIt then return true end
                end
            end
            return false
        end
    end

    return nil
end

function Render:IsItemCollected(owner, itemID)
    -- v15.5.0: delegate to CollectionAPI. The 4-step resolve chain that
    -- lived here (try itemID, try link, try constructed link, fallback to
    -- sourceID) is owned by CollectionAPI now, with a shared cache that's
    -- bigger and properly invalidated by collection events.
    --
    -- For display purposes BLT treats COLLECTED and KNOWN_OTHER the same:
    -- both mean "you have this look", which is what users see as a "✓".
    if not itemID then return false end
    if not ETB.CollectionAPI then return false end
    local state = ETB.CollectionAPI:GetState(itemID)
    if state == "COLLECTED" or state == "KNOWN_OTHER" then return true end
    return false
end

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

function Render:CanPlayerUseItem(owner, itemID)
    if not itemID then return false end

    -- v15.5.0: delegate equipLoc / armor-type / weapon-type / item-class
    -- checks to CollectionAPI. If item info isn't loaded yet, CollectionAPI
    -- returns ok=true (permissive) so the display doesn't drop unloaded items.
    if ETB.CollectionAPI then
        local ok = ETB.CollectionAPI:CanLearn(itemID)
        if not ok then return false end
    else
        -- Defensive fallback for the (impossible) case where CollectionAPI
        -- isn't loaded. Drop through to the class-restriction check below.
    end

    -- Class-specific restrictions (e.g. warrior-only tier items) are
    -- orthogonal to CollectionAPI's checks — keep handled here.
    if ETB.ClassRestrictions then
        if not ETB.ClassRestrictions:IsItemUsableByPlayer(itemID) then
            return false
        end
    end

    return true
end

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

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

function Render:AddLootToTooltip(owner, tooltip, npcID)
    if not tooltip or not npcID then return end
    if not owner.indexBuilt then return end
    
    local npcItems = owner.npcIndex[npcID]
    if not npcItems then return end
    
    -- Clear pending items for this render pass
    owner._pendingItems = {}
    
    -- Detect current instance difficulty (for header display only).
    -- v15.7.1: GetCurrentDifficulty is a file-local in BLT.lua, re-exposed
    -- on the owner as owner.GetCurrentDifficulty. Bare `GetCurrentDifficulty()`
    -- doesn't resolve here because Lua locals don't cross file chunks.
    local diffInfo = owner.GetCurrentDifficulty and owner.GetCurrentDifficulty() or nil
    
    -- Get player class bitmask for tier token expansion
    local playerClassBit = 0
    -- Primary: ETB.ClassRestrictions module
    if ETB.ClassRestrictions and ETB.ClassRestrictions.GetPlayerClassID then
        local cid = ETB.ClassRestrictions:GetPlayerClassID()
        if cid and cid >= 1 and cid <= 11 then
            local bits = {1,2,4,8,16,32,64,128,256,512,1024}
            playerClassBit = bits[cid] or 0
        end
    end
    -- Fallback: Blizzard API
    if playerClassBit == 0 then
        local _, _, classID = UnitClass("player")
        if classID and classID >= 1 and classID <= 11 then
            local bits = {1,2,4,8,16,32,64,128,256,512,1024}
            playerClassBit = bits[classID] or 0
        end
    end
    
    -- Determine bucket mode from JLoot diff values on this boss's items:
    --   "flat"     = Classic/TBC: single list, no headers
    --   "size"     = Wrath Naxx/OS/EoE/Ulduar/VoA: 10-Man / 25-Man
    --   "wrath4"   = Wrath ICC/ToC/RS: 10N / 10H / 25N / 25H
    --   "difficulty" = Cata/MoP: Normal / Heroic
    local has16or64, has32or128, hasCataStyle = false, false, false
    for itemID in pairs(npcItems) do
        if ETB.JLoot then
            local ld = ETB.JLoot[itemID]
            if ld then
                local d = type(ld[1]) == "table" and (ld[1][2] or 0) or (ld[2] or 0)
                if d == 16 or d == 64 then has16or64 = true end
                if d == 32 or d == 128 then has32or128 = true end
                if d == 1 or d == 2 or d == 3 or d == 7 or d == 8 or d == 11 or d == 15 then
                    hasCataStyle = true
                end
            end
        end
        if has32or128 then break end  -- 4-bucket confirmed, stop early
    end
    
    local bucketMode
    if has32or128 then
        bucketMode = "wrath4"
    elseif has16or64 then
        bucketMode = "size"
    elseif hasCataStyle then
        bucketMode = "difficulty"
    else
        bucketMode = "flat"
    end
    
    -- Bucket definitions per mode
    local buckets, bucketOrder, bucketNames, bucketColors, seenPerBucket
    
    if bucketMode == "wrath4" then
        buckets = { ["10N"]={}, ["10H"]={}, ["25N"]={}, ["25H"]={} }
        bucketOrder = {"10N", "10H", "25N", "25H"}
        bucketNames = { ["10N"]="10-Man Normal", ["10H"]="10-Man Heroic",
                        ["25N"]="25-Man Normal", ["25H"]="25-Man Heroic" }
        bucketColors = { ["10N"]="|cffffff00", ["10H"]="|cffff8000",
                         ["25N"]="|cffffff00", ["25H"]="|cffff8000" }
        seenPerBucket = { ["10N"]={}, ["10H"]={}, ["25N"]={}, ["25H"]={} }
    elseif bucketMode == "size" then
        buckets = { ["10"]={}, ["25"]={} }
        bucketOrder = {"10", "25"}
        bucketNames = { ["10"]="10-Man", ["25"]="25-Man" }
        bucketColors = { ["10"]="|cffffff00", ["25"]="|cffffff00" }
        seenPerBucket = { ["10"]={}, ["25"]={} }
    elseif bucketMode == "difficulty" then
        buckets = { ["N"]={}, ["H"]={} }
        bucketOrder = {"N", "H"}
        bucketNames = { ["N"]="Normal", ["H"]="Heroic" }
        bucketColors = { ["N"]="|cffffff00", ["H"]="|cffff8000" }
        seenPerBucket = { ["N"]={}, ["H"]={} }
    else -- flat
        buckets = { ["all"]={} }
        bucketOrder = {"all"}
        bucketNames = { ["all"]="" }
        bucketColors = { ["all"]="|cffffff00" }
        seenPerBucket = { ["all"]={} }
    end
    
    local collectedCount = 0
    local totalCount = 0
    
    local DIFF_IS_HEROIC = {[2]=true,[8]=true,[11]=true}
    
    local function getBucketKey(jlootDiff, forceBucket)
        if type(forceBucket) == "string" then return forceBucket end
        if bucketMode == "wrath4" then
            if jlootDiff == 16 then return "10N"
            elseif jlootDiff == 32 then return "10H"
            elseif jlootDiff == 64 then return "25N"
            elseif jlootDiff == 128 then return "25H"
            else return "25N" end
        elseif bucketMode == "size" then
            if jlootDiff == 16 then return "10"
            elseif jlootDiff == 64 then return "25"
            else return "25" end
        elseif bucketMode == "difficulty" then
            if forceBucket == true then return "H"
            elseif forceBucket == false then return "N"
            elseif jlootDiff and DIFF_IS_HEROIC[jlootDiff] then return "H"
            else return "N" end
        else
            return "all"
        end
    end
    
    local function getJLootDiff(itemID)
        if ETB.JLoot then
            local ld = ETB.JLoot[itemID]
            if ld then
                return type(ld[1]) == "table" and (ld[1][2] or nil) or (ld[2] or nil)
            end
        end
        return nil
    end
    
    local function addItem(itemID, isToken, forceBucket)
        local jd = getJLootDiff(itemID)
        local bucketKey = getBucketKey(jd, forceBucket)
        
        local seen = seenPerBucket[bucketKey]
        if seen[itemID] then return end
        
        local itemName, _, quality, _, _, _, _, _, _, texture = GetItemInfo(itemID)
        if not itemName then
            GetItemInfo(itemID)
            local _, _, _, _, icon = GetItemInfoInstant(itemID)
            itemName = nil
            texture = icon
            quality = 3
            if not owner._pendingItems then owner._pendingItems = {} end
            owner._pendingItems[itemID] = npcID
        end
        if not itemName then
            itemName = "Loading..."
        end
        
        seen[itemID] = true
        
        local isCollected = not isToken and owner:IsItemCollected(itemID) or false

        
        local entry = {
            itemID = itemID,
            name = itemName,
            quality = quality or 0,
            texture = texture,
            collected = isCollected,
            isToken = isToken or false,
        }
        
        buckets[bucketKey][#buckets[bucketKey] + 1] = entry
        
        if not isToken then
            totalCount = totalCount + 1
            if isCollected then
                collectedCount = collectedCount + 1
            end
        end
    end
    
    for itemID, flag in pairs(npcItems) do
        -- Quick filter: skip trinkets/rings/necks even if uncached
        local _, _, _, dispEquipLoc = GetItemInfoInstant(itemID)
        if dispEquipLoc and owner.EXCLUDED_EQUIP_LOCS[dispEquipLoc] then
            -- skip non-transmoggable slots
        else
        local isTierToken = ETB.TierTokenMap and ETB.TierTokenMap[itemID]
        
        if not isTierToken and not owner:CanPlayerUseItem(itemID) then
            -- skip
        elseif isTierToken then
            -- Only show this token if it has pieces for the player's class
            local hasClassPiece = false
            if playerClassBit > 0 then
                for _, mapping in ipairs(isTierToken) do
                    local classMask = mapping[2]
                    if classMask == 0 or bit.band(classMask, playerClassBit) > 0 then
                        hasClassPiece = true
                        break
                    end
                end
            else
                -- Class unknown: show all tokens
                hasClassPiece = true
            end
            
            if hasClassPiece then
                local tokenDiff = getJLootDiff(itemID)
                local tokenBucket = getBucketKey(tokenDiff, nil)
                
                -- Add the token to its bucket
                addItem(itemID, true, tokenBucket)
                
                -- Add expanded pieces into SAME bucket as token
                for _, mapping in ipairs(isTierToken) do
                    local pieceID = mapping[1]
                    local classMask = mapping[2]
                    if playerClassBit == 0 or classMask == 0 or bit.band(classMask, playerClassBit) > 0 then
                        addItem(pieceID, false, tokenBucket)
                    end
                end
            end
        else
            addItem(itemID, false)
        end
        end -- equipLoc filter
    end
    
    -- Add shared boss loot (items from the same instance but no specific boss)
    if owner.instanceLoot then
        local instIDsToCheck = {}
        
        -- Primary: npcToInstances (from compact data)
        if owner.npcToInstances and owner.npcToInstances[npcID] then
            for instID in pairs(owner.npcToInstances[npcID]) do
                instIDsToCheck[instID] = true
            end
        end
        
        -- Fallback: JNpcToEnc → JEncounters → instanceID (for JLoot-only bosses)
        if not next(instIDsToCheck) and ETB.JNpcToEnc and ETB.JEncounters then
            local encID = ETB.JNpcToEnc[npcID]
            if encID then
                local encData = ETB.JEncounters[encID]
                if encData and encData[2] then
                    instIDsToCheck[encData[2]] = true
                end
            end
        end
        
        for instID in pairs(instIDsToCheck) do
            local sharedItems = owner.instanceLoot[instID]
            if sharedItems then
                for itemID in pairs(sharedItems) do
                    if owner:CanPlayerUseItem(itemID) then
                        local _, _, _, sblEquipLoc = GetItemInfoInstant(itemID)
                        if not (sblEquipLoc and owner.EXCLUDED_EQUIP_LOCS[sblEquipLoc]) then
                            addItem(itemID, false)
                        end
                    end
                end
            end
        end
    end
    
    if totalCount == 0 then
        local anyItems = false
        for _, key in ipairs(bucketOrder) do
            if #buckets[key] > 0 then anyItems = true; break end
        end
        if not anyItems then return end
    end
    
    -- Sort each bucket: tokens first, then quality desc, then name
    local function sortItems(a, b)
        if a.isToken ~= b.isToken then return a.isToken end

        if a.quality ~= b.quality then return a.quality > b.quality end
        return a.name < b.name
    end
    
    -- Dedup by name within each bucket
    local function dedupBucket(bucket)
        local seen = {}
        local deduped = {}
        for _, entry in ipairs(bucket) do
            if entry.isToken or entry.name == "Loading..." then
                deduped[#deduped + 1] = entry
            elseif not seen[entry.name] then
                seen[entry.name] = true
                deduped[#deduped + 1] = entry
            end
        end
        return deduped
    end
    
    for _, key in ipairs(bucketOrder) do
        table.sort(buckets[key], sortItems)
        buckets[key] = dedupBucket(buckets[key])
    end
    
    -- ========================================================================
    -- DIFFICULTY FILTER: When inside a raid, only show the matching bucket.
    -- Outside instances, show all buckets for reference.
    -- ========================================================================
    if diffInfo and bucketMode ~= "flat" then
        local _, _, difficultyID = GetInstanceInfo()
        local matchBucket = nil
        
        if bucketMode == "wrath4" then
            local DIFF_TO_WRATH4 = {
                [3]="10N", [5]="10H", [4]="25N", [6]="25H",
                [1]="10N", [2]="10H",  -- dungeons: map to 10-man equivalents
            }
            matchBucket = DIFF_TO_WRATH4[difficultyID]
        elseif bucketMode == "size" then
            local DIFF_TO_SIZE = {
                [3]="10", [5]="10", [4]="25", [6]="25",
                [1]="10", [2]="10",
            }
            matchBucket = DIFF_TO_SIZE[difficultyID]
        elseif bucketMode == "difficulty" then
            local DIFF_TO_NH = {
                [1]="N", [3]="N", [4]="N", [14]="N",  -- Normal, 10N, 25N, Flex
                [2]="H", [5]="H", [6]="H",            -- Heroic, 10H, 25H
            }
            matchBucket = DIFF_TO_NH[difficultyID]
        end
        
        if matchBucket and buckets[matchBucket] and #buckets[matchBucket] > 0 then
            bucketOrder = { matchBucket }
        end
    end
    
    -- Recount after dedup + difficulty filter
    totalCount = 0
    collectedCount = 0
    for _, key in ipairs(bucketOrder) do
        for _, entry in ipairs(buckets[key]) do
            if not entry.isToken then
                totalCount = totalCount + 1
                if entry.collected then collectedCount = collectedCount + 1 end
            end
        end
    end
    
    -- Render
    local pct = totalCount > 0 and (100 * collectedCount / totalCount) or 0
    local countColor = collectedCount == totalCount and owner.COLLECTED_COLOR or owner.UNCOLLECTED_COLOR
    
    if diffInfo then
        tooltip:AddDoubleLine(
            "|cffffff00Contains:|r " .. owner.DIFFICULTY_COLOR .. diffInfo.name .. "|r",
            string.format("%s%d / %d (%.0f%%)|r", countColor, collectedCount, totalCount, pct)
        )
    else
        tooltip:AddDoubleLine(
            "|cffffff00Contains:|r",
            string.format("%s%d / %d (%.0f%%)|r", countColor, collectedCount, totalCount, pct)
        )
    end
    
    local shown = 0
    
    local function renderEntry(entry)
        if shown >= owner.MAX_ITEMS_SHOWN then return false end
        local icon = entry.texture and ("|T" .. entry.texture .. ":14:14:0:0|t ") or ""
        if entry.isToken then
            tooltip:AddDoubleLine(
                " " .. icon .. owner.TOKEN_COLOR .. "[" .. entry.name .. "]|r",
                owner.TOKEN_COLOR .. "Token|r"
            )
        else
            local qualityColor = "|cffffffff"
            if entry.quality > 0 then
                local r, g, b = GetItemQualityColor(entry.quality)
                qualityColor = string.format("|cff%02x%02x%02x", r*255, g*255, b*255)
            end
            local status = entry.collected and ICON_COLLECTED or ICON_NOT_COLLECTED

            tooltip:AddDoubleLine(
                " " .. icon .. qualityColor .. "[" .. entry.name .. "]|r",
                status
            )
        end
        shown = shown + 1
        return true
    end
    
    -- Render each bucket with section header
    for _, key in ipairs(bucketOrder) do
        if #buckets[key] > 0 and shown < owner.MAX_ITEMS_SHOWN then
            -- Only show header if it has a name (skip for flat mode)
            if bucketNames[key] ~= "" then
                tooltip:AddLine(bucketColors[key] .. bucketNames[key] .. "|r")
            end
            for _, entry in ipairs(buckets[key]) do
                if not renderEntry(entry) then break end
            end
        end
    end
    
    -- Overflow
    if shown >= owner.MAX_ITEMS_SHOWN then
        local remaining = totalCount - shown
        if remaining > 0 then
            tooltip:AddLine("|cff888888+" .. remaining .. " more items...|r")
        end
    end
    
    tooltip:Show()
end

