-- VDS_Rows_Warehouse
-- ============================================================================
-- Stock tab row factories. Three kinds share pooled Button frames:
--   warehouseDyeRow         -- per-dye have/target/need + +/- editor
--   warehouseGroupHeaderRow -- color-group section header with group editor
--   herbListItemRow         -- Craft-mode herb side panel entry
-- Pattern: lazy-build per kind + explicit cross-kind hide on every bind.

VDS = VDS or {}

local WHITE              = "Interface\\Buttons\\WHITE8x8"
local DYE_ROW_SWATCH_TEX = "Interface\\AddOns\\VamoosesDyeStudio\\textures\\vds_swatch_tex"

-- ============================================================================
-- warehouseDyeRow
-- ============================================================================

local function lazyBuildWarehouseDyeChrome(row)
    if row._vdsWarehouseDyeBuilt then return end
    row._vdsWarehouseDyeBuilt = true

    -- Full-row colored backdrop tinted to the dye's swatchRGB at bind time.
    -- Mirrors VDS_Rows_Dye chrome so the Stock list scans visually the same
    -- way as Colors -- you can find Brass / Bronze / Gold by colour.
    -- BACKGROUND so the existing 16x16 dye-item icon (ARTWORK) sits on top.
    local rowBg = row:CreateTexture(nil, "BACKGROUND", nil, 0)
    rowBg:SetAllPoints()
    rowBg:SetTexture(DYE_ROW_SWATCH_TEX)
    row._whRowBg = rowBg

    local rowGloss = row:CreateTexture(nil, "BACKGROUND", nil, 1)
    rowGloss:SetAllPoints()
    rowGloss:SetTexture(WHITE)
    rowGloss:SetBlendMode("ADD")
    rowGloss:SetGradient("VERTICAL",
        _G.CreateColor(1, 1, 1, 0), _G.CreateColor(1, 1, 1, 0.15))
    row._whRowGloss = rowGloss

    -- Swatch (left). Rebound at bind time to the dye item's colored-vial icon;
    -- falls back to a flat-color tint when itemID isn't yet loadable.
    local swatch = row:CreateTexture(nil, "ARTWORK")
    swatch:SetSize(16, 16)
    swatch:SetPoint("LEFT", 8, 0)
    swatch:SetTexture(WHITE)
    row._whSwatch = swatch

    -- Name + override badge
    local nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    nameText:SetPoint("LEFT", swatch, "RIGHT", 8, 0)
    nameText:SetJustifyH("LEFT")
    nameText:SetWordWrap(false)
    VDS.Theme:Register(nameText, "TextRowName")
    row._whName = nameText

    local overrideFlag = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    overrideFlag:SetPoint("LEFT", nameText, "RIGHT", 6, 0)
    overrideFlag:SetText("override")
    VDS.Theme:Register(overrideFlag, "TextOverrideBadge")
    overrideFlag:Hide()
    row._whOverrideFlag = overrideFlag

    -- Have column
    local haveText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    haveText:SetPoint("RIGHT", row, "RIGHT", -190, 0)
    haveText:SetWidth(40)
    haveText:SetJustifyH("RIGHT")
    row._whHave = haveText

    -- Loot-bag atlas: visual hint that Have includes bag + bank + warband.
    -- Alternative atlases tested:
    --   "QuestSharing-QuestLog-Details-ModifiersLootIconOff" -- greyed variant
    local bankerIcon = row:CreateTexture(nil, "ARTWORK")
    bankerIcon:SetSize(14, 14)
    bankerIcon:SetPoint("LEFT", haveText, "RIGHT", 2, 0)
    bankerIcon:SetAtlas("QuestSharing-QuestLog-Loot")
    row._whBanker = bankerIcon

    -- Reset (transmog-revert atlas), shown only when override is set.
    local resetBtn = CreateFrame("Button", nil, row)
    resetBtn:SetSize(16, 16)
    resetBtn:SetPoint("RIGHT", row, "RIGHT", -150, 0)
    local resetIcon = resetBtn:CreateTexture(nil, "ARTWORK")
    resetIcon:SetAllPoints()
    resetIcon:SetAtlas(VDS.Constants.ATLAS.RESET_REVERT)
    resetIcon:SetVertexColor(0.85, 0.85, 0.85, 1)
    resetBtn:SetScript("OnEnter", function() resetIcon:SetVertexColor(1, 1, 1, 1) end)
    resetBtn:SetScript("OnLeave", function() resetIcon:SetVertexColor(0.85, 0.85, 0.85, 1) end)
    resetBtn:Hide()
    row._whResetBtn = resetBtn

    -- Target editor: [-] N [+]. Atlas-icon parity with the header all-groups
    -- buttons; faux-state hover (desat/alpha) matches the iconButton kind.
    local minus = CreateFrame("Button", nil, row)
    minus:SetSize(18, 20)
    minus:SetPoint("RIGHT", row, "RIGHT", -120, 0)
    local minusIcon = minus:CreateTexture(nil, "ARTWORK")
    minusIcon:SetAllPoints()
    minusIcon:SetAtlas("communities-chat-icon-minus")
    minusIcon:SetDesaturated(true); minusIcon:SetAlpha(0.85)
    minus:SetScript("OnEnter", function() minusIcon:SetDesaturated(false); minusIcon:SetAlpha(1.0) end)
    minus:SetScript("OnLeave", function() minusIcon:SetDesaturated(true);  minusIcon:SetAlpha(0.85) end)
    row._whMinus = minus

    local targetVal = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    targetVal:SetPoint("LEFT", minus, "RIGHT", 2, 0)
    targetVal:SetWidth(26); targetVal:SetJustifyH("CENTER")
    VDS.Theme:Register(targetVal, "TextStockTarget")
    row._whTarget = targetVal

    local plus = CreateFrame("Button", nil, row)
    plus:SetSize(18, 20)
    plus:SetPoint("LEFT", targetVal, "RIGHT", 2, 0)
    local plusIcon = plus:CreateTexture(nil, "ARTWORK")
    plusIcon:SetAllPoints()
    plusIcon:SetAtlas("communities-chat-icon-plus")
    plusIcon:SetDesaturated(true); plusIcon:SetAlpha(0.85)
    plus:SetScript("OnEnter", function() plusIcon:SetDesaturated(false); plusIcon:SetAlpha(1.0) end)
    plus:SetScript("OnLeave", function() plusIcon:SetDesaturated(true);  plusIcon:SetAlpha(0.85) end)
    row._whPlus = plus

    -- Need column: number when short, common-icon-checkmark when at/over target.
    -- Number and check share the RIGHT -8 anchor; swapped (not stacked).
    local needText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    needText:SetPoint("RIGHT", row, "RIGHT", -8, 0)
    needText:SetWidth(50); needText:SetJustifyH("RIGHT")
    row._whNeed = needText

    local check = row:CreateTexture(nil, "OVERLAY")
    check:SetSize(16, 16)
    check:SetPoint("RIGHT", row, "RIGHT", -8, 0)
    check:SetAtlas("common-icon-checkmark")
    check:Hide()
    row._whCheck = check

    -- Click handlers (idempotent re-bind guard).
    if not row._whHandlersWired then
        row._whHandlersWired = true
        minus:SetScript("OnClick",    function() VDS.Controller_Warehouse:BumpDye(row, -1) end)
        plus:SetScript("OnClick",     function() VDS.Controller_Warehouse:BumpDye(row,  1) end)
        resetBtn:SetScript("OnClick", function() VDS.Controller_Warehouse:ResetDye(row)    end)
    end

    -- Hover tooltip via centralized recipe (VDS.TooltipRecipes.WarehouseDyeRow).
    -- Recipe gates on _lsRowKind to avoid firing on pool-rebind to header rows.
    VDS.TooltipEngine:Attach(row, VDS.TooltipRecipes.WarehouseDyeRow)
