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

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

-- ============================================================================
-- CACHED DYE ICON (avoids per-row C_Item.GetItemIconByID + string.format)
-- ============================================================================

local cachedDyeIconStr -- lazily populated
-- sourceType -> chip label for AllDecorDB alt field
-- Gate types only — Vendor (5) is an acquisition method, not a gate
local ALT_CHIP_LABELS = {
    [1] = "Ach", [2] = "Q", [3] = "Q", [4] = "Drop",
    [6] = "Craft", [7] = "Learn", [8] = "Gath", [9] = "Tres",
    [10] = "Promo", [11] = "Prof",
}

local function GetDyeIconStr()
    if not cachedDyeIconStr then
        local icon = C_Item.GetItemIconByID(259113)
        if icon then cachedDyeIconStr = string.format("|T%s:12:12|t", icon) end
    end
    return cachedDyeIconStr
end

-- ============================================================================
-- SHARED ROW HANDLERS (avoids per-row closure allocation)
-- ============================================================================

local function ItemRow_OnClick(self)
    if IsShiftKeyDown() and HDG.Store then
        local npcIDs = HDG_VendorByItem and HDG_VendorByItem[self.itemID]
        local npcID = nil
        if npcIDs and #npcIDs > 0 then
            npcID = self.parentNpcID or npcIDs[1]
        end
        if HDG.Store:AddToVendorShoppingList(self.itemID, npcID) then
            local iName = HDG_Data and HDG_Data.GetCachedItemName(self.itemID) or ("Item " .. self.itemID)
            local dest = npcID and "shopping list" or "wish list"
            print(HDG.Theme.C_Success() .. "[HDG]|r Added " .. "|cFF14b8a6" .. iName .. "|r to " .. dest)
            if HDG_VendorShoppingList and HDG_VendorShoppingList.IsShown and HDG_VendorShoppingList.IsShown() then
                HDG_VendorShoppingList.Refresh()
            end
            HDG_AcqRows.UpdateShopListBadge()
        end
        return
    end
    if self._onSelect then self._onSelect(self.itemID, self.parentNpcID) end
end

local function ItemRow_OnEnter(self)
    if self._selectedID ~= self.itemID then
        local c = EnsureCachedScheme()
        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.78, 0.78, 0.78)
                GameTooltip:Show()
            end
        end)
    end
end

local function ItemRow_OnLeave(self)
    if self._selectedID ~= self.itemID then
        local c = EnsureCachedScheme()
        if c then self:SetBackdropColor(c.panel.r, c.panel.g, c.panel.b, c.panel.a) end
    end
    GameTooltip:Hide()
end

