-- Housing Decor Guide - Acquisition Row Factories & Pooling
-- Row/cell creation for item list, vendor headers, and 3D gallery.

local addonName, addon = ...

HDG_AcqRows = HDG_AcqRows or {}

-- Theme helpers (delegating to ThemeEngine canonical helpers)
local HDG = _G.HousingDecorGuide
local function GetScheme() return HDG.Theme.GetCurrentScheme() end
local function RegisterWidget(w, t) HDG.Theme.RegisterWidget(w, t) end
local ApplyFont = function(fs, s, ft) HDG.Theme.ApplyFont(fs, s, ft) end
local function C_Text() return HDG.Theme.C_Text() end
local function C_Dim() return HDG.Theme.C_Dim() end
local function C_Success() return HDG.Theme.C_Success() end

-- ============================================================================
-- CACHED SCHEME (avoids per-row GetScheme/ApplyFont overhead)
-- ============================================================================

local cachedScheme = nil

function HDG_AcqRows.InvalidateSchemeCache()
    cachedScheme = nil
end

local function EnsureCachedScheme()
    if not cachedScheme then cachedScheme = GetScheme() end
    return cachedScheme
end

-- ============================================================================
-- ROW POOL STATE
-- ============================================================================

local rowPool = {}
local activeRows = {}
local rowFreeList = {}

local vendorRowPool = {}
local activeVendorRows = {}
local vendorRowFreeList = {}

local cellPool = {}
local activeCells = {}
local cellFreeList = {}

local pendingModelTimers = {}

-- ============================================================================
-- POOL MANAGEMENT
-- ============================================================================

local function GetPooledRow(parent)
    local row = table.remove(rowFreeList)
    if row then
        row.inUse = true
        if row:GetParent() ~= parent then row:SetParent(parent) end
        row:ClearAllPoints()
        table.insert(activeRows, row)
        return row
    end
    return nil
end

function HDG_AcqRows.ReleaseAllRows()
    cachedScheme = nil -- invalidate so next batch picks up any theme change
    for _, row in ipairs(activeRows) do
        row.inUse = false
        row:Hide()
        table.insert(rowFreeList, row)
    end
    wipe(activeRows)

    for _, row in ipairs(activeVendorRows) do
        row.inUse = false
        row:Hide()
        table.insert(vendorRowFreeList, row)
    end
    wipe(activeVendorRows)
end

local function GetPooledVendorRow(parent)
    local row = table.remove(vendorRowFreeList)
    if row then
        row.inUse = true
        if row:GetParent() ~= parent then row:SetParent(parent) end
        row:ClearAllPoints()
        table.insert(activeVendorRows, row)
        return row
    end
    return nil
end

local function GetPooledCell(parent)
    local cell = table.remove(cellFreeList)
    if cell then
        cell.inUse = true
        if cell:GetParent() ~= parent then cell:SetParent(parent) end
        cell:ClearAllPoints()
        table.insert(activeCells, cell)
        return cell
    end
    return nil
end

function HDG_AcqRows.ReleaseAllCells()
    for _, timer in ipairs(pendingModelTimers) do
        timer:Cancel()
    end
    wipe(pendingModelTimers)

    for _, cell in ipairs(activeCells) do
        cell.inUse = false
        cell:Hide()
        if cell.modelScene then cell.modelScene:Hide() end
        table.insert(cellFreeList, cell)
    end
    wipe(activeCells)
end

-- Update the "List N" badge on the Acquire tab button
function HDG_AcqRows.UpdateShopListBadge()
    local btn = HDG_AcqRows.shopListBtn
    if btn and HDG_VendorShoppingList then
        local count = HDG_VendorShoppingList.GetCount()
        btn:SetText(count > 0 and ("List " .. count) or "List")
    end
end

-- ============================================================================
-- CREATE ITEM ROW
-- ============================================================================

