
local addonName, privateDB = ...

-- ============================================================================
-- UI HELPER FUNCTIONS MODULE
-- Centralized UI utilities for consistency and reduced code duplication
-- Added in v1.9.5 as part of Phase 1 refactoring
-- ============================================================================

local CONFIG = privateDB.CONFIG
local L = HDG_L or {}  -- Localization

-- Module namespace
local UIHelpers = {}
privateDB.UIHelpers = UIHelpers

-- ============================================================================
-- TEXT COLOR CONSTANTS
-- Reference centralized CONFIG.TEXT_COLORS (defined in HDG_Config.lua)
-- ============================================================================

-- Direct reference to CONFIG.TEXT_COLORS - single source of truth
UIHelpers.TEXT_COLORS = CONFIG.TEXT_COLORS

-- Shorthand for common color operations
function UIHelpers.ColorText(text, colorKey)
    local color = UIHelpers.TEXT_COLORS[colorKey] or UIHelpers.TEXT_COLORS.WHITE
    return color .. text .. UIHelpers.TEXT_COLORS.RESET
end

-- ============================================================================
-- BACKDROP PRESETS
-- Standard backdrop configurations for consistent panel styling
-- ============================================================================

-- ============================================================================
-- COLOR REFERENCES (from CONFIG.PALETTE - single source of truth)
-- These add alpha values to the base palette colors
-- ============================================================================

local PALETTE = CONFIG.PALETTE

-- Standard border colors: dim teal (modern Dragonflight-style)
UIHelpers.BORDER_COLOR = { PALETTE.TEAL_BORDER[1], PALETTE.TEAL_BORDER[2], PALETTE.TEAL_BORDER[3], 1 }
UIHelpers.BORDER_COLOR_DIM = { PALETTE.TEAL_BORDER[1], PALETTE.TEAL_BORDER[2], PALETTE.TEAL_BORDER[3], 0.7 }
UIHelpers.BORDER_COLOR_FAINT = { PALETTE.TEAL_BORDER[1], PALETTE.TEAL_BORDER[2], PALETTE.TEAL_BORDER[3], 0.6 }
UIHelpers.BORDER_COLOR_BRIGHT = { PALETTE.TEAL_BORDER_BRIGHT[1], PALETTE.TEAL_BORDER_BRIGHT[2], PALETTE.TEAL_BORDER_BRIGHT[3], 1 }

-- UI State colors: consistent active/inactive/hover states across all interactive elements
UIHelpers.STATE_ACTIVE = { PALETTE.TEAL_DARK[1], PALETTE.TEAL_DARK[2], PALETTE.TEAL_DARK[3], 1.0 }
UIHelpers.STATE_INACTIVE = { PALETTE.GRAY_INACTIVE[1], PALETTE.GRAY_INACTIVE[2], PALETTE.GRAY_INACTIVE[3], 1.0 }
UIHelpers.STATE_HOVER = { PALETTE.GRAY_HOVER[1], PALETTE.GRAY_HOVER[2], PALETTE.GRAY_HOVER[3], 1.0 }

-- Panel background colors
UIHelpers.BG_PANEL = { 0.05, 0.05, 0.05, 0.95 }       -- Main panel background
UIHelpers.BG_PANEL_DARK = { 0.08, 0.08, 0.1, 0.9 }    -- Slightly lighter panel bg
UIHelpers.BG_HEADER = { 0.1, 0.1, 0.1, 0.8 }          -- Section header background
UIHelpers.BG_TRANSPARENT = { 0, 0, 0, 0 }             -- Fully transparent

-- Button colors (non-state buttons like compact, pin)
UIHelpers.BTN_NORMAL = { PALETTE.SLATE_MED[1], PALETTE.SLATE_MED[2], 0.17, 0.9 }
UIHelpers.BTN_HOVER = { PALETTE.SLATE_HOVER[1], PALETTE.SLATE_HOVER[2], PALETTE.SLATE_HOVER[3], 1.0 }

-- Row alternating colors (for lists/tables)
UIHelpers.ROW_EVEN = { PALETTE.SLATE_DARK[1], PALETTE.SLATE_DARK[2], 0.15, 0.6 }
UIHelpers.ROW_ODD = { 0.08, 0.08, 0.10, 0.6 }
UIHelpers.ROW_HOVER = { 0.2, 0.2, 0.25, 0.8 }

-- Discord integration colors
UIHelpers.DISCORD_BLURPLE = { 0.34, 0.40, 0.95, 0.9 }
UIHelpers.DISCORD_BLURPLE_HOVER = { 0.44, 0.50, 1.0, 1.0 }

-- Tooltip default anchor (from config)
UIHelpers.TOOLTIP_ANCHOR = CONFIG.TOOLTIP.DEFAULT_ANCHOR

-- ============================================================================
-- DATA ACCESS HELPER
-- Nil-safe accessor for HDG_Data module (may not be loaded yet at file parse time)
-- ============================================================================

function UIHelpers.Data()
    return _G.HDG_Data or {}