local function VendorRow_OnClick(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(HDG.Theme.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 self._onToggle then self._onToggle(self.npcID) end
end

local function VendorRow_OnEnter(self)
    local c = EnsureCachedScheme()
    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(self._displayName or "", 1, 1, 1)
    GameTooltip:AddLine("Shift-Click to add all items to Shopping List", 0.78, 0.78, 0.78)
    GameTooltip:Show()
end

local function VendorRow_OnLeave(self)
    local c = EnsureCachedScheme()
    if c then self:SetBackdropColor(c.bg.r, c.bg.g, c.bg.b, 0.8) end
    GameTooltip:Hide()
end

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

local itemRowPool
local vendorRowPool
local galleryCellPool = { all = {}, active = {}, free = {}, timers = {} }

function HDG_AcqRows.ReleaseAllRows()
    cachedScheme = nil
    if itemRowPool then itemRowPool:ReleaseAll() end
    if vendorRowPool then vendorRowPool:ReleaseAll() end
end

function HDG_AcqRows.ReleaseAllCells()
    HDG_UIHelpers.ReleaseAllGalleryCells(galleryCellPool)
end

-- Update the shopping list badge on the main title bar
function HDG_AcqRows.UpdateShopListBadge()
    if HDG_UI and HDG_UI.UpdateShopListBadge then HDG_UI.UpdateShopListBadge() end
end

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

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

    if not itemRowPool then
        itemRowPool = HDG_UIHelpers.CreateRowPool(function(parent)
            local 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.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            row.nameText:SetJustifyH("LEFT")
            row.nameText:SetWordWrap(false)

            return row
        end)
    end

    local row = itemRowPool:Acquire(parent)
    row.parentNpcID = nil

    -- 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.gateChip then
        local chip = CreateFrame("Frame", nil, row, "BackdropTemplate")
        chip:SetSize(36, 14)
        chip:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 0.5 })
        local cs = EnsureCachedScheme()
        chip:SetBackdropColor(cs.bg.r, cs.bg.g, cs.bg.b, 0.5)
        chip:SetBackdropBorderColor(cs.border.r, cs.border.g, cs.border.b, 0.4)
        chip.text = chip:CreateFontString(nil, "OVERLAY", "GameFontHighlightExtraSmall")
        chip.text:SetPoint("CENTER", 0, 0)
        chip.text:SetJustifyH("CENTER")
        chip:SetPoint("RIGHT", row.collectedIcon, "LEFT", -2, 0)
        chip:Hide()
        RegisterWidget(chip, "Frame")
        row.gateChip = chip
    end
    if not row.statusText then
        row.statusText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.statusText:SetPoint("RIGHT", row.gateChip, "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 si0 = HDG_AcqData.GetSourceInfo(itemID)
    local adEntry = HDG_AllDecorDB and HDG_AllDecorDB[itemID]
    local name = HDG_Data.GetCachedItemName(itemID) or ("Item #" .. itemID)

    -- Chip: AllDecorDB alt is primary source, VendorDB enhances with met/unmet
    local npcIDs = HDG_VendorByItem and HDG_VendorByItem[itemID]
    local hasGate, meetsGate, gateType = false, false, nil
    local alt = adEntry and adEntry.alt
    if alt then
        local label = ALT_CHIP_LABELS[alt[1]]
        if label then hasGate = true; gateType = label end
    end
    -- VendorDB: enhance with met/unmet status + rep/race gates (have actual IDs)
    if npcIDs then
        for _, npcID in ipairs(npcIDs) do
            local ve = HDG_VendorDB[npcID]
            local ie = ve and ve.items and ve.items[itemID]
            if ie then
                if (ie[2] or 0) ~= 0 then
                    hasGate = true; gateType = gateType or "Rep"
                    if not HDG_VendorResolver.MeetsRepRequirement({ factionID = ie[2], minRep = ie[3] or 0 }) then meetsGate = false end
                end
                if (ie[6] or 0) ~= 0 then
                    if not C_QuestLog.IsQuestFlaggedCompleted(ie[6]) then meetsGate = false end
                end
                if (ie[7] or 0) ~= 0 then
                    local _, _, _, completed = GetAchievementInfo(ie[7])
                    if not completed then meetsGate = false end
                end
            end
            if ve and ve.race then
                hasGate = true; gateType = gateType or "Race"
                if not HDG_VendorResolver.MeetsRaceRequirement({ race = ve.race }) then meetsGate = false end
            end
            if hasGate then 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 = si0
    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 — uses cached icon string)
    local StyleEngine = addon.HDG_StyleEngine
    if StyleEngine and StyleEngine:IsDyeable(itemID) then
        local dyeStr = GetDyeIconStr()
        if dyeStr then displayName = displayName .. " " .. dyeStr end
    end

    -- User note indicator
    if HDG and HDG.Store and HDG.Store:GetUserNote(itemID) then
        displayName = displayName .. " " .. HDG.Theme.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 siType == ST.SHOP)) or (isCraftable and not (si and HDG_AcqData.IsAcquirableType(siType)))
    row.nameText:SetWidth(listWidth - indent - ((hasStatus or hasGate) and 110 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

    -- Gate chip (Rep/Q/Ach/Race label with met/unmet coloring)
    if hasGate and gateType then
        local cs = HDG.Theme.currentScheme
        local clr = (isCollected or meetsGate) and cs.success or cs.warning
        row.gateChip.text:SetText(gateType)
        row.gateChip.text:SetTextColor(clr.r, clr.g, clr.b)
        row.gateChip:Show()
    else
        row.gateChip:Hide()
    end

    -- Completion status for acquirable source types and craftable items (skip when gate chip shown)
    if hasStatus and not hasGate 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
    row._onSelect = onSelect
    row._selectedID = selectedID

    -- Shared handlers (set once, reused across pool)
    if not row._handlersSet then
        row:SetScript("OnClick", ItemRow_OnClick)
        row:SetScript("OnEnter", ItemRow_OnEnter)
        row:SetScript("OnLeave", ItemRow_OnLeave)
        row._handlersSet = true
    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

    if not vendorRowPool then
        vendorRowPool = HDG_UIHelpers.CreateRowPool(function(parent)
            local 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.collapseBtn = CreateFrame("Button", nil, row)
            row.collapseBtn:SetSize(14, 14)
            row.collapseBtn:SetPoint("LEFT", 4, 0)
            row.collapseBtn:SetNormalAtlas("common-button-collapseExpand-down")
            row.collapseBtn:SetPushedAtlas("common-button-collapseExpand-down-pressed")
            row.collapseBtn:SetHighlightAtlas("common-button-collapseExpand-hover")

            row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            row.nameText:SetPoint("LEFT", row.collapseBtn, "RIGHT", 2, 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)

            return row
        end)
    end

    local row = vendorRowPool:Acquire(parent)

    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.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

    if isExpanded then
        row.collapseBtn:SetNormalAtlas("common-button-collapseExpand-down")
        row.collapseBtn:SetPushedAtlas("common-button-collapseExpand-down-pressed")
    else
        row.collapseBtn:SetNormalAtlas("common-button-collapseExpand-up")
        row.collapseBtn:SetPushedAtlas("common-button-collapseExpand-up-pressed")
    end
    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 .. HDG_VendorResolver.GetVendorNameByID(npcID)
    if HDG and HDG.Store and HDG.Store:GetVendorNote(npcID) then
        vendorDisplayName = vendorDisplayName .. " " .. HDG.Theme.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
    row._onToggle = onToggle
    row._displayName = vendorDisplayName

    -- Shared handlers (set once, reused across pool)
    if not row._handlersSet then
        row:SetScript("OnClick", VendorRow_OnClick)
        row:SetScript("OnEnter", VendorRow_OnEnter)
        row:SetScript("OnLeave", VendorRow_OnLeave)
        row.collapseBtn:SetScript("OnClick", function(self) VendorRow_OnClick(self:GetParent()) end)
        row._handlersSet = true
    end

    row:Show()
    return row
end

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

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

    local cell = HDG_UIHelpers.CreateGalleryCell(galleryCellPool, parent, cellSize, {
        statusIconSize = ACQ.STATUS_ICON_SIZE,
        checkmarkTexture = CONFIG.TEXTURES.CHECKMARK,
    })
    cell:SetPoint("TOPLEFT", x, y)

    local isCollected = HDG_Data.IsItemCollected(itemID)
    local isSelected = (selectedID == itemID)
    HDG_UIHelpers.UpdateGalleryCellBackground(cell, isSelected)
    cell.checkmark:SetShown(isCollected)
    cell.redX:SetShown(not isCollected)

    cell.itemID = itemID
    cell.parentNpcID = npcID

    -- Lazy-load visuals via staggered timer
    local cellRef = cell
    local loadDelay = #galleryCellPool.timers * 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)
        local sceneID = (decorInfo and decorInfo.uiModelSceneID) or ACQ.DEFAULT_SCENE_ID
        HDG_UIHelpers.UpdateGalleryCellVisuals(cellRef, decorInfo, isCollected, sceneID)
        if decorInfo and decorInfo.recordID then
            HDG_UIHelpers.UpdateGalleryCellQuantity(cellRef, decorInfo.recordID)
        end
    end)
    table.insert(galleryCellPool.timers, timer)

    -- Click handler with shift-click shopping list support
    cell:SetScript("OnClick", function(self)
        if IsShiftKeyDown() and HDG.Store then
            local nIDs = HDG_VendorByItem and HDG_VendorByItem[self.itemID]
            local nID = nil
            if nIDs and #nIDs > 0 then
                nID = self.parentNpcID or nIDs[1]
            end
            if HDG.Store:AddToVendorShoppingList(self.itemID, nID) then
                local iName = HDG_Data and HDG_Data.GetCachedItemName(self.itemID) or ("Item " .. self.itemID)
                local dest = nID and "shopping list" or "wish list"
                print(HDG.Theme.C_Success() .. "[HDG]|r Added " .. "|cFF14b8a6" .. iName .. "|r to " .. dest)
                if HDG_VendorShoppingList and HDG_VendorShoppingList.IsShown and HDG_VendorShoppingList.IsShown() then
                    HDG_VendorShoppingList.Refresh()
                end
                HDG_AcqRows.UpdateShopListBadge()
            end
            return
        end
        if onSelect then onSelect(self.itemID, self.parentNpcID) end
    end)

    -- Tooltip (set once per cell allocation, not per-refresh)
    if not cell._handlersSet then
        cell:SetScript("OnEnter", function(self)
            self.hoverBg:Show()
            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(HDG.Theme.C_Success() .. "Collected|r", 0.2, 0.9, 0.2)
            else
                GameTooltip:AddLine(HDG.Theme.C_Dim() .. "Not collected|r", 0.78, 0.78, 0.78)
            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:AddLine(" ")
            GameTooltip:AddLine("Shift-Click to add to Shopping List", 0.78, 0.78, 0.78)
            GameTooltip:Show()
        end)
        cell:SetScript("OnLeave", function(self)
            self.hoverBg:Hide()
            GameTooltip:Hide()
        end)
        cell._handlersSet = true
    end

    cell:Show()
    return cell
end

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

function HDG_AcqRows.UpdateGalleryHighlights(selectedItemID)
    for _, cell in ipairs(galleryCellPool.active) do
        if not cell.inUse then break end
        HDG_UIHelpers.UpdateGalleryCellBackground(cell, cell.itemID == selectedItemID)
    end
end

function HDG_AcqRows.UpdateRowHighlights(selectedItemID, selectedNpcID)
    local scheme = GetScheme()
    if not scheme or not itemRowPool then return end
    for _, row in ipairs(itemRowPool:GetActive()) do
        if not row.inUse then break end
        if row.itemID == selectedItemID and (not selectedNpcID or row.parentNpcID == selectedNpcID) 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