end

-- Pool sharing in VDS (each scrollBox owns one Button pool; binds need only
-- hide siblings within their own pool, not addon-wide):
--   stock.list        -- warehouseDyeRow <-> warehouseGroupHeaderRow
--   stock.herbs.list  -- herbListItemRow (sole kind)
--   browse.scrollbox  -- dye/herb (Rows_Dye / Rows_Herb own these)
local function hideGroupHeadChrome(row)
    if row._ghRowBg    then row._ghRowBg:Hide() end
    if row._ghChev     then row._ghChev:Hide() end
    if row._ghName     then row._ghName:Hide() end
    if row._ghBarTrack then row._ghBarTrack:Hide() end
    if row._ghBarFill  then row._ghBarFill:Hide() end
    if row._ghMinus    then row._ghMinus:Hide() end
    if row._ghPlus     then row._ghPlus:Hide() end
    if row._ghTarget   then row._ghTarget:Hide() end
    if row._ghCounts   then row._ghCounts:Hide() end
    if row._ghDivider  then row._ghDivider:Hide() end
end

local function showWarehouseDyeChrome(row)
    if row._whRowBg    then row._whRowBg:Show() end
    if row._whRowGloss then row._whRowGloss:Show() end
    if row._whSwatch   then row._whSwatch:Show() end
    if row._whName     then row._whName:Show() end
    if row._whHave     then row._whHave:Show() end
    if row._whBanker   then row._whBanker:Show() end
    if row._whMinus    then row._whMinus:Show() end
    if row._whPlus     then row._whPlus:Show() end
    if row._whTarget   then row._whTarget:Show() end
    if row._whNeed     then row._whNeed:Show() end