end

UIHelpers.BACKDROPS = {
    -- Main panel backdrop (used for recipe, shopping, materials panels)
    -- Modern 1-pixel solid border with dim teal
    panel = {
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,  -- Solid 1px border
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    },

    -- Simple solid backdrop (no border)
    solid = {
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
    },

    -- Header backdrop
    header = {
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    },

    -- Button backdrop
    button = {
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
    },

    -- Lumber row backdrop
    lumber = {
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    },

    -- Main frame backdrop (with border)
    mainFrame = {
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,  -- Solid 1px border
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    },

    -- Compact panel backdrop
    compact = {
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,  -- Solid 1px border
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    },
}

-- ============================================================================
-- BACKDROP HELPER
-- Apply consistent backdrop styling to frames
-- ============================================================================

--- Apply a predefined backdrop to a frame
-- @param frame The frame to apply backdrop to
-- @param backdropType String key from BACKDROPS table (e.g., "panel", "solid", "header")
-- @param bgColor Optional table {r, g, b, a} for background color
-- @param borderColor Optional table {r, g, b, a} for border color (defaults to dim teal)
function UIHelpers.ApplyBackdrop(frame, backdropType, bgColor, borderColor)
    if not frame then return end

    local backdrop = UIHelpers.BACKDROPS[backdropType]
    if not backdrop then
        backdrop = UIHelpers.BACKDROPS.solid -- Fallback
    end

    frame:SetBackdrop(backdrop)

    if bgColor then
        frame:SetBackdropColor(unpack(bgColor))
    end

    -- Apply border color (default to dim teal if not specified)
    if frame.SetBackdropBorderColor then
        local border = borderColor or UIHelpers.BORDER_COLOR
        frame:SetBackdropBorderColor(unpack(border))
    end
end

-- ============================================================================
-- TOOLTIP HELPER
-- Simplified tooltip setup for frames
-- ============================================================================

--- Setup standard tooltip behavior for a frame
-- @param frame The frame to add tooltip to
-- @param title Tooltip title (will be colored gold)
-- @param description Optional description line(s) - can be string or table of strings
-- @param anchor Optional anchor point (default from CONFIG.TOOLTIP.DEFAULT_ANCHOR)
function UIHelpers.SetupTooltip(frame, title, description, anchor)
    if not frame then return end

    anchor = anchor or CONFIG.TOOLTIP.DEFAULT_ANCHOR

    frame:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, anchor)
        GameTooltip:AddLine(UIHelpers.TEXT_COLORS.GOLD .. title .. UIHelpers.TEXT_COLORS.RESET, 1, 1, 1)

        if description then
            if type(description) == "table" then
                for _, line in ipairs(description) do
                    GameTooltip:AddLine(line, 1, 1, 1)
                end
            else
                GameTooltip:AddLine(description, 1, 1, 1)
            end
        end

        GameTooltip:Show()
    end)

    frame:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)
end

--- Setup tooltip with item information (uses SetItemByID)
-- @param frame The frame to add tooltip to
-- @param itemID The item ID to display
-- @param extraLines Optional table of extra lines to add after item tooltip
-- @param anchor Optional anchor point
function UIHelpers.SetupItemTooltip(frame, itemID, extraLines, anchor)
    if not frame or not itemID then return end

    anchor = anchor or CONFIG.TOOLTIP.DEFAULT_ANCHOR

    frame:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, anchor)
        GameTooltip:SetItemByID(itemID)

        if extraLines then
            GameTooltip:AddLine(" ")
            for _, line in ipairs(extraLines) do
                if type(line) == "table" then
                    GameTooltip:AddLine(line.text, line.r or 1, line.g or 1, line.b or 1)
                else
                    GameTooltip:AddLine(line, 1, 1, 1)
                end
            end
        end

        GameTooltip:Show()
    end)

    frame:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)
end

-- ============================================================================
-- HOVER EFFECT HELPER
-- Consistent hover state changes for interactive elements
-- ============================================================================

--- Setup hover effect with color change
-- @param frame The frame to add hover effect to
-- @param normalColor Table {r, g, b, a} for normal state
-- @param hoverColor Table {r, g, b, a} for hover state
-- @param textElement Optional FontString to change text color on hover
-- @param normalTextColor Optional table {r, g, b} for normal text color
-- @param hoverTextColor Optional table {r, g, b} for hover text color
function UIHelpers.SetupHoverEffect(frame, normalColor, hoverColor, textElement, normalTextColor, hoverTextColor)
    if not frame then return end

    normalTextColor = normalTextColor or { 0.9, 0.9, 0.9 }
    hoverTextColor = hoverTextColor or { 1, 1, 1 }

    frame:SetScript("OnEnter", function(self)
        self:SetBackdropColor(unpack(hoverColor))
        if textElement then
            textElement:SetTextColor(unpack(hoverTextColor))
        end
    end)

    frame:SetScript("OnLeave", function(self)
        self:SetBackdropColor(unpack(normalColor))
        if textElement then
            textElement:SetTextColor(unpack(normalTextColor))
        end
    end)
