local addonName, privateDB = ...
local HDG = _G.HousingDecorGuide

-- ============================================================================
-- HDG StyleCardRow -- row-of-cards initializer for WowScrollBoxList
-- Delegates cell widget construction to HDG_UIHelpers.CreateGalleryCell so all
-- visual features (3D model preview, hoverBg, checkmark/redX, qty badge) are
-- preserved.  Recent-session overlays are layered on top.
--
-- CARDS_PER_ROW = 8.  One DataProvider row = one rowFrame = up to 8 cards.
-- ============================================================================

HDG.StyleCardRow = HDG.StyleCardRow or {}
local M = HDG.StyleCardRow

local CARDS_PER_ROW = 8
M.CARDS_PER_ROW = CARDS_PER_ROW

-- Default cfg -- callers override via M.Create(parent, cfg).
-- cellSize / statusIconSize / checkmarkTexture / sceneID mirror CONFIG.STYLES_TAB.
local DEFAULT_CFG = {
    cellSize         = 97,
    statusIconSize   = 12,
    checkmarkTexture = "Interface\\RAIDFRAME\\ReadyCheck-Ready",
    sceneID          = 691,
    -- onEnter / onLeave / onClick: function(cell[, button]) -- set by caller
}

-- BatchIntoRows splits a flat array of card data into row tables.
-- cellsPerRow defaults to CARDS_PER_ROW (8).
function M.BatchIntoRows(cards, cellsPerRow)
    cellsPerRow = cellsPerRow or CARDS_PER_ROW
    local rows = {}
    for i = 1, #cards, cellsPerRow do
        local row = { cards = {} }
        for j = i, math.min(i + cellsPerRow - 1, #cards) do
            table.insert(row.cards, cards[j])
        end
        table.insert(rows, row)
    end
    return rows
end

-- ConstructCard: acquires/creates one gallery cell via HDG_UIHelpers then adds
-- the Recent-specific overlay widgets on top.  Does NOT set scripts (BindCard does).
local function ConstructCard(pool, parent, cfg)
    local cellCfg = {
        statusIconSize   = cfg.statusIconSize,
        checkmarkTexture = cfg.checkmarkTexture,
    }
    local cell = HDG_UIHelpers.CreateGalleryCell(pool, parent, cfg.cellSize, cellCfg)

    -- Add Recent overlays if not already present (pool recycle: they survive)
    if not cell._recentOverlaysAdded then
        -- Top-right: placed/removed event type icons
        local placedOverlay = cell:CreateTexture(nil, "OVERLAY", nil, 3)
        placedOverlay:SetSize(14, 14)
        placedOverlay:SetPoint("TOPRIGHT", -2, -2)
        placedOverlay:Hide()
        cell.placedOverlay = placedOverlay

        local removedOverlay = cell:CreateTexture(nil, "OVERLAY", nil, 3)
        removedOverlay:SetSize(14, 14)
        removedOverlay:SetPoint("TOPRIGHT", -2, -16)
        removedOverlay:Hide()
        cell.removedOverlay = removedOverlay

        -- Bottom-left count badge for Recent (sits above the checkmark/redX row)
        local recentCount = cell:CreateFontString(nil, "OVERLAY", "NumberFontNormalSmall")
        recentCount:SetPoint("BOTTOMLEFT", 4, 16)
        recentCount:SetTextColor(1, 1, 1)
        recentCount:Hide()
        cell.recentCount = recentCount

        cell._recentOverlaysAdded = true
    end

    return cell
end

-- BindCard: populate one gallery cell with cardData.
-- cardData shape:
--   Standard:  { itemID, decorID, entry, _isSelected?, _badgeText?, _badgeColorR/G/B?,
--                _onEnter?, _onLeave?, _onClick? }
--   Recent:    { decorID, _recentEventType, _recentLiveCount?, _recentDeadCount?,
--                _recentEventCount?, _recentName?, _recentIcon?, _readOnly? }
local function BindCard(card, cardData, cfg)
    if not cardData then card:Hide(); return end
    card:Show()

    card.itemID    = cardData.itemID
    card.decorID   = cardData.decorID
    card.entry     = cardData.entry
    card._readOnly = cardData._readOnly

    -- Reset Recent overlays each bind
    card.placedOverlay:Hide()
    card.removedOverlay:Hide()
    card.recentCount:Hide()

    if cardData._recentEventType then
        -- ----------------------------------------------------------------
        -- Recent session card
        -- ----------------------------------------------------------------
        local et         = cardData._recentEventType
        local liveCount  = cardData._recentLiveCount  or 0
        local deadCount  = cardData._recentDeadCount  or 0
        local eventCount = cardData._recentEventCount or 0

        local isPlacedLive = (et == "placed" and liveCount > 0)
        card.checkmark:SetShown(isPlacedLive)
        card.redX:SetShown(not isPlacedLive)
        HDG_UIHelpers.UpdateGalleryCellBackground(card, false)
        card.infoText:Hide()

        -- Visuals via central catalog cache
        local decorInfo = HDG_Data and HDG_Data.GetCatalog and HDG_Data.GetCatalog(cardData.decorID)
        HDG_UIHelpers.UpdateGalleryCellVisuals(card, decorInfo, isPlacedLive, cfg.sceneID)

        -- Pre-resolved icon fileID override (Recent events store this)
        if cardData._recentIcon and card.fallbackIcon and not card.modelScene:IsShown() then
            card.fallbackIcon:SetTexture(cardData._recentIcon)
            card.fallbackIcon:SetDesaturated(not isPlacedLive)
            card.fallbackIcon:SetAlpha(isPlacedLive and 1 or 0.5)
            card.fallbackIcon:Show()
        end

        -- Event overlays
        if et == "placed" and liveCount > 0 then
            card.placedOverlay:SetAtlas("communities-chat-icon-plus"); card.placedOverlay:Show()
            card:SetAlpha(1.0)
            card.recentCount:SetText(liveCount > 1 and ("x" .. liveCount) or "")
            card.recentCount:SetShown(liveCount > 1)
        elseif et == "placed" and liveCount == 0 then
            card.placedOverlay:SetAtlas("communities-chat-icon-plus"); card.placedOverlay:Show()
            card.removedOverlay:SetAtlas("communities-chat-icon-minus")
            card.removedOverlay:ClearAllPoints()
            card.removedOverlay:SetPoint("TOPRIGHT", -2, -16)
            card.removedOverlay:Show()
            card:SetAlpha(0.7)
            card.recentCount:SetText(deadCount > 1 and ("x" .. deadCount) or "")
            card.recentCount:SetShown(deadCount > 1)
        elseif et == "removed" then
            card.removedOverlay:SetAtlas("communities-chat-icon-minus")
            card.removedOverlay:ClearAllPoints()
            card.removedOverlay:SetPoint("TOPRIGHT", -2, -2)
            card.removedOverlay:Show()
            card:SetAlpha(0.7)
            card.recentCount:SetText(eventCount > 1 and ("x" .. eventCount) or "")
            card.recentCount:SetShown(eventCount > 1)
        end

        -- Handlers
        local cd = cardData
        card:RegisterForClicks("LeftButtonUp", "RightButtonUp")
        card:SetScript("OnEnter", function(self2)
            self2.hoverBg:Show()
            GameTooltip:SetOwner(self2, "ANCHOR_RIGHT")
            GameTooltip:AddLine(cd._recentName or ("Decor #" .. (cd.decorID or "?")), 1, 1, 1)
            local lc = cd._recentLiveCount or 0
            local dc = cd._recentDeadCount or 0
            local ec = cd._recentEventCount or 0
            if cd._recentEventType == "placed" then
                if lc > 0 then
                    GameTooltip:AddLine("|cff33cc33+" .. lc .. " placed|r", 0.2, 0.9, 0.2)
                end
                if dc > 0 then
                    GameTooltip:AddLine("|cff888888" .. dc .. " since removed|r", 0.6, 0.6, 0.6)
                end
            else
                GameTooltip:AddLine("|cffff6666-" .. ec .. " removed|r", 0.9, 0.4, 0.4)
            end
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cffaaaaaa" .. "Open the house editor (Shift+Z) to place another copy." .. "|r", 0.67, 0.67, 0.67)
            GameTooltip:Show()
        end)
        card:SetScript("OnLeave", function(self2)
            self2.hoverBg:Hide()
            GameTooltip:Hide()
        end)
        card:SetScript("OnClick", function(self2)
            if self2._readOnly then
                print("|cFFFFD100[HDG]|r Open the house editor (Shift+Z) to place another copy.")
            end
        end)

    else
        -- ----------------------------------------------------------------
        -- Standard gallery card (style items, snapshot, shopping list)
        -- ----------------------------------------------------------------
        local isCollected = (HDG_Data and HDG_Data.IsItemCollected(cardData.itemID)) or false
        card.checkmark:SetShown(isCollected)
        card.redX:SetShown(not isCollected)
        card.isSelected = cardData._isSelected or false
        HDG_UIHelpers.UpdateGalleryCellBackground(card, card.isSelected)
        card:SetAlpha(1.0)

        local decorInfo = HDG_UIHelpers.UpdateGalleryCellQuantity(card, cardData.decorID)
        if not decorInfo and HDG_Data and HDG_Data.GetCatalog then
            decorInfo = HDG_Data.GetCatalog(cardData.decorID)
        end
        HDG_UIHelpers.UpdateGalleryCellVisuals(card, decorInfo, isCollected, cfg.sceneID)

        -- Optional pre-computed badge (snapshot placed count, shopping list qty)
        if cardData._badgeText then
            card.infoText:SetText(cardData._badgeText)
            card.infoText:SetTextColor(
                cardData._badgeColorR or 1,
                cardData._badgeColorG or 1,
                cardData._badgeColorB or 1)
            card.infoText:Show()
        end

        -- Scripts: per-item override > cfg default
        local onEnter = cardData._onEnter or cfg.onEnter
        local onLeave = cardData._onLeave or cfg.onLeave
        local onClick = cardData._onClick or cfg.onClick
        card:RegisterForClicks("LeftButtonUp", "RightButtonUp")
        if onEnter then card:SetScript("OnEnter", onEnter) end
        if onLeave then card:SetScript("OnLeave", onLeave) end
        if onClick then card:SetScript("OnClick", onClick)
        else             card:SetScript("OnClick", nil)     end
    end

    card._cardData = cardData
end

-- ============================================================================

-- Signature changed: scrollBox replaces (pool, cfg) -- reads stashed state.
function M.InitializeRow(rowFrame, rowData, scrollBox)
    local cellsPerRow     = scrollBox._cellsPerRow
    local cellConstructor = scrollBox._cellConstructor
    local cellBinder      = scrollBox._cellBinder
    local cfg             = scrollBox._cardGridCfg
    -- Construct card slots on first use (rowFrame is recycled by ScrollBox).
    -- Each rowFrame owns a private cell pool so virtualization can't steal cells
    -- across recycled rowFrames (a shared pool caused empty rows on rebind).
    if not rowFrame._cards then
        rowFrame._cards = {}
        rowFrame._cellPool = HDG_UIHelpers.CreateGalleryCellPool()
        for i = 1, cellsPerRow do
            local c = cellConstructor(rowFrame, rowFrame._cellPool)
            c:SetPoint("LEFT", (i - 1) * (cfg.cellSize + 4), 0)
            rowFrame._cards[i] = c
        end
    end
    -- Bind each slot; cellBinder hides trailing empty slots
    for i = 1, cellsPerRow do
        cellBinder(rowFrame._cards[i], rowData.cards[i], cfg)
    end
end

-- M.Create: build the WowScrollBoxList + MinimalScrollBar.
-- cfg: optional table overriding DEFAULT_CFG fields plus onEnter/onLeave/onClick.
-- cfg.cellsPerRow:     optional integer (default 8)
-- cfg.cellConstructor: optional function(rowFrame) -> frame; when absent the
--                      standard gallery-cell pool path is used unchanged.
-- cfg.cellBinder:      optional function(cell, itemData, cfg); when absent
--                      BindCard is used (gallery cells).
-- Returns scrollBox, scrollBar (caller sets anchor points on scrollBox).
function M.Create(parent, cfg)
    cfg = cfg or {}
    local resolvedCfg = {}
    for k, v in pairs(DEFAULT_CFG) do resolvedCfg[k] = v end
    for k, v in pairs(cfg) do
        if k ~= "cellConstructor" and k ~= "cellsPerRow" and k ~= "cellBinder" then
            resolvedCfg[k] = v
        end
    end

    local cellsPerRow     = cfg.cellsPerRow or CARDS_PER_ROW
    local cellConstructor = cfg.cellConstructor
    local cellBinder      = cfg.cellBinder or BindCard   -- default: gallery BindCard
    if not cellConstructor then
        -- Per-rowFrame pool is created in InitializeRow and passed in here.
        -- No shared pool: cells live with their owning rowFrame for life.
        cellConstructor = function(rowFrame, perRowPool)
            return ConstructCard(perRowPool, rowFrame, resolvedCfg)
        end
    end

    local scrollBox = CreateFrame("Frame", nil, parent, "WowScrollBoxList")

    local scrollBar = CreateFrame("EventFrame", nil, parent, "MinimalScrollBar")
    scrollBar:SetPoint("TOPLEFT", scrollBox, "TOPRIGHT", 2, 0)
    scrollBar:SetPoint("BOTTOMLEFT", scrollBox, "BOTTOMRIGHT", 2, 0)

    local rowSpacing = 4
    local view = CreateScrollBoxListLinearView(0, 0, 0, 0, rowSpacing)
    view:SetElementExtentCalculator(function() return resolvedCfg.cellSize + rowSpacing end)
    view:SetElementInitializer("Frame", function(rowFrame, rowData)
        M.InitializeRow(rowFrame, rowData, scrollBox)
    end)

    ScrollUtil.InitScrollBoxListWithScrollBar(scrollBox, scrollBar, view)
    -- Per-rowFrame pools (rowFrame._cellPool) replace the previous shared pool.
    scrollBox._cellConstructor = cellConstructor
    scrollBox._cellBinder      = cellBinder
    scrollBox._cellsPerRow     = cellsPerRow
    scrollBox._cardGridView    = view
    scrollBox._cardGridCfg     = resolvedCfg
    return scrollBox, scrollBar
end

-- M.SetItems: release pool (if any), batch items into rows, push to ScrollBox.
-- retainScroll (optional, default false): pass true to preserve the current
-- scroll position (e.g. live refresh on the same dataset). Default resets to
-- top, which is the correct behavior for tier/filter/style changes where
-- retained scroll can leave the user staring at an empty region.
function M.SetItems(scrollBox, displayItems, retainScroll)
    if not scrollBox then return end
    -- No pool release: cells are owned by their rowFrames for life. ScrollBox
    -- handles rowFrame recycling; BindCard fully resets visual state per slot.
    local rows = M.BatchIntoRows(displayItems or {}, scrollBox._cellsPerRow)
    local provider = CreateDataProvider(rows)
    -- Use the explicit Retain/Discard constants rather than relying on
    -- SetDataProvider's nil-default. When switching between styles (different
    -- item counts) the implicit "reset" sometimes leaves the view scrolled
    -- past the new content; DiscardScrollPosition is the canonical reset.
    local flag = retainScroll and ScrollBoxConstants.RetainScrollPosition
                              or  ScrollBoxConstants.DiscardScrollPosition
    scrollBox:SetDataProvider(provider, flag)
end

function M.Clear(scrollBox)
    if not scrollBox then return end
    scrollBox:SetDataProvider(CreateDataProvider({}))
end