end
local function hideWarehouseDyeChrome(row)
    if row._whRowBg        then row._whRowBg:Hide() end
    if row._whRowGloss     then row._whRowGloss:Hide() end
    if row._whSwatch       then row._whSwatch:Hide() end
    if row._whName         then row._whName:Hide() end
    if row._whOverrideFlag then row._whOverrideFlag:Hide() end
    if row._whHave         then row._whHave:Hide() end
    if row._whBanker       then row._whBanker:Hide() end
    if row._whResetBtn     then row._whResetBtn:Hide() end
    if row._whMinus        then row._whMinus:Hide() end
    if row._whPlus         then row._whPlus:Hide() end
    if row._whCheck        then row._whCheck:Hide() end
    if row._whTarget       then row._whTarget:Hide() end
    if row._whNeed         then row._whNeed:Hide() end
end

local function bindWarehouseDyeRow(row, data)
    lazyBuildWarehouseDyeChrome(row)
    hideGroupHeadChrome(row)  -- within-pool sibling in stock.list
    showWarehouseDyeChrome(row)

    row._lsRowKind = "warehouseRow"
    row._dyeID = data.dyeID
    row._group = data.group
    -- ADR-041 ed-projection: handlers (+/- OnClick, tooltip) read row._* instead
    -- of diving into Store. Selector emits target/have/need/hasOverride.
    row._target       = data.target
    row._have         = data.have
    row._need         = data.need
    row._hasOverride  = data.hasOverride
    row._groupDefault = data.groupDefault

    -- Full-row colored backdrop. data.color = info.swatchRGB from the selector;
    -- fall back to dim grey when missing (matches dye-row "no color" tint).
    local rc = data.color
    if rc then
        row._whRowBg:SetVertexColor(rc.r, rc.g, rc.b, 1)
    else
        row._whRowBg:SetVertexColor(0.3, 0.3, 0.3, 1)
    end

    -- In-game dye-item icon (colored vial). C_Item.GetItemIconByID is sync --
    -- icon lives in client ItemTexture-N.blp. Flat tint fallback if no itemID.
    local icon = data.itemID and _G.C_Item.GetItemIconByID(data.itemID)
    if icon then
        row._whSwatch:SetTexture(icon)
        row._whSwatch:SetVertexColor(1, 1, 1, 1)
    else
        row._whSwatch:SetTexture(WHITE)
        local c = data.color or { r = 0.5, g = 0.5, b = 0.5 }
        row._whSwatch:SetVertexColor(c.r, c.g, c.b, 1)
    end

    row._whName:SetText(data.name or ("Dye " .. row._dyeID))
    row._whOverrideFlag:SetShown(data.hasOverride and true or false)
    row._whHave:SetText(tostring(data.have))
    row._whTarget:SetText(tostring(data.target))
    row._whResetBtn:SetShown(data.hasOverride and true or false)

    -- need > 0: number in TextStockNeed theme; need == 0: green check.
    if data.need > 0 then
        row._whNeed:SetText(tostring(data.need))
        VDS.Theme:Apply(row._whNeed, "TextStockNeed")
        row._whNeed:Show()
        row._whCheck:Hide()
    else
        row._whNeed:Hide()
        row._whCheck:Show()
    end
    row:SetAlpha(1.0)