function HDG_AcqRows.CreateItemRow(parent, itemID, yOffset, listWidth, indent, selectedID, onSelect, CONFIG)
    local scheme = EnsureCachedScheme()
    indent = indent or 0

    local row = GetPooledRow(parent)
    if row then row.parentNpcID = nil end -- clear stale vendor context
    if not row then
        row = CreateFrame("Button", nil, parent, "BackdropTemplate")
        row:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Buttons\\WHITE8x8",
            edgeSize = 1,
            insets = { left = 0, right = 0, top = 0, bottom = 0 },
        })
        row.inUse = true
        table.insert(rowPool, row)
        table.insert(activeRows, row)

        row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.nameText:SetJustifyH("LEFT")
        row.nameText:SetWordWrap(false)
    end

    -- Ensure icons exist (pooled row may be recycled group header)
    if not row.collectedIcon then
        row.collectedIcon = row:CreateTexture(nil, "OVERLAY")
        row.collectedIcon:SetSize(CONFIG.ACQUISITION_TAB.STATUS_ICON_SIZE, CONFIG.ACQUISITION_TAB.STATUS_ICON_SIZE)
        row.collectedIcon:SetPoint("RIGHT", -6, 0)
        row.collectedIcon:SetTexture(CONFIG.TEXTURES.CHECKMARK)
    end
    if not row.repIcon then
        row.repIcon = row:CreateTexture(nil, "OVERLAY")
        row.repIcon:SetSize(CONFIG.ACQUISITION_TAB.STATUS_ICON_SIZE, CONFIG.ACQUISITION_TAB.STATUS_ICON_SIZE)
        row.repIcon:SetPoint("RIGHT", row.collectedIcon, "LEFT", -2, 0)
    end
    if not row.statusText then
        row.statusText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.statusText:SetPoint("RIGHT", row.repIcon, "LEFT", -2, 0)
        row.statusText:SetJustifyH("RIGHT")
    end

    row:SetSize(listWidth, CONFIG.ACQUISITION_TAB.ITEM_ROW_HEIGHT)
    row:SetPoint("TOPLEFT", indent, yOffset)

    row.nameText:ClearAllPoints()
    row.nameText:SetPoint("LEFT", 6, 0)

    local isCollected = HDG_Data.IsItemCollected(itemID)
    local name = HDG_Data.GetCachedItemName(itemID) or ("Item #" .. itemID)

    local npcIDs = HDG_VendorByItem and HDG_VendorByItem[itemID]
    local hasRep, meetsRep = false, true
    if npcIDs then
        for _, npcID in ipairs(npcIDs) do
            local ie = HDG_VendorDB[npcID] and HDG_VendorDB[npcID].items and HDG_VendorDB[npcID].items[itemID]
            if ie and (ie[2] or 0) ~= 0 then
                hasRep = true
                meetsRep = HDG_VendorResolver.MeetsRepRequirement({ factionID = ie[2], minRep = ie[3] or 0 })
                break
            end
        end
    end

    -- Apply colors
    if scheme then
        if selectedID == itemID then
            row:SetBackdropColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 0.3)
            row:SetBackdropBorderColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 0.8)
        else
            row:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, scheme.panel.a)
            row:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, scheme.border.a)
        end
        ApplyFont(row.nameText, scheme, "small")
    end

    -- Source icon prefix
    local displayName = name
    local si = HDG_AcqData.GetSourceInfo(itemID)
    local siType = si and si[2] or 0
    local isCraftable = HDG_AcqData.craftableByItem[itemID] ~= nil
    if si and HDG_AcqData.IsAcquirableType(siType) then
        local iconRef = HDG_AcqData.GetSourceTypeAtlas(siType, CONFIG)
        if iconRef then
            if iconRef:find("^TEXTURE:") then
                displayName = string.format("|T%s:14:14|t", iconRef:sub(9)) .. " " .. name
            else
                displayName = CreateAtlasMarkup(iconRef, 14, 14) .. " " .. name
            end
        end
    elseif isCraftable then
        displayName = string.format("|T%s:14:14|t", CONFIG.TEXTURES.CRAFTED_ICON) .. " " .. name
    end

    -- Dyeable indicator (dye icon after name)
    if HDG_DyeData and HDG_DyeData.IsDecorDyeable(itemID) then
        local dyeIcon = C_Item.GetItemIconByID(259113)
        if dyeIcon then
            displayName = displayName .. " " .. string.format("|T%s:12:12|t", dyeIcon)
        end
    end

    -- User note indicator
    if HDG and HDG.Store and HDG.Store:GetUserNote(itemID) then
        displayName = displayName .. " " .. C_Dim() .. "[N]|r"
    end

    -- Name text width constraint
    local ST = CONFIG.SOURCE_TYPE
    local hasStatus = (si and HDG_AcqData.IsAcquirableType(siType) and (siType == ST.ACHIEVEMENT or siType == ST.QUEST or siType == ST.WORLD_QUEST or siType == ST.DROP or siType == ST.CRAFTED or siType == ST.TREASURE or siType == ST.PROMOTION)) or (isCraftable and not (si and HDG_AcqData.IsAcquirableType(siType)))
    row.nameText:SetWidth(listWidth - indent - (hasStatus and 90 or 30))
    row.nameText:SetText(displayName)
    if scheme then
        if isCollected then
            row.nameText:SetTextColor(scheme.text.r, scheme.text.g, scheme.text.b, scheme.text.a)
        else
            row.nameText:SetTextColor(scheme.text_dim.r, scheme.text_dim.g, scheme.text_dim.b, scheme.text_dim.a)
        end
    end

    -- Collected icon
    if isCollected then
        row.collectedIcon:SetVertexColor(0.2, 0.9, 0.2)
        row.collectedIcon:Show()
    else
        row.collectedIcon:Hide()
    end

    -- Rep icon (atlas based on whether player meets requirement)
    if hasRep then
        local atlas = meetsRep and "gficon-chest-evergreen-greatvault-collect" or "gficon-chest-evergreen-greatvault-incomplete"
        row.repIcon:SetAtlas(atlas)
        row.repIcon:Show()
    else
        row.repIcon:Hide()
    end

    -- Completion status for acquirable source types and craftable items
    if hasStatus then
        if scheme then ApplyFont(row.statusText, scheme, "xsmall") end
        if isCollected then
            row.statusText:SetText("Done")
            row.statusText:SetTextColor(0.2, 0.9, 0.2)
        else
            row.statusText:SetText("Incomplete")
            row.statusText:SetTextColor(0.9, 0.4, 0.2)
        end
        row.statusText:Show()
    else
        row.statusText:Hide()
    end

    row.itemID = itemID

    -- Click handler
    row:SetScript("OnClick", function(self)
        if IsShiftKeyDown() then
            local npcIDs = HDG_VendorByItem and HDG_VendorByItem[self.itemID]
            if npcIDs and #npcIDs > 0 and HDG.Store then
                local npcID = self.parentNpcID or npcIDs[1]
                if HDG.Store:AddToVendorShoppingList(self.itemID, npcID) then
                    local iName = HDG_Data and HDG_Data.GetCachedItemName(self.itemID) or ("Item " .. self.itemID)
                    print(C_Success() .. "[HDG]|r Added " .. "|cFF14b8a6" .. iName .. "|r to shopping list")
                    if HDG_VendorShoppingList and HDG_VendorShoppingList.IsShown and HDG_VendorShoppingList.IsShown() then
                        HDG_VendorShoppingList.Refresh()
                    end
                    HDG_AcqRows.UpdateShopListBadge()
                end
            end
            return
        end
        if onSelect then onSelect(self.itemID) end
    end)

    -- Hover
    row:SetScript("OnEnter", function(self)
        if selectedID ~= self.itemID then
            local c = GetScheme()
            if c then self:SetBackdropColor(c.button_hover.r, c.button_hover.g, c.button_hover.b, 0.5) end
        end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        if self.itemID then
            local item = Item:CreateFromItemID(self.itemID)
            item:ContinueOnItemLoad(function()
                if GameTooltip:IsOwned(self) then
                    GameTooltip:SetItemByID(self.itemID)
                    GameTooltip:AddLine(" ")
                    GameTooltip:AddLine("Shift-Click to add to Shopping List", 0.5, 0.5, 0.5)
                    GameTooltip:Show()
                end
            end)
        end
    end)
    row:SetScript("OnLeave", function(self)
        if selectedID ~= self.itemID then
            local c = GetScheme()
            if c then
                self:SetBackdropColor(c.panel.r, c.panel.g, c.panel.b, c.panel.a)
            end
        end
        GameTooltip:Hide()
    end)

    row:Show()
    return row