end

--- Setup combined hover effect with tooltip
-- @param frame The frame
-- @param normalColor Normal backdrop color
-- @param hoverColor Hover backdrop color
-- @param tooltipTitle Tooltip title
-- @param tooltipDesc Optional tooltip description
-- @param anchor Optional tooltip anchor
function UIHelpers.SetupHoverWithTooltip(frame, normalColor, hoverColor, tooltipTitle, tooltipDesc, anchor)
    if not frame then return end

    anchor = anchor or CONFIG.TOOLTIP.DEFAULT_ANCHOR

    frame:SetScript("OnEnter", function(self)
        self:SetBackdropColor(unpack(hoverColor))

        GameTooltip:SetOwner(self, anchor)
        GameTooltip:AddLine(UIHelpers.TEXT_COLORS.GOLD .. tooltipTitle .. UIHelpers.TEXT_COLORS.RESET, 1, 1, 1)

        if tooltipDesc then
            if type(tooltipDesc) == "table" then
                for _, line in ipairs(tooltipDesc) do
                    GameTooltip:AddLine(line, 1, 1, 1)
                end
            else
                GameTooltip:AddLine(tooltipDesc, 1, 1, 1)
            end
        end

        GameTooltip:Show()
    end)

    frame:SetScript("OnLeave", function(self)
        self:SetBackdropColor(unpack(normalColor))
        GameTooltip:Hide()
    end)
end

-- ============================================================================
-- SECTION HEADER HELPER
-- Create consistent section headers
-- ============================================================================

--- Create a section header font string
-- @param parent Parent frame
-- @param text Header text
-- @param yOffset Y offset from top (default -8)
-- @param colorKey Optional color key from TEXT_COLORS (default "TITLE")
-- @return FontString object
function UIHelpers.CreateSectionHeader(parent, text, yOffset, colorKey)
    if not parent then return nil end

    yOffset = yOffset or -8
    colorKey = colorKey or "TITLE"

    local header = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    header:SetPoint("TOP", 0, yOffset)
    header:SetText(UIHelpers.TEXT_COLORS[colorKey] .. text .. UIHelpers.TEXT_COLORS.RESET)

    return header
end

--- Create a left-aligned header
-- @param parent Parent frame
-- @param text Header text
-- @param xOffset X offset from left
-- @param yOffset Y offset from top
-- @param colorKey Optional color key
-- @return FontString object
function UIHelpers.CreateLeftHeader(parent, text, xOffset, yOffset, colorKey)
    if not parent then return nil end

    colorKey = colorKey or "TITLE"

    local header = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    header:SetPoint("TOPLEFT", xOffset or 8, yOffset or -8)
    header:SetText(UIHelpers.TEXT_COLORS[colorKey] .. text .. UIHelpers.TEXT_COLORS.RESET)

    return header
end

-- ============================================================================
-- FILTER BUTTON FACTORY
-- Shared filter button creation for GoblinTab and DecorPreviewTab
-- ============================================================================

--- Create a styled filter button
-- @param parent Parent frame
-- @param text Button label text
-- @param width Button width
-- @param isActive Whether button is in active state
-- @param onClick Click handler function
-- @param options Optional table: { height = 16, inactiveTextColor = "B0B0B0" }
-- @return Button frame
function UIHelpers.CreateFilterButton(parent, text, width, isActive, onClick, options)
    options = options or {}
    local height = options.height or 16
    local inactiveTextColor = options.inactiveTextColor or "B0B0B0"

    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetHeight(height)
    btn:SetWidth(width)
    btn:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, edgeSize = 1,
        insets = {left = 0, right = 0, top = 0, bottom = 0}
    })

    if isActive then
        btn:SetBackdropColor(unpack(UIHelpers.STATE_ACTIVE))
        btn:SetBackdropBorderColor(unpack(UIHelpers.BORDER_COLOR_BRIGHT))
    else
        btn:SetBackdropColor(unpack(UIHelpers.STATE_INACTIVE))
        btn:SetBackdropBorderColor(unpack(UIHelpers.BORDER_COLOR_DIM))
    end

    local btnText = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    btnText:SetPoint("LEFT", 4, 0)
    btnText:SetPoint("RIGHT", -4, 0)
    btnText:SetJustifyH("CENTER")
    btnText:SetWordWrap(false)
    btnText:SetText(isActive and ("|cFFFFFFFF" .. text .. "|r") or ("|cFF" .. inactiveTextColor .. text .. "|r"))
    btn.text = btnText
    btn.label = text
    btn.isActive = isActive

    btn:SetScript("OnClick", onClick)
    btn:SetScript("OnEnter", function(self)
        if not self.isActive then
            self:SetBackdropColor(unpack(UIHelpers.STATE_HOVER))
        end
    end)
    btn:SetScript("OnLeave", function(self)
        if not self.isActive then
            self:SetBackdropColor(unpack(UIHelpers.STATE_INACTIVE))
        end
    end)

    return btn