end

local function resetWarehouseDyeRow(row)
    row._dyeID = nil
    row._group = nil
    if row._whName then row._whName:SetText("") end
end

VDS.RowRegistry:Register("warehouseDyeRow", {
    init  = bindWarehouseDyeRow,
    reset = resetWarehouseDyeRow,
})

-- ============================================================================
-- warehouseGroupHeaderRow
-- ============================================================================

local function lazyBuildGroupHeaderChrome(row)
    if row._vdsGroupHeadBuilt then return end
    row._vdsGroupHeadBuilt = true

    -- Neutral-grey row backdrop, same vds_swatch_tex the dye rows use but with
    -- a desaturated tint so the header reads as a "band" without competing with
    -- the colour group's own RGB (which already paints the chev/name + progress
    -- bar fill). Created BEFORE barTrack so the bar draws on top within the
    -- same BACKGROUND/0 sublayer (WoW draws same-layer textures in creation order).
    local rowBg = row:CreateTexture(nil, "BACKGROUND", nil, 0)
    rowBg:SetAllPoints()
    rowBg:SetTexture(DYE_ROW_SWATCH_TEX)
    rowBg:SetVertexColor(0.18, 0.18, 0.20, 1)
    row._ghRowBg = rowBg

    -- Collapse/expand chevron (Soulbinds TREE_HEADER atlas).
    local chev = row:CreateTexture(nil, "ARTWORK")
    chev:SetSize(16, 16)
    chev:SetPoint("LEFT", row, "LEFT", 4, 0)
    row._ghChev = chev

    local name = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    name:SetPoint("LEFT", 22, 0)
    name:SetWidth(70); name:SetJustifyH("LEFT")
    row._ghName = name

    local barTrack = row:CreateTexture(nil, "BACKGROUND")
    barTrack:SetHeight(3)
    barTrack:SetPoint("LEFT", name, "RIGHT", 8, 0)
    barTrack:SetPoint("RIGHT", row, "RIGHT", -210, 0)
    barTrack:SetTexture(WHITE)
    barTrack:SetVertexColor(1, 1, 1, 0.07)
    row._ghBarTrack = barTrack

    local barFill = row:CreateTexture(nil, "ARTWORK")
    barFill:SetHeight(3)
    barFill:SetPoint("LEFT", barTrack, "LEFT", 0, 0)
    barFill:SetTexture(WHITE)
    row._ghBarFill = barFill

    local minus = CreateFrame("Button", nil, row)
    minus:SetSize(18, 18)
    minus:SetPoint("RIGHT", row, "RIGHT", -160, 0)
    local minusIcon = minus:CreateTexture(nil, "ARTWORK")
    minusIcon:SetAllPoints()
    minusIcon:SetAtlas("communities-chat-icon-minus")
    minusIcon:SetDesaturated(true); minusIcon:SetAlpha(0.85)
    minus:SetScript("OnEnter", function() minusIcon:SetDesaturated(false); minusIcon:SetAlpha(1.0) end)
    minus:SetScript("OnLeave", function() minusIcon:SetDesaturated(true);  minusIcon:SetAlpha(0.85) end)
    row._ghMinus = minus

    local targetVal = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    targetVal:SetPoint("LEFT", minus, "RIGHT", 0, 0)
    targetVal:SetWidth(26); targetVal:SetJustifyH("CENTER")
    VDS.Theme:Register(targetVal, "TextStockTarget")
    row._ghTarget = targetVal

    local plus = CreateFrame("Button", nil, row)
    plus:SetSize(18, 18)
    plus:SetPoint("LEFT", targetVal, "RIGHT", 0, 0)
    local plusIcon = plus:CreateTexture(nil, "ARTWORK")
    plusIcon:SetAllPoints()
    plusIcon:SetAtlas("communities-chat-icon-plus")
    plusIcon:SetDesaturated(true); plusIcon:SetAlpha(0.85)
    plus:SetScript("OnEnter", function() plusIcon:SetDesaturated(false); plusIcon:SetAlpha(1.0) end)
    plus:SetScript("OnLeave", function() plusIcon:SetDesaturated(true);  plusIcon:SetAlpha(0.85) end)
    row._ghPlus = plus

    local counts = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    counts:SetPoint("RIGHT", row, "RIGHT", -8, 0)
    counts:SetWidth(110); counts:SetJustifyH("RIGHT")
    VDS.Theme:Register(counts, "TextRowMeta")
    row._ghCounts = counts

    -- Thin divider under the group header.
    local divider = row:CreateTexture(nil, "ARTWORK")
    divider:SetHeight(1)
    divider:SetPoint("BOTTOMLEFT",  row, "BOTTOMLEFT",  4, 0)
    divider:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -4, 0)
    divider:SetTexture(WHITE)
    divider:SetVertexColor(1, 1, 1, 0.10)
    row._ghDivider = divider

    if not row._ghHandlersWired then
        row._ghHandlersWired = true
        minus:SetScript("OnClick", function() VDS.Controller_Warehouse:BumpGroup(row, -1) end)
        plus:SetScript("OnClick",  function() VDS.Controller_Warehouse:BumpGroup(row,  1) end)
    end

    -- Row click toggles collapse. WoW doesn't bubble, so +/- child OnClicks
    -- consume their own events; this only fires for clicks NOT on a child.
    row:RegisterForClicks("LeftButtonUp")
    if not row._ghRowClickWired then
        row._ghRowClickWired = true
        row:SetScript("OnClick", function(self)
            if self._group then VDS.Controller_Warehouse:ToggleGroup(self._group) end
        end)
    end