end

-- ============================================================================
-- CREATE VENDOR HEADER ROW
-- ============================================================================

function HDG_AcqRows.CreateVendorHeaderRow(parent, npcID, yOffset, listWidth, isExpanded, onToggle, CONFIG)
    local scheme = EnsureCachedScheme()
    local ACQ = CONFIG.ACQUISITION_TAB
    local ve = HDG_VendorDB and HDG_VendorDB[npcID]
    if not ve then return nil, yOffset end

    local row = GetPooledVendorRow(parent)
    if not row then
        row = CreateFrame("Button", nil, parent, "BackdropTemplate")
        row:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Buttons\\WHITE8x8",
            edgeSize = 1,
            insets = { left = 0, right = 0, top = 0, bottom = 0 },
        })
        row.inUse = true
        table.insert(vendorRowPool, row)
        table.insert(activeVendorRows, row)

        row.collapseText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.collapseText:SetPoint("LEFT", 6, 0)
        row.collapseText:SetWidth(12)
        row.collapseText:SetJustifyH("LEFT")

        row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        row.nameText:SetPoint("LEFT", row.collapseText, "RIGHT", 4, 0)
        row.nameText:SetJustifyH("LEFT")
        row.nameText:SetWordWrap(false)

        row.countText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.countText:SetPoint("RIGHT", -6, 0)
        row.countText:SetJustifyH("RIGHT")
        row.countText:SetWordWrap(false)

        row.zoneText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.zoneText:SetPoint("RIGHT", row.countText, "LEFT", -4, 0)
        row.zoneText:SetJustifyH("RIGHT")
        row.zoneText:SetWordWrap(false)
    end

    row:SetSize(listWidth, ACQ.VENDOR_ROW_HEIGHT)
    row:SetPoint("TOPLEFT", 0, yOffset)

    if scheme then
        row:SetBackdropColor(scheme.bg.r, scheme.bg.g, scheme.bg.b, 0.8)
        row:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, scheme.border.a)

        ApplyFont(row.collapseText, scheme, "xsmall")
        row.collapseText:SetTextColor(scheme.text_dim.r, scheme.text_dim.g, scheme.text_dim.b, scheme.text_dim.a)

        ApplyFont(row.nameText, scheme, "small")
        row.nameText:SetTextColor(scheme.text_header.r, scheme.text_header.g, scheme.text_header.b, scheme.text_header.a)
        row.nameText:SetWidth(listWidth * 0.55)

        ApplyFont(row.countText, scheme, "small")
        row.countText:SetTextColor(scheme.text_dim.r, scheme.text_dim.g, scheme.text_dim.b, scheme.text_dim.a)

        ApplyFont(row.zoneText, scheme, "xsmall")
        row.zoneText:SetTextColor(scheme.text_dim.r, scheme.text_dim.g, scheme.text_dim.b, scheme.text_dim.a)
        row.zoneText:SetWidth(listWidth * 0.30)
    end

    row.collapseText:SetText(isExpanded and "-" or "+")
    row.zoneText:SetText(ve[2] or "")

    -- Faction icon inline
    local vendorFaction = ve[6] or "N"
    local factionPrefix = ""
    if vendorFaction == "A" then
        factionPrefix = CreateAtlasMarkup("communities-create-button-wow-alliance", 14, 14) .. " "
    elseif vendorFaction == "H" then
        factionPrefix = CreateAtlasMarkup("communities-create-button-wow-horde", 14, 14) .. " "
    end
    local vendorDisplayName = factionPrefix .. (ve[1] or ("NPC #" .. npcID))
    if HDG and HDG.Store and HDG.Store:GetVendorNote(npcID) then
        vendorDisplayName = vendorDisplayName .. " " .. C_Dim() .. "[N]|r"
    end
    row.nameText:SetText(vendorDisplayName)

    -- Use cached counts from RebuildFilteredVendors
    local counts = HDG_AcqData.vendorCounts[npcID]
    local total = counts and counts.total or 0
    local collected = counts and counts.collected or 0
    row.countText:SetText(string.format("(%d/%d)", collected, total))

    row.npcID = npcID

    -- Click: toggle expand (or shift-click to add all items to shopping list)
    row:SetScript("OnClick", function(self)
        if IsShiftKeyDown() then
            local ve = HDG_VendorDB and HDG_VendorDB[self.npcID]
            if ve and ve.items and HDG.Store then
                local addCount = 0
                for iID, _ in pairs(ve.items) do
                    if HDG.Store:AddToVendorShoppingList(iID, self.npcID) then
                        addCount = addCount + 1
                    end
                end
                if addCount > 0 then
                    local vname = HDG_VendorResolver.GetVendorNameByID(self.npcID)
                    print(C_Success() .. "[HDG]|r Added " .. "|cFF14b8a6" .. addCount .. "|r items from " .. vname .. " to shopping list")
                    if HDG_VendorShoppingList and HDG_VendorShoppingList.IsShown and HDG_VendorShoppingList.IsShown() then
                        HDG_VendorShoppingList.Refresh()
                    end
                    HDG_AcqRows.UpdateShopListBadge()
                end
            end
            return
        end
        if onToggle then onToggle(self.npcID) end
    end)

    -- Hover
    row:SetScript("OnEnter", function(self)
        local c = GetScheme()
        if c then self:SetBackdropColor(c.button_hover.r, c.button_hover.g, c.button_hover.b, 0.3) end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(vendorDisplayName, 1, 1, 1)
        GameTooltip:AddLine("Shift-Click to add all items to Shopping List", 0.5, 0.5, 0.5)
        GameTooltip:Show()
    end)
    row:SetScript("OnLeave", function(self)
        local c = GetScheme()
        if c then self:SetBackdropColor(c.bg.r, c.bg.g, c.bg.b, 0.8) end
        GameTooltip:Hide()
    end)

    row:Show()
    return row