end

-- ============================================================================
-- PANEL CREATION HELPER
-- Simplified panel creation with consistent styling
-- ============================================================================

--- Create a standard panel frame
-- @param parent Parent frame
-- @param config Table with: width, height, position (table), title (optional), scrollable (bool)
-- @return Frame object
function UIHelpers.CreatePanel(parent, config)
    if not parent or not config then return nil end

    local frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")

    -- Position
    if config.position then
        frame:SetPoint(unpack(config.position))
    end

    -- Size
    frame:SetSize(config.width or 200, config.height or 200)

    -- Backdrop
    UIHelpers.ApplyBackdrop(frame, "panel", CONFIG.COLORS.PANEL_BG, CONFIG.COLORS.PANEL_BORDER)

    -- Title
    if config.title then
        frame.title = UIHelpers.CreateSectionHeader(frame, config.title, -8)
    end

    return frame
end

-- ============================================================================
-- UTILITY FUNCTIONS
-- ============================================================================

--- Clear all children and regions from a scroll content frame
-- @param scrollContent The content frame to clear
function UIHelpers.ClearScrollContent(scrollContent)
    if not scrollContent then return end

    local children = { scrollContent:GetChildren() }
    local regions = { scrollContent:GetRegions() }

    for _, child in ipairs(children) do
        for _, grandchild in ipairs({ child:GetChildren() }) do
            grandchild:Hide()
            grandchild:ClearAllPoints()
            grandchild:SetParent(nil)
        end
        child:Hide()
        child:ClearAllPoints()
        child:SetParent(nil)
    end

    for _, region in ipairs(regions) do
        if region:GetObjectType() == "FontString" or region:GetObjectType() == "Texture" then
            region:Hide()
            region:SetParent(nil)
        end
    end
end

--- Format gold amount for display
-- @param copper Amount in copper
-- @param useTextures Whether to use coin textures (default true)
-- @return Formatted string
function UIHelpers.FormatGold(copper, useTextures)
    if not copper or copper == 0 then return "0g" end

    local gold = math.floor(copper / 10000)
    local silver = math.floor((copper % 10000) / 100)
    local copperRem = copper % 100

    if useTextures ~= false then
        return C_CurrencyInfo.GetCoinTextureString(copper)
    else
        if gold > 0 then
            return string.format("%dg %ds %dc", gold, silver, copperRem)
        elseif silver > 0 then
            return string.format("%ds %dc", silver, copperRem)
        else
            return string.format("%dc", copperRem)
        end
    end
end

--- Print addon message with consistent formatting
-- @param message The message to print
-- @param messageType Optional: "success", "error", "warning", "info" (default "info")
function UIHelpers.PrintMessage(message, messageType)
    local prefix = UIHelpers.TEXT_COLORS.GOLD .. "[Housing Decor Guide]" .. UIHelpers.TEXT_COLORS.RESET

    local coloredMessage = message
    if messageType == "success" then
        coloredMessage = UIHelpers.TEXT_COLORS.SUCCESS .. message .. UIHelpers.TEXT_COLORS.RESET
    elseif messageType == "error" then
        coloredMessage = UIHelpers.TEXT_COLORS.ERROR .. message .. UIHelpers.TEXT_COLORS.RESET
    elseif messageType == "warning" then
        coloredMessage = UIHelpers.TEXT_COLORS.WARNING .. message .. UIHelpers.TEXT_COLORS.RESET
    end

    print(prefix .. " " .. coloredMessage)
end

-- ============================================================================
-- MINIMALIST SCROLLBAR STYLING
-- Modern scrollbar appearance: hide arrows, slim thumb, subtle colors
-- ============================================================================