end

local function showGroupHeadChrome(row)
    if row._ghRowBg    then row._ghRowBg:Show() end
    if row._ghChev     then row._ghChev:Show() end
    if row._ghName     then row._ghName:Show() end
    if row._ghBarTrack then row._ghBarTrack:Show() end
    if row._ghBarFill  then row._ghBarFill:Show() end
    if row._ghMinus    then row._ghMinus:Show() end
    if row._ghPlus     then row._ghPlus:Show() end
    if row._ghTarget   then row._ghTarget:Show() end
    if row._ghCounts   then row._ghCounts:Show() end
    if row._ghDivider  then row._ghDivider:Show() end
end

local function bindGroupHeaderRow(row, data)
    lazyBuildGroupHeaderChrome(row)
    hideWarehouseDyeChrome(row)  -- within-pool sibling in stock.list
    showGroupHeadChrome(row)

    row._lsRowKind = "groupHead"
    row._group = data.group
    -- ADR-041 ed-projection: BumpGroup's OnClick reads row._targetValue.
    row._targetValue = data.targetValue

    row._ghChev:SetAtlas(data.collapsed
        and VDS.Constants.ATLAS.TREE_HEADER_EXPAND
        or  VDS.Constants.ATLAS.TREE_HEADER_COLLAPSE)

    local label = (data.group or ""):upper()
    row._ghName:SetText(label)
    local rgb = VDS.StaticData.COLOR_GROUP_RGB[data.group]
    if rgb then
        -- Bar fill keeps the raw group RGB -- it draws on a translucent white
        -- track, so even dark colours read.
        row._ghBarFill:SetVertexColor(rgb.r, rgb.g, rgb.b, 0.9)
        -- Text needs a luminance floor: COLOR_GROUP_RGB.Black is (0.18) which
        -- matches the new dark row backdrop exactly -- text disappears. Scale
        -- the channels up (preserving hue) so the brightest channel is at
        -- least 0.5 for legibility against the 0.18-grey backdrop.
        local r, g, b = rgb.r, rgb.g, rgb.b
        local mx = math.max(r, g, b)
        if mx > 0 and mx < 0.4 then
            local scale = 0.4 / mx
            r, g, b = r * scale, g * scale, b * scale
        end
        row._ghName:SetTextColor(r, g, b, 1)
    end

    row._ghTarget:SetText(tostring(data.targetValue))
    row._ghCounts:SetText(string.format("%d/%d - need %d",
        data.have, data.target, data.need))

    local pct
    if data.target > 0 then pct = math.min(100, (data.have / data.target) * 100) else pct = 0 end
    local trackW = row._ghBarTrack:GetWidth()
    if trackW and trackW > 0 then
        row._ghBarFill:SetWidth(math.max(1, trackW * pct / 100))
    end