end

-- ============================================================================
-- CREATE GALLERY CELL (3D model)
-- ============================================================================

function HDG_AcqRows.CreateGalleryCell(parent, itemID, x, y, cellSize, selectedID, onSelect, CONFIG)
    local scheme = EnsureCachedScheme()
    local ACQ = CONFIG.ACQUISITION_TAB

    local cell = GetPooledCell(parent)
    if not cell then
        cell = CreateFrame("Button", nil, parent, "BackdropTemplate")
        cell:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Buttons\\WHITE8x8",
            edgeSize = 1,
            insets = { left = 0, right = 0, top = 0, bottom = 0 },
        })
        cell.inUse = true
        table.insert(cellPool, cell)
        table.insert(activeCells, cell)
        RegisterWidget(cell, "Panel")

        cell.modelScene = CreateFrame("ModelScene", nil, cell, "PanningModelSceneMixinTemplate")
        cell.modelScene:SetSize(cellSize - 4, cellSize - 4)
        cell.modelScene:SetPoint("TOPLEFT", 2, -2)
        cell.modelScene:EnableMouse(false)

        cell.fallbackIcon = cell:CreateTexture(nil, "ARTWORK")
        cell.fallbackIcon:SetSize(cellSize - 8, cellSize - 8)
        cell.fallbackIcon:SetPoint("CENTER", 0, 6)
        cell.fallbackIcon:Hide()

        cell.nameLabel = cell:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        cell.nameLabel:SetPoint("BOTTOM", 0, 2)
        cell.nameLabel:SetWidth(ACQ.CELL_NAME_WIDTH)
        cell.nameLabel:SetJustifyH("CENTER")

        cell.checkmark = cell:CreateTexture(nil, "OVERLAY", nil, 2)
        cell.checkmark:SetSize(ACQ.STATUS_ICON_SIZE, ACQ.STATUS_ICON_SIZE)
        cell.checkmark:SetPoint("BOTTOMRIGHT", -2, 14)
        cell.checkmark:SetTexture(CONFIG.TEXTURES.CHECKMARK)
        cell.checkmark:SetVertexColor(0.2, 0.9, 0.2)
    end

    cell:SetSize(cellSize, cellSize + 14)
    cell:SetPoint("TOPLEFT", x, y)

    local isCollected = HDG_Data.IsItemCollected(itemID)
    local isSelected = (selectedID == itemID)

    if scheme then
        cell:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.8)
        if isSelected then
            cell:SetBackdropBorderColor(1.0, 0.84, 0.0, 1.0)
        elseif isCollected then
            cell:SetBackdropBorderColor(0.2, 0.9, 0.2, 0.8)
        else
            cell:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 0.5)
        end
    end

    if not isCollected and not isSelected then
        cell:SetAlpha(0.7)
    else
        cell:SetAlpha(1.0)
    end

    if isCollected then cell.checkmark:Show() else cell.checkmark:Hide() end

    local name = HDG_Data.GetCachedItemName(itemID) or ("Item #" .. itemID)
    cell.nameLabel:SetText(name)
    if scheme then
        ApplyFont(cell.nameLabel, scheme, "small")
        cell.nameLabel:SetTextColor(scheme.text_dim.r, scheme.text_dim.g, scheme.text_dim.b, scheme.text_dim.a)
    end

    cell.itemID = itemID
    cell.modelScene:Hide()
    cell.fallbackIcon:Hide()

    -- Lazy-load 3D model via staggered timer
    local cellRef = cell
    local loadDelay = #pendingModelTimers * 0.05
    local timer = C_Timer.NewTimer(loadDelay, function()
        if not cellRef.inUse or cellRef.itemID ~= itemID then return end
        if not C_HousingCatalog or not C_HousingCatalog.GetCatalogEntryInfoByItem then return end
        local decorInfo = C_HousingCatalog.GetCatalogEntryInfoByItem(itemID, true)
        if decorInfo and decorInfo.asset then
            local sceneID = decorInfo.uiModelSceneID or ACQ.DEFAULT_SCENE_ID
            local sOk = pcall(function()
                cellRef.modelScene:TransitionToModelSceneID(sceneID, CAMERA_TRANSITION_TYPE_IMMEDIATE, CAMERA_MODIFICATION_TYPE_DISCARD, true)
            end)
            if sOk then
                local actor = cellRef.modelScene:GetActorByTag("decor")
                if actor then actor:SetModelByFileID(decorInfo.asset) end
                cellRef.modelScene:Show()
                return
            end
        end
        local icon = C_Item.GetItemIconByID(itemID)
        cellRef.fallbackIcon:SetTexture(icon or "Interface\\Icons\\INV_Misc_QuestionMark")
        cellRef.fallbackIcon:Show()
    end)
    table.insert(pendingModelTimers, timer)

    -- Click handler
    cell:SetScript("OnClick", function(self)
        if onSelect then onSelect(self.itemID) end
    end)

    -- Tooltip
    cell:SetScript("OnEnter", function(self)
        local c = GetScheme()
        if c then self:SetBackdropBorderColor(1.0, 0.84, 0.0, 1.0) end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        local tipName = HDG_Data.GetCachedItemName(self.itemID) or ("Item #" .. self.itemID)
        local tipCollected = HDG_Data.IsItemCollected(self.itemID)
        local tipNpcIDs = HDG_VendorByItem and HDG_VendorByItem[self.itemID]
        local tipPrice = 0
        if tipNpcIDs and tipNpcIDs[1] and HDG_VendorDB then
            local ve = HDG_VendorDB[tipNpcIDs[1]]
            local ie = ve and ve.items and ve.items[self.itemID]
            tipPrice = ie and ie[1] or 0
        end
        local priceStr = HDG_VendorResolver.FormatGoldCost(tipPrice)

        GameTooltip:AddLine(tipName, 1, 1, 1)
        if tipCollected then
            GameTooltip:AddLine(C_Success() .. "Collected|r", 0.2, 0.9, 0.2)
        else
            GameTooltip:AddLine(C_Dim() .. "Not collected|r", 0.5, 0.5, 0.5)
        end
        if priceStr then
            GameTooltip:AddLine("Price: " .. priceStr, 0.8, 0.8, 0.8)
        else
            local currencies = HDG_VendorResolver.GetCurrencyCost(self.itemID)
            if currencies then
                local iconSize = CONFIG.ACQUISITION_TAB.CURRENCY_ICON_SIZE
                for _, cc in ipairs(currencies) do
                    local iconStr = cc.icon and cc.icon ~= 0 and string.format("|T%s:%d:%d|t ", cc.icon, iconSize, iconSize) or ""
                    GameTooltip:AddDoubleLine(iconStr .. (cc.name or "Currency"), tostring(cc.amount), 0.8, 0.8, 0.8, 1, 1, 1)
                end
            else
                GameTooltip:AddLine("|cFFAAAA00Visit vendor to see cost|r", 0.7, 0.7, 0.5)
            end
        end
        GameTooltip:Show()
    end)
    cell:SetScript("OnLeave", function(self)
        local c = GetScheme()
        local collected = HDG_Data.IsItemCollected(self.itemID)
        if c then
            if selectedID == self.itemID then
                self:SetBackdropBorderColor(1.0, 0.84, 0.0, 1.0)
            elseif collected then
                self:SetBackdropBorderColor(0.2, 0.9, 0.2, 0.8)
            else
                self:SetBackdropBorderColor(c.border.r, c.border.g, c.border.b, 0.5)
            end
        end
        GameTooltip:Hide()
    end)

    cell:Show()
    return cell