--- Style a scroll frame to have minimalist scrollbars
-- @param scrollFrame The scroll frame using UIPanelScrollFrameTemplate
-- @param thumbColor Optional thumb color table {r, g, b, a} (default: dim teal)
-- @param trackColor Optional track color table {r, g, b, a} (default: very dark)
function UIHelpers.StyleScrollBar(scrollFrame, thumbColor, trackColor)
    if not scrollFrame then return end

    -- Default colors
    thumbColor = thumbColor or { 0, 0.4, 0.4, 0.7 }  -- Semi-transparent teal
    trackColor = trackColor or { 0.1, 0.1, 0.1, 0.3 }  -- Very subtle dark track

    -- Get the scrollbar (named ScrollBar for UIPanelScrollFrameTemplate)
    local scrollBar = scrollFrame.ScrollBar or _G[scrollFrame:GetName() .. "ScrollBar"]
    if not scrollBar then return end

    -- Hide the up/down arrow buttons
    local scrollUpButton = scrollBar.ScrollUpButton or _G[scrollBar:GetName() .. "ScrollUpButton"]
    local scrollDownButton = scrollBar.ScrollDownButton or _G[scrollBar:GetName() .. "ScrollDownButton"]

    if scrollUpButton then
        scrollUpButton:SetAlpha(0)
        scrollUpButton:EnableMouse(false)
        scrollUpButton:SetSize(1, 1)
    end

    if scrollDownButton then
        scrollDownButton:SetAlpha(0)
        scrollDownButton:EnableMouse(false)
        scrollDownButton:SetSize(1, 1)
    end

    -- Style the thumb (the draggable part)
    local thumbTexture = scrollBar.ThumbTexture or scrollBar:GetThumbTexture()
    if thumbTexture then
        thumbTexture:SetTexture(CONFIG.TEXTURES.WHITE_BG)
        thumbTexture:SetVertexColor(unpack(thumbColor))
        thumbTexture:SetSize(6, 40)  -- Slim width, reasonable height
    end

    -- Make scrollbar narrower
    scrollBar:SetWidth(8)

    -- Create a subtle track background if it doesn't exist
    if not scrollBar.trackBg then
        local track = scrollBar:CreateTexture(nil, "BACKGROUND")
        track:SetTexture(CONFIG.TEXTURES.WHITE_BG)
        track:SetVertexColor(unpack(trackColor))
        track:SetAllPoints(scrollBar)
        scrollBar.trackBg = track
    end

    -- Add hover effect to thumb
    local originalThumbColor = { unpack(thumbColor) }
    local hoverThumbColor = { thumbColor[1] + 0.1, thumbColor[2] + 0.1, thumbColor[3] + 0.1, 0.9 }

    scrollBar:HookScript("OnEnter", function()
        if thumbTexture then
            thumbTexture:SetVertexColor(unpack(hoverThumbColor))
        end
    end)

    scrollBar:HookScript("OnLeave", function()
        if thumbTexture then
            thumbTexture:SetVertexColor(unpack(originalThumbColor))
        end
    end)
end

-- ============================================================================
-- LUMBER ROW HELPER
-- Shared function for creating lumber rows (used by embedded and pinned panels)
-- ============================================================================

-- Helper: Format elapsed time as mm:ss or hh:mm:ss
local function FormatElapsedTime(seconds)
    if not seconds or seconds < 0 then seconds = 0 end
    local hours = math.floor(seconds / 3600)
    local mins = math.floor((seconds % 3600) / 60)
    local secs = math.floor(seconds % 60)
    if hours > 0 then
        return string.format("%d:%02d:%02d", hours, mins, secs)
    else
        return string.format("%d:%02d", mins, secs)
    end
end

-- Helper: Get progress bar color based on percentage (teal gradient)
-- Exported via UIHelpers.GetProgressBarColor for use by other modules
local function GetProgressBarColor(percentage)
    local teal = CONFIG.PALETTE.TEAL_PROGRESS
    local tealFull = CONFIG.PALETTE.TEAL_PROGRESS_FULL

    if percentage >= 100 then
        return tealFull[1], tealFull[2], tealFull[3], 1.0
    elseif percentage >= 1 then
        local opacity = 0.3 + (percentage / 100) * 0.4
        return teal[1], teal[2], teal[3], opacity
    else
        return teal[1], teal[2], teal[3], 0.15
    end
end
UIHelpers.GetProgressBarColor = GetProgressBarColor

-- Helper: Create a progress bar for lumber display (StatusBar-based like v2.1.0)
local function CreateLumberProgressBar(parent, current, max, xOffset, yOffset, showText)
    if not parent then return nil end

    -- Handle case where max is 0 (no need) - still create the bar but empty
    local needed = max or 0
    local have = current or 0

    local bar = CreateFrame("StatusBar", nil, parent, "BackdropTemplate")
    bar:SetStatusBarTexture(CONFIG.TEXTURES.STATUSBAR)
    bar:SetMinMaxValues(0, math.max(1, needed))
    bar:SetValue(math.min(have, math.max(1, needed)))

    local percentage = needed > 0 and (have / needed * 100) or 0
    bar:SetStatusBarColor(GetProgressBarColor(percentage))

    -- Background
    local bg = bar:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints(bar)
    bg:SetTexture(CONFIG.TEXTURES.STATUSBAR)
    bg:SetVertexColor(unpack(CONFIG.COLORS.PROGRESS_BG))
    bar.bg = bg

    -- Overlay for visual polish
    local overlay = bar:CreateTexture(nil, "OVERLAY")
    overlay:SetAllPoints(bar)
    overlay:SetTexture(CONFIG.TEXTURES.STATUSBAR)
    overlay:SetVertexColor(0.06, 0.73, 0.51, 0.15)
    overlay:SetDrawLayer("OVERLAY", 1)
    bar.overlay = overlay

    if showText then
        local text = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        text:SetPoint("CENTER", 0, 0)
        text:SetJustifyH("CENTER")
        text:SetDrawLayer("OVERLAY", 2)
        text:SetFont(text:GetFont(), 10, "")

        if have >= needed and needed > 0 then
            local extra = have - needed
            text:SetText(extra > 0 and string.format("|cFFFFFFFF%d/%d+%d|r", needed, needed, extra) or
                string.format("|cFFFFFFFF%d/%d|r", needed, needed))
        else
            text:SetText(string.format("|cFFFFFFFF%d/%d|r", have, needed))
        end
        bar.text = text
    end

    return bar