end

local function resetGroupHeaderRow(row)
    row._group = nil
    if row._ghName then row._ghName:SetText("") end
end

VDS.RowRegistry:Register("warehouseGroupHeaderRow", {
    init  = bindGroupHeaderRow,
    reset = resetGroupHeaderRow,
})

-- ============================================================================
-- herbListItemRow
-- ============================================================================

local function lazyBuildHerbListItemChrome(row)
    if row._vdsHerbListItemBuilt then return end
    row._vdsHerbListItemBuilt = true

    local name = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    name:SetPoint("TOPLEFT", 8, -4)
    name:SetWidth(120); name:SetJustifyH("LEFT")
    VDS.Theme:Register(name, "TextRowName")
    row._hlName = name

    local qty = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    qty:SetPoint("TOPLEFT", name, "TOPRIGHT", 4, 0)
    qty:SetWidth(40); qty:SetJustifyH("RIGHT")
    row._hlQty = qty

    local total = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    total:SetPoint("TOPRIGHT", row, "TOPRIGHT", -8, -4)
    total:SetJustifyH("RIGHT")
    VDS.Theme:Register(total, "TextStockCost")
    row._hlTotal = total

    local unit = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    unit:SetPoint("BOTTOMLEFT", 8, 4)
    VDS.Theme:Register(unit, "TextRowMeta")
    row._hlUnit = unit

    VDS.TooltipEngine:Attach(row, VDS.TooltipRecipes.HerbListRow)
end

local function showHerbListChrome(row)
    if row._hlName  then row._hlName:Show() end
    if row._hlQty   then row._hlQty:Show() end
    if row._hlTotal then row._hlTotal:Show() end
    if row._hlUnit  then row._hlUnit:Show() end
end

local function bindHerbListItemRow(row, data)
    lazyBuildHerbListItemChrome(row)
    -- stock.herbs.list is sole-kind; no cross-kind hide needed.
    showHerbListChrome(row)

    row._lsRowKind = "herbListItem"
    row._itemID = data.itemID

    -- Async name kick lives in widget config (resolveAsync); BindingEngine
    -- middleware handles the Request + dedup. Row factory just paints.
    row._hlName:SetText(data.name or RETRIEVING_DATA)
    row._hlQty:SetText("x" .. tostring(data.qty))
    -- lineCost + unitPrice are COPPER (Auctionator's unit); SD.FormatGold renders.
    row._hlTotal:SetText(VDS.StaticData.FormatGold(data.lineCost))

    -- Subline: unit price + ownership context. Owned + gross are surfaced
    -- always (not just when buyGap=on) so the per-row gap reads cleanly:
    --   "@ 2g each * own 331 of 1360 needed"
    -- When the player owns zero of this herb, drop the suffix so cold rows
    -- stay terse.
    local unitText = "@ " .. VDS.StaticData.FormatGold(data.unitPrice) .. " each"
    if data.ownedQty and data.ownedQty > 0 and data.grossQty then
        unitText = unitText .. " - own " .. data.ownedQty
                            .. " of " .. data.grossQty .. " needed"
    end
    row._hlUnit:SetText(unitText)
end

local function resetHerbListItemRow(row)
    row._itemID = nil
    if row._hlName then row._hlName:SetText("") end
end

VDS.RowRegistry:Register("herbListItemRow", {
    init  = bindHerbListItemRow,
    reset = resetHerbListItemRow,
})