end

-- ============================================================================
-- HIGHLIGHT-ONLY UPDATES (no rebuild)
-- ============================================================================

function HDG_AcqRows.UpdateGalleryHighlights(selectedItemID)
    local scheme = GetScheme()
    if not scheme then return end
    for _, cell in ipairs(activeCells) do
        if not cell.inUse then break end
        local isSelected = (cell.itemID == selectedItemID)
        local isCollected = HDG_Data.IsItemCollected(cell.itemID)
        if isSelected then
            cell:SetBackdropBorderColor(1.0, 0.84, 0.0, 1.0)
            cell:SetAlpha(1.0)
        elseif isCollected then
            cell:SetBackdropBorderColor(0.2, 0.9, 0.2, 0.8)
            cell:SetAlpha(1.0)
        else
            cell:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 0.5)
            cell:SetAlpha(0.7)
        end
    end
end

function HDG_AcqRows.UpdateRowHighlights(selectedItemID)
    local scheme = GetScheme()
    if not scheme then return end
    for _, row in ipairs(activeRows) do
        if not row.inUse then break end
        if row.itemID == selectedItemID then
            row:SetBackdropColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 0.3)
            row:SetBackdropBorderColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 0.8)
        elseif row.itemID then
            row:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, scheme.panel.a)
            row:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, scheme.border.a)
        end
    end
end