end

--- Create a lumber row for tracking lumber needs
-- @param parent Parent frame
-- @param lumberId ItemID of the lumber
-- @param lumberInfo Table with name, expansion
-- @param shoppingData Optional table with have, needed for crafting queue
-- @param allData Optional table with have, needed for all uncollected recipes
-- @param yOffset Y offset for positioning
-- @param farmingOptions Optional table: { stats, isActive, isJustCollected, animationPhase, onEndSession }
-- @return nextYOffset
function UIHelpers.CreateLumberRow(parent, lumberId, lumberInfo, shoppingData, allData, yOffset, farmingOptions)
    if not parent or not lumberId or not lumberInfo then return yOffset end

    local isActiveFarming = farmingOptions and farmingOptions.isActive
    local isJustCollected = farmingOptions and farmingOptions.isJustCollected
    local animationPhase = farmingOptions and farmingOptions.animationPhase or 0
    local farmingStats = farmingOptions and farmingOptions.stats
    local onEndSession = farmingOptions and farmingOptions.onEndSession

    local rowHeight = isActiveFarming and 62 or 18
    local contentYOffset = isActiveFarming and 18 or 0

    local row = CreateFrame("Button", nil, parent, "BackdropTemplate")
    row:SetSize(CONFIG.LUMBER_COL_1 + CONFIG.LUMBER_COL_2 + CONFIG.LUMBER_COL_3 + 4, rowHeight)
    row:SetPoint("TOPLEFT", 0, yOffset)
    row:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })

    if isActiveFarming then
        row:SetBackdropColor(0.08, 0.15, 0.08, 0.95)
    else
        row:SetBackdropColor(unpack(UIHelpers.BG_TRANSPARENT))
    end

    -- Store data for tooltip
    row.lumberInfo = lumberInfo
    row.shoppingData = shoppingData
    row.allData = allData
    row.isActiveFarming = isActiveFarming

    -- Preload item data to ensure tooltip displays correctly on first hover
    C_Item.RequestLoadItemDataByID(lumberId)

    -- Tooltip on hover
    row:SetScript("OnEnter", function(self)
        if not self.isActiveFarming then
            self:SetBackdropColor(unpack(CONFIG.COLORS.LUMBER_HOVER))
        end

        GameTooltip:SetOwner(self, UIHelpers.TOOLTIP_ANCHOR)
        -- Use SetHyperlink instead of SetItemByID for more reliable tooltip display
        -- SetItemByID can clear custom lines when item data loads asynchronously
        local itemLink = select(2, C_Item.GetItemInfo(lumberId))
        if itemLink then
            GameTooltip:SetHyperlink(itemLink)
        else
            -- Fallback: use item name from lumberInfo if item not cached yet
            local itemName = self.lumberInfo.name or "Lumber"
            GameTooltip:SetText(itemName, 1, 1, 1)
        end
        GameTooltip:AddLine(" ")

        local expColor = HDG_ExpansionData.GetColor(self.lumberInfo.expansion)
        GameTooltip:AddDoubleLine("Expansion:", self.lumberInfo.expansion,
            1, 1, 1, expColor[1], expColor[2], expColor[3])
        GameTooltip:AddLine(" ")

        if self.shoppingData then
            GameTooltip:AddLine("|cFFfcd34dCrafting Queue:|r", 1, 1, 1)
            GameTooltip:AddDoubleLine("  Account Stock:",
                BreakUpLargeNumbers(self.shoppingData.have), 0.8, 0.8, 0.8, 1, 1, 1)
            -- Show per-character breakdown if available
            if self.shoppingData.breakdown and #self.shoppingData.breakdown > 0 then
                for _, charInfo in ipairs(self.shoppingData.breakdown) do
                    local prefix = charInfo.isCurrent and "    * " or "    "
                    GameTooltip:AddDoubleLine(prefix .. charInfo.name .. ":",
                        BreakUpLargeNumbers(charInfo.count), 0.6, 0.6, 0.6, 0.8, 0.8, 0.8)
                end
            end
            GameTooltip:AddDoubleLine("  Needed:",
                BreakUpLargeNumbers(self.shoppingData.needed), 0.8, 0.8, 0.8, 1, 1, 1)
            if self.shoppingData.have >= self.shoppingData.needed then
                GameTooltip:AddLine("  |cFF88FF88Have enough for crafting queue|r", 0.5, 1, 0.5)
            else
                local shortfall = self.shoppingData.needed - self.shoppingData.have
                GameTooltip:AddLine(string.format("  |cFFFF5555Need %s more|r",
                    BreakUpLargeNumbers(shortfall)), 1, 0.3, 0.3)
            end
        else
            GameTooltip:AddLine("|cFF888888Not needed for crafting queue|r", 0.6, 0.6, 0.6)
        end

        GameTooltip:AddLine(" ")

        if self.allData then
            if self.allData.needed > 0 then
                GameTooltip:AddLine("|cFFfcd34dUncollected crafted Decor needs:|r", 1, 1, 1)
                GameTooltip:AddDoubleLine("  Account Stock:",
                    BreakUpLargeNumbers(self.allData.have), 0.8, 0.8, 0.8, 1, 1, 1)
                -- Show per-character breakdown if available
                if self.allData.breakdown and #self.allData.breakdown > 0 then
                    for _, charInfo in ipairs(self.allData.breakdown) do
                        local prefix = charInfo.isCurrent and "    * " or "    "
                        GameTooltip:AddDoubleLine(prefix .. charInfo.name .. ":",
                            BreakUpLargeNumbers(charInfo.count), 0.6, 0.6, 0.6, 0.8, 0.8, 0.8)
                    end
                end
                GameTooltip:AddDoubleLine("  Total Needed:",
                    BreakUpLargeNumbers(self.allData.needed), 0.8, 0.8, 0.8, 1, 1, 1)
                if self.allData.have >= self.allData.needed then
                    GameTooltip:AddLine("  |cFF88FF88Have enough for all recipes|r", 0.5, 1, 0.5)
                else
                    local shortfall = self.allData.needed - self.allData.have
                    GameTooltip:AddLine(string.format("  |cFFFF5555Need %s more|r",
                        BreakUpLargeNumbers(shortfall)), 1, 0.3, 0.3)
                end
            else
                GameTooltip:AddLine("|cFF888888All recipes using this collected|r", 0.6, 0.6, 0.6)
            end
        else
            GameTooltip:AddLine("|cFF888888All recipes using this collected|r", 0.6, 0.6, 0.6)
        end

        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFFFFAA00Lumber must be farmed|r", 1, 0.8, 0)
        GameTooltip:Show()
    end)

    row:SetScript("OnLeave", function(self)
        if not self.isActiveFarming then
            self:SetBackdropColor(unpack(UIHelpers.BG_TRANSPARENT))
        end
        GameTooltip:Hide()
    end)

    -- Column 1: Lumber name
    local nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    nameText:SetPoint("LEFT", 0, contentYOffset)
    nameText:SetWidth(CONFIG.LUMBER_COL_1)
    nameText:SetJustifyH("LEFT")
    nameText:SetWordWrap(false)

    local expColor = HDG_ExpansionData.GetColor(lumberInfo.expansion)
    -- Get localized material name (strips "Lumber/Madeira/Holz/etc." from localized name)
    local displayName = HDG_ExpansionData.GetLumberMaterialName(lumberId) or lumberInfo.shortName or lumberInfo.name

    -- Animation effects for farming
    if isJustCollected and animationPhase == 1 then
        nameText:SetFont(nameText:GetFont(), 14, "")
        nameText:SetText(string.format("|cFFFFFFFF%s|r", displayName))
    elseif isJustCollected and animationPhase == 2 then
        nameText:SetFont(nameText:GetFont(), 12, "")
        nameText:SetText(string.format("|cFFFFD700%s|r", displayName))
    elseif isActiveFarming then
        nameText:SetFont(nameText:GetFont(), 11, "")
        nameText:SetText(string.format("|cFFfcd34d%s|r", displayName))
    else
        nameText:SetText(string.format("|cFF%02x%02x%02x%s|r",
            math.min(255, expColor[1] * 255 * 1.3),
            math.min(255, expColor[2] * 255 * 1.3),
            math.min(255, expColor[3] * 255 * 1.3),
            displayName))
    end

    -- Column 2: Shopping/Needed progress bar
    if shoppingData then
        local shoppingBar = CreateLumberProgressBar(row, shoppingData.have, shoppingData.needed, 0, 0, true)
        if shoppingBar then
            shoppingBar:SetSize(CONFIG.LUMBER_COL_2 - 4, 12)
            shoppingBar:ClearAllPoints()
            shoppingBar:SetPoint("LEFT", CONFIG.LUMBER_COL_1 + 2, contentYOffset)
            if shoppingBar.text then
                shoppingBar.text:SetWidth(76)
                shoppingBar.text:SetWordWrap(false)
            end
        end
    else
        local emptyText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        emptyText:SetPoint("LEFT", CONFIG.LUMBER_COL_1 + 2, contentYOffset)
        emptyText:SetWidth(CONFIG.LUMBER_COL_2)
        emptyText:SetJustifyH("CENTER")
        emptyText:SetText("-")
    end

    -- Column 3: Stock/needed or just stock count
    if allData and allData.needed > 0 then
        -- Show progress bar with have/needed when there are uncollected recipes
        local allBar = CreateLumberProgressBar(row, allData.have, allData.needed, 0, 0, true)
        if allBar then
            allBar:SetSize(CONFIG.LUMBER_COL_3 - 4, 12)
            allBar:ClearAllPoints()
            allBar:SetPoint("LEFT", CONFIG.LUMBER_COL_1 + CONFIG.LUMBER_COL_2 + 4, contentYOffset)
            if allBar.text then
                allBar.text:SetWidth(76)
                allBar.text:SetWordWrap(false)
            end
        end
    else
        -- No uncollected recipes need this lumber - show just stock count in teal
        local stockText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        stockText:SetPoint("LEFT", CONFIG.LUMBER_COL_1 + CONFIG.LUMBER_COL_2 + 4, contentYOffset)
        stockText:SetWidth(CONFIG.LUMBER_COL_3)
        stockText:SetJustifyH("CENTER")
        if allData then
            stockText:SetText(string.format("|cFF14b8a6%s|r", BreakUpLargeNumbers(allData.have)))
        else
            -- allData is nil (no uncollected recipes) - still show account-wide stock count
            local accountTotal = HDG_Data and HDG_Data.GetAccountWideLumberCount and HDG_Data.GetAccountWideLumberCount(lumberId) or 0
            stockText:SetText(string.format("|cFF14b8a6%s|r", BreakUpLargeNumbers(accountTotal)))
        end
    end

    -- Farming stats row (only when actively farming)
    if isActiveFarming and farmingStats then
        local statsText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        statsText:SetPoint("LEFT", 4, 0)
        statsText:SetPoint("RIGHT", -4, 0)
        statsText:SetJustifyH("LEFT")

        local rateStr = string.format("%.1f", farmingStats.perHour or 0)
        local timeStr = FormatElapsedTime(farmingStats.elapsed or 0)

        if isJustCollected and animationPhase == 1 then
            statsText:SetFont(statsText:GetFont(), 12, "")
            statsText:SetText(string.format("|cFFFFFFFF+%d this session|r  |cFFFFFFFFRate:|r |cFFFFFFFF%s/hr|r  |cFFcccccc(%s)|r",
                farmingStats.collected or 0, rateStr, timeStr))
        elseif isJustCollected and animationPhase == 2 then
            statsText:SetFont(statsText:GetFont(), 10, "")
            statsText:SetText(string.format("|cFFFFD700+%d this session|r  |cFFe2e8f0Rate:|r |cFFFFD700%s/hr|r  |cFF888888(%s)|r",
                farmingStats.collected or 0, rateStr, timeStr))
        else
            statsText:SetFont(statsText:GetFont(), 9, "")
            statsText:SetText(string.format("|cFF88FF88+%d this session|r  |cFFe2e8f0Rate:|r |cFFfcd34d%s/hr|r  |cFF888888(%s)|r",
                farmingStats.collected or 0, rateStr, timeStr))
        end

        -- End Session button
        local endBtn = CreateFrame("Button", nil, row, "BackdropTemplate")
        endBtn:SetSize(60, 14)
        endBtn:SetPoint("BOTTOMRIGHT", -4, 4)
        endBtn:SetBackdrop({
            bgFile = CONFIG.TEXTURES.WHITE_BG,
            edgeFile = CONFIG.TEXTURES.WHITE_BG,
            tile = false,
            edgeSize = 1,
            insets = { left = 0, right = 0, top = 0, bottom = 0 }
        })
        endBtn:SetBackdropColor(0.3, 0.15, 0.15, 0.8)
        endBtn:SetBackdropBorderColor(0.5, 0.3, 0.3, 0.6)

        local endBtnText = endBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        endBtnText:SetPoint("CENTER", 0, 0)
        endBtnText:SetFont(endBtnText:GetFont(), 8, "")
        endBtnText:SetText("|cFFFF8888End Session|r")

        endBtn:SetScript("OnEnter", function(self)
            self:SetBackdropColor(0.5, 0.2, 0.2, 1)
            GameTooltip:SetOwner(self, UIHelpers.TOOLTIP_ANCHOR)
            GameTooltip:SetText("End Farming Session", 1, 0.5, 0.5)
            GameTooltip:AddLine("Ends the current farming session tracking", 0.7, 0.7, 0.7)
            GameTooltip:Show()
        end)
        endBtn:SetScript("OnLeave", function(self)
            self:SetBackdropColor(0.3, 0.15, 0.15, 0.8)
            GameTooltip:Hide()
        end)
        endBtn:SetScript("OnClick", function()
            if onEndSession then
                onEndSession(lumberId)
            end
        end)

        -- Zone display
        local zoneText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        zoneText:SetPoint("BOTTOMLEFT", 4, 6)
        zoneText:SetPoint("RIGHT", endBtn, "LEFT", -4, 0)
        zoneText:SetJustifyH("LEFT")
        zoneText:SetFont(zoneText:GetFont(), 9, "")

        local zoneName = GetZoneText() or "Unknown"
        zoneText:SetText(string.format("|cFFe2e8f0Zone:|r |cFF87CEEB%s|r", zoneName))
    end

    return yOffset - rowHeight
end

-- ============================================================================
-- EXPORT TO GLOBAL
-- Make helpers accessible from other modules
-- ============================================================================

_G.HDG_UIHelpers = UIHelpers

return UIHelpers
