--

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

HDG_LumberTracker = {}

-- Local references
local mainFrame
local lumberFrame
local lumberContent
local pinnedFrame  -- Independent frame parented to UIParent (persists when main window closes)

-- Session-only farming stats (not saved, resets on logout/reload)
local sessionFarmingStats = {}  -- [lumberId] = { startCount, startTime, collected, zone, sessionId, lastCollectTime }
local previousLumberCounts = {}  -- Track previous counts to detect increases
local activeFarmingLumber = nil  -- Currently highlighted lumber ID
local justCollectedLumber = nil  -- Animation: lumber that was just collected (triggers pop animation)
local animationPhase = 0         -- Animation phase: 0=none, 1=grow, 2=shrink
local animationTimer = nil       -- Timer handle for clearing animation state

-- Track non-farming lumber sources (mail, bank, merchant, trade) to prevent false sessions
local isMailOpen = false
local isBankOpen = false
local isMerchantOpen = false
local isTradeOpen = false
local isSessionInitialized = false  -- Prevents false collection detection during login

-- ============================================================================
-- HELPER FUNCTIONS
-- ============================================================================

-- Use centralized Data() accessor from UIHelpers
local function Data()
    return HDG_UIHelpers.Data()
end

-- Reference centralized lumber data
local LUMBER_TYPES = HDG_ExpansionData.LUMBER_TYPES

-- ============================================================================
-- SESSION FARMING TRACKING
-- ============================================================================

-- Initialize previous lumber counts (call on addon load)
local function InitializeLumberCounts()
    for lumberId, _ in pairs(LUMBER_TYPES) do
        previousLumberCounts[lumberId] = Data().GetItemCount(lumberId) or 0
    end
end

-- Clear all session state (called on init)
local function ClearAllSessionState()
    activeFarmingLumber = nil
    justCollectedLumber = nil
    animationPhase = 0
    if animationTimer then
        animationTimer:Cancel()
        animationTimer = nil
    end
end

-- Save lumber gathering coordinates for later analysis (heatmaps, route optimization, etc.)
local function SaveLumberCoordinates(lumberId, quantity)
    -- Initialize storage if needed
    if not HDG_Settings.lumberCoordinates then
        HDG_Settings.lumberCoordinates = {}
    end

    -- Get current position
    local mapID = C_Map.GetBestMapForUnit("player")
    if not mapID then return end

    local position = C_Map.GetPlayerMapPosition(mapID, "player")
    if not position then return end

    local x, y = position:GetXY()
    if not x or not y then return end

    -- Get zone info
    local zone = GetZoneText() or "Unknown"
    local subzone = GetSubZoneText() or ""

    -- Create coordinate entry
    local entry = {
        lumberId = lumberId,
        quantity = quantity or 1,
        mapID = mapID,
        x = math.floor(x * 10000) / 10000,  -- 4 decimal places (0.01% precision)
        y = math.floor(y * 10000) / 10000,
        zone = zone,
        subzone = subzone,
        timestamp = time(),
        character = UnitName("player"),
        realm = GetRealmName()
    }

    table.insert(HDG_Settings.lumberCoordinates, entry)

    -- Notify LumberHUD of new gathering location (auto-show compass)
    if privateDB.HDG_LumberHUD and privateDB.HDG_LumberHUD.OnLocationAdded then
        privateDB.HDG_LumberHUD.OnLocationAdded(x, y, mapID)
    end
end

-- Save or update a farming session to history (upsert)
local function SaveFarmingSession(lumberId)
    local stats = sessionFarmingStats[lumberId]
    if not stats or stats.collected == 0 then return end

    local lumberInfo = LUMBER_TYPES[lumberId]
    if not lumberInfo then return end

    local now = GetTime()
    local duration = now - stats.startTime
    local perHour = duration > 0 and (stats.collected / duration) * 3600 or 0

    -- Get HDG_DataTab to save/update the session
    local HDG_DataTab = privateDB and privateDB.HDG_DataTab
    if HDG_DataTab and HDG_DataTab.SaveFarmingSession then
        HDG_DataTab.SaveFarmingSession({
            sessionId = stats.sessionId,
            lumberId = lumberId,
            lumberName = HDG_ExpansionData.GetLumberMaterialName(lumberId) or lumberInfo.shortName or lumberInfo.name,
            collected = stats.collected,
            duration = duration,
            perHour = perHour,
            zone = stats.zone or "Unknown",
            timestamp = time(),
            character = UnitName("player"),
            realm = GetRealmName()
        })
    end
end

-- Check for lumber collection increases and update farming stats
-- Sessions only start fresh on reload/login - zone changes don't split sessions
local function CheckLumberChanges()
    -- Skip if session tracking hasn't been initialized yet (prevents false detection on login)
    if not isSessionInitialized then
        return nil
    end

    -- Skip farming tracking if lumber came from mail, bank, merchant, or trade
    if isMailOpen or isBankOpen or isMerchantOpen or isTradeOpen then
        -- Still update previous counts so we don't trigger on close
        for lumberId, _ in pairs(LUMBER_TYPES) do
            previousLumberCounts[lumberId] = Data().GetItemCount(lumberId) or 0
        end
        return nil
    end

    local now = GetTime()
    local changedLumber = nil
    local currentZone = GetZoneText() or "Unknown"

    for lumberId, _ in pairs(LUMBER_TYPES) do
        local currentCount = Data().GetItemCount(lumberId) or 0
        local previousCount = previousLumberCounts[lumberId] or 0

        if currentCount > previousCount then
            local increase = currentCount - previousCount
            changedLumber = lumberId

            -- Initialize session if this is first collection of this lumber type this session
            if not sessionFarmingStats[lumberId] then
                sessionFarmingStats[lumberId] = {
                    startCount = previousCount,
                    startTime = now,
                    collected = 0,
                    zone = currentZone,  -- Record zone where farming started
                    sessionId = lumberId .. "_" .. time(),
                    lastCollectTime = now
                }
            end

            -- Update stats (including zone - always track current location)
            sessionFarmingStats[lumberId].collected = sessionFarmingStats[lumberId].collected + increase
            sessionFarmingStats[lumberId].lastCollectTime = now
            sessionFarmingStats[lumberId].zone = currentZone  -- Update zone to current location

            -- Save/update the session to history immediately
            SaveFarmingSession(lumberId)

            -- Save coordinates for later analysis (heatmaps, route optimization)
            SaveLumberCoordinates(lumberId, increase)

            -- Focus mode / auto-show only activates if the setting is enabled
            -- When disabled, lumber tracking still happens but no focus mode visual changes
            local autoShowEnabled = (HDG_Settings.lumberAutoShow == true)
            if autoShowEnabled then
                -- Auto-show pinned lumber tracker if not already visible
                if not HDG_LumberTracker.IsPinnedVisible() then
                    HDG_LumberTracker.ShowPinned()
                end
                -- Set as active farming lumber (controls focus mode highlighting)
                activeFarmingLumber = lumberId
            end

            -- Vamoose quotes - random quotes while farming (0 = off)
            -- Check both master toggle and lumber quotes toggle
            local quotesEnabled = HDG_Settings.quotesEnabled
            if quotesEnabled == nil then quotesEnabled = true end
            local lumberQuotesEnabled = HDG_Settings.lumberQuotesEnabled
            if lumberQuotesEnabled == nil then lumberQuotesEnabled = true end
            local chattiness = HDG_Settings.vamooseChattiness or 30
            if quotesEnabled and lumberQuotesEnabled and chattiness > 0 and privateDB.HDG_Vamoose then
                local quote, speaker = privateDB.HDG_Vamoose.OnLumberCollected(lumberId, increase, chattiness)
                if quote then
                    local onlyChat = HDG_Settings.quotesOnlyChat or false
                    -- Show talking head unless "only chat" is enabled
                    if not onlyChat then
                        privateDB.HDG_Vamoose.DisplayQuote(quote, speaker)
                    end
                    -- Print to chat if "only chat" is enabled (always print when chat-only mode)
                    if onlyChat then
                        local speakerName, speakerColor
                        if speaker == "GOBLIN" then
                            speakerName = "Gazlowe"
                            speakerColor = "FFD100"  -- Gold for goblin
                        elseif speaker == "SQUIRREL" then
                            speakerName = "Squirrel"
                            speakerColor = "A0522D"  -- Brown for squirrel
                        else
                            speakerName = "Vamoose"
                            speakerColor = "0070DE"  -- Shaman blue
                        end
                        print(string.format("|cFF%s[%s]|r %s", speakerColor, speakerName, quote))
                    end
                end
            end

            -- Trigger collection animation (3-phase: grow -> hold -> shrink)
            justCollectedLumber = lumberId
            animationPhase = 1  -- Start with grow phase
            if animationTimer then
                animationTimer:Cancel()
            end
            -- Refresh immediately to show phase 1 (large)
            if HDG_LumberTracker.IsPinnedVisible() then
                HDG_LumberTracker.RefreshPinned()
            end
            -- Phase 2: Hold at large size briefly
            animationTimer = C_Timer.NewTimer(0.2, function()
                animationPhase = 2  -- Shrink phase
                if HDG_LumberTracker.IsPinnedVisible() then
                    HDG_LumberTracker.RefreshPinned()
                end
                -- Phase 3: Return to normal
                animationTimer = C_Timer.NewTimer(0.15, function()
                    justCollectedLumber = nil
                    animationPhase = 0
                    if HDG_LumberTracker.IsPinnedVisible() then
                        HDG_LumberTracker.RefreshPinned()
                    end
                end)
            end)
        end

        -- Update previous count
        previousLumberCounts[lumberId] = currentCount
    end

    return changedLumber
end

-- Get farming stats for a lumber type
local function GetFarmingStats(lumberId)
    local stats = sessionFarmingStats[lumberId]
    if not stats or stats.collected == 0 then
        return nil
    end

    local now = GetTime()
    local elapsed = now - stats.startTime
    local perHour = 0

    if elapsed > 0 then
        perHour = (stats.collected / elapsed) * 3600  -- Convert to per hour
    end

    return {
        collected = stats.collected,
        elapsed = elapsed,
        perHour = perHour
    }
end

-- Format elapsed time nicely
local function FormatElapsedTime(seconds)
    if seconds < 60 then
        return string.format("%ds", math.floor(seconds))
    elseif seconds < 3600 then
        return string.format("%dm %ds", math.floor(seconds / 60), math.floor(seconds % 60))
    else
        return string.format("%dh %dm", math.floor(seconds / 3600), math.floor((seconds % 3600) / 60))
    end
end

-- ============================================================================
-- LUMBER CALCULATION FUNCTIONS
-- These are the primary implementations; HDG_Data delegates to these
-- ============================================================================

-- Calculate lumber needs from all uncollected recipes
-- Uses account-wide lumber counts (summed across all characters)
function HDG_LumberTracker.CalculateLumberNeeds(professions)
    local lumberNeeds = {}

    for _, profData in ipairs(professions) do
        for _, recipe in ipairs(profData.items) do
            if not Data().IsItemCollected(recipe.id) and recipe.reagents then
                for reagentKey, reagent in pairs(recipe.reagents) do
                    local reagentID = reagent.id or reagentKey
                    if LUMBER_TYPES[reagentID] then
                        if not lumberNeeds[reagentID] then
                            -- Use account-wide lumber count instead of just current character
                            local accountTotal, breakdown = Data().GetAccountWideLumberCount(reagentID)
                            lumberNeeds[reagentID] = {
                                id = reagentID,
                                name = LUMBER_TYPES[reagentID].name,
                                expansion = LUMBER_TYPES[reagentID].expansion,
                                needed = 0,
                                have = accountTotal,
                                breakdown = breakdown  -- Per-character breakdown for tooltip
                            }
                        end
                        lumberNeeds[reagentID].needed = lumberNeeds[reagentID].needed +
                            (reagent.amount or reagent.qty or 1)
                    end
                end
            end
        end
    end

    return lumberNeeds
end

-- Calculate lumber needs from shopping list only
-- Uses account-wide lumber counts (summed across all characters)
function HDG_LumberTracker.CalculateShoppingListLumber(shoppingList)
    local lumberNeeds = {}

    for i, item in ipairs(shoppingList) do
        if item.reagents then
            for reagentKey, reagent in pairs(item.reagents) do
                local reagentID = reagent.id or reagentKey
                if LUMBER_TYPES[reagentID] then
                    if not lumberNeeds[reagentID] then
                        -- Use account-wide lumber count instead of just current character
                        local accountTotal, breakdown = Data().GetAccountWideLumberCount(reagentID)
                        lumberNeeds[reagentID] = {
                            id = reagentID,
                            name = LUMBER_TYPES[reagentID].name,
                            needed = 0,
                            have = accountTotal,
                            breakdown = breakdown,  -- Per-character breakdown for tooltip
                            sources = {}
                        }
                    end

                    local reagentAmount = reagent.amount or reagent.qty or reagent.count or 1
                    lumberNeeds[reagentID].needed = lumberNeeds[reagentID].needed + (reagentAmount * item.quantity)
                    lumberNeeds[reagentID].sources[i] = true
                end
            end
        end
    end

    return lumberNeeds
end

-- Use centralized GetProgressBarColor from UIHelpers
local function GetProgressBarColor(percentage)
    return HDG_UIHelpers.GetProgressBarColor(percentage)
end

local function CreateProgressBar(parent, have, needed, xPos, yPos, showText)
    if not CONFIG.PROGRESS_BAR.SHOW_BARS then return nil end

    local bar = CreateFrame("StatusBar", nil, parent, "BackdropTemplate")
    bar:SetSize(CONFIG.PROGRESS_BAR.WIDTH, CONFIG.PROGRESS_BAR.HEIGHT)
    bar:SetPoint("RIGHT", xPos or -60, yPos or 0)
    bar:SetStatusBarTexture(CONFIG.TEXTURES.STATUSBAR)
    bar:SetMinMaxValues(0, needed)
    bar:SetValue(math.min(have, needed))

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

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

    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)

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

        if have >= needed 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))
            text:SetFont(text:GetFont(), 10, "")
        else
            text:SetText(string.format("|cFFFFFFFF%d/%d|r", have, needed))
            text:SetFont(text:GetFont(), 10, "")
        end
        bar.text = text
    end

    return bar
end

-- Helper function to format elapsed time
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

-- Handle end session callback for farming tracking
local function HandleEndSession(lumberId)
    local stats = sessionFarmingStats[lumberId]
    if stats and stats.collected > 0 then
        local duration = (stats.lastCollectTime or GetTime()) - stats.startTime
        local perHour = duration > 0 and (stats.collected / duration) * 3600 or 0
        local lumberInfo = LUMBER_TYPES[lumberId]
        local lumberName = HDG_ExpansionData.GetLumberMaterialName(lumberId) or (lumberInfo and lumberInfo.shortName) or (lumberInfo and lumberInfo.name) or "Lumber"
        local expColor = HDG_ExpansionData.GetColor(lumberInfo and lumberInfo.expansion)
        local colorHex = string.format("%02x%02x%02x",
            math.min(255, expColor[1] * 255 * 1.3),
            math.min(255, expColor[2] * 255 * 1.3),
            math.min(255, expColor[3] * 255 * 1.3))
        print(string.format("|cFFfcd34d[Housing Decor Guide]|r |cFF%s%s|r farming session ended: |cFF88FF88%d|r collected in %s |cFF888888(first to last)|r (|cFFfcd34d%.1f/hr|r)",
            colorHex, lumberName, stats.collected, FormatElapsedTime(duration), perHour))
    end
    -- Clear session stats and all session state (including timeout timer)
    sessionFarmingStats[lumberId] = nil
    ClearAllSessionState()
    -- Hide the lumber radar when session ends
    local HDG_LumberHUD = privateDB and privateDB.HDG_LumberHUD
    if HDG_LumberHUD and HDG_LumberHUD.Hide then
        HDG_LumberHUD.Hide()
    end
    -- Refresh displays
    if HDG_LumberTracker.IsPinnedVisible() then
        HDG_LumberTracker.RefreshPinned()
    end
    local HDG_DataTab = privateDB and privateDB.HDG_DataTab
    if HDG_DataTab and HDG_DataTab.RefreshFarmingDisplay then
        HDG_DataTab.RefreshFarmingDisplay()
    end
end

-- Helper: Create a lumber row using shared UIHelpers function
-- Returns: nextYOffset (accounts for expanded row height if farming active)
local function CreateLumberRow(parent, lumberId, lumberInfo, shoppingData, allData, yOffset)
    local isActiveFarming = (activeFarmingLumber == lumberId)
    local isJustCollected = (justCollectedLumber == lumberId)
    local farmingStats = GetFarmingStats(lumberId)

    -- Build farming options for the shared helper
    local farmingOptions = nil
    if isActiveFarming or isJustCollected then
        farmingOptions = {
            isActive = isActiveFarming,
            isJustCollected = isJustCollected,
            animationPhase = animationPhase,
            stats = farmingStats and {
                collected = farmingStats.collected,
                perHour = farmingStats.perHour,
                elapsed = farmingStats.elapsed
            },
            onEndSession = HandleEndSession
        }
    end

    return HDG_UIHelpers.CreateLumberRow(parent, lumberId, lumberInfo, shoppingData, allData, yOffset, farmingOptions)
end

-- ============================================================================
-- INITIALIZATION
-- ============================================================================

function HDG_LumberTracker.Init(frame)
    mainFrame = frame
end

-- Initialize lumber tracking on addon load (called from main entry point)
function HDG_LumberTracker.InitializeSessionTracking()
    -- Clear any stale session state from previous session
    -- This ensures fresh start on login/reload
    ClearAllSessionState()
    sessionFarmingStats = {}
    isSessionInitialized = false  -- Block CheckLumberChanges until counts are initialized

    -- Initialize lumber counts for session tracking
    -- Delay to ensure bags/bank data is fully loaded (matches SaveCharacterLumberInventory timing)
    -- This prevents false "collection" detection on first login when bags load
    C_Timer.After(3.5, function()
        InitializeLumberCounts()
        isSessionInitialized = true  -- Now safe to detect lumber changes
    end)
end

-- Save all active farming sessions (called on logout/reload)
function HDG_LumberTracker.SaveAllSessions()
    for lumberId, stats in pairs(sessionFarmingStats) do
        if stats and stats.collected > 0 then
            SaveFarmingSession(lumberId)
        end
    end
end

-- Check for lumber changes and return true if any lumber was collected
-- Called from QueueInventoryUpdate in HousingDecorGuide.lua
function HDG_LumberTracker.CheckForLumberChanges()
    return CheckLumberChanges()
end

-- Set mail open state (called from main event handler)
function HDG_LumberTracker.SetMailOpen(open)
    isMailOpen = open
end

-- Set bank open state (called from main event handler)
function HDG_LumberTracker.SetBankOpen(open)
    isBankOpen = open
end

-- Set merchant open state (called from main event handler)
function HDG_LumberTracker.SetMerchantOpen(open)
    isMerchantOpen = open
end

-- Set trade open state (called from main event handler)
function HDG_LumberTracker.SetTradeOpen(open)
    isTradeOpen = open
end

-- Get active farming lumber (for debug)
function HDG_LumberTracker.GetActiveFarmingLumber()
    return activeFarmingLumber
end

-- Get session farming stats (for debug)
function HDG_LumberTracker.GetSessionStats()
    -- Calculate perHour for each active session
    local result = {}
    local now = GetTime()
    for lumberId, stats in pairs(sessionFarmingStats) do
        if stats then
            local elapsed = now - (stats.startTime or now)
            local perHour = 0
            if elapsed > 0 and stats.collected > 0 then
                perHour = (stats.collected / elapsed) * 3600
            end
            result[lumberId] = {
                collected = stats.collected or 0,
                perHour = perHour,
                zone = stats.zone,
                startTime = stats.startTime,
            }
        end
    end
    return result
end

-- Get previous lumber counts (for debug)
function HDG_LumberTracker.GetPreviousCounts()
    return previousLumberCounts
end

-- ============================================================================
-- PINNED LUMBER WIDGET (Independent frame parented to UIParent)
-- Note: Embedded lumber frame is created in HDG_RecipesTab.lua (CreateShoppingPanel)
-- ============================================================================

local PINNED_WIDTH = 280
local PINNED_ROW_HEIGHT = 18
local PINNED_HEADER_HEIGHT = 34
local PINNED_FOOTER_PADDING = 8
local PINNED_PANEL_PADDING = 12  -- Padding inside lumber panel (top 4 + bottom 8)
local PINNED_MATERIALS_HEIGHT = 372  -- Fixed height for materials section when expanded

-- Calculate lumber panel height dynamically based on number of lumber types
local function GetLumberCount()
    return #HDG_ExpansionData.LUMBER_ORDER
end

local function GetLumberContentHeight()
    return GetLumberCount() * PINNED_ROW_HEIGHT
end

local function GetLumberPanelHeight()
    return GetLumberContentHeight() + PINNED_PANEL_PADDING
end

local function GetCompactHeight()
    return PINNED_HEADER_HEIGHT + GetLumberPanelHeight() + PINNED_FOOTER_PADDING
end

local function GetExpandedHeight()
    return GetCompactHeight() + PINNED_MATERIALS_HEIGHT
end

-- Helper to create standard button
local function CreateHeaderButton(parent, size, tooltip)
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(size, size)
    btn:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    btn:SetBackdropColor(unpack(HDG_UIHelpers.BTN_NORMAL))
    btn:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))

    btn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(unpack(HDG_UIHelpers.BTN_HOVER))
        if tooltip then
            GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
            GameTooltip:SetText(tooltip, 1, 1, 1)
            GameTooltip:Show()
        end
    end)
    btn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(unpack(HDG_UIHelpers.BTN_NORMAL))
        GameTooltip:Hide()
    end)

    return btn
end

-- Save pinned frame position
local function SavePinnedPosition()
    if not pinnedFrame then return end
    local point, _, relativePoint, xOfs, yOfs = pinnedFrame:GetPoint()
    HDG_Settings.pinnedLumberPosition = { point, relativePoint, xOfs, yOfs }
end

-- Force recreate the pinned frame (destroys existing and creates new)
function HDG_LumberTracker.ResetPinnedFrame()
    if pinnedFrame then
        SavePinnedPosition()
        pinnedFrame:Hide()
        pinnedFrame:SetParent(nil)
        pinnedFrame = nil
    end
    return HDG_LumberTracker.CreatePinnedFrame()
end

-- Create the independent pinned lumber frame
function HDG_LumberTracker.CreatePinnedFrame()
    if pinnedFrame then return pinnedFrame end

    -- Create frame parented to UIParent (independent of main addon window)
    pinnedFrame = CreateFrame("Frame", "HDG_PinnedLumberFrame", UIParent, "BackdropTemplate")
    pinnedFrame:SetSize(PINNED_WIDTH, GetCompactHeight())
    pinnedFrame:SetFrameStrata("DIALOG")  -- Show above main window and its close button
    pinnedFrame:SetFrameLevel(100)
    pinnedFrame:SetClampedToScreen(true)
    pinnedFrame:SetMovable(true)
    pinnedFrame:EnableMouse(true)
    pinnedFrame:RegisterForDrag("LeftButton")

    -- Restore position or default to center-right
    local pos = HDG_Settings.pinnedLumberPosition
    if pos and pos[1] and pos[3] and pos[4] then
        pinnedFrame:SetPoint(pos[1], UIParent, pos[2] or pos[1], pos[3], pos[4])
    else
        pinnedFrame:SetPoint("RIGHT", UIParent, "RIGHT", -50, 0)
    end

    pinnedFrame:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    pinnedFrame:SetBackdropColor(unpack(CONFIG.COLORS.MAIN_BG))
    pinnedFrame:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))

    -- Dragging
    pinnedFrame:SetScript("OnDragStart", function(self) self:StartMoving() end)
    pinnedFrame:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        SavePinnedPosition()
    end)

    -- Title texture (replaces icon + text)
    local titleTexture = pinnedFrame:CreateTexture(nil, "ARTWORK")
    titleTexture:SetSize(160, 35)
    titleTexture:SetPoint("LEFT", 4, 0)
    titleTexture:SetPoint("TOP", 0, 0)
    titleTexture:SetTexture(CONFIG.ADDON_TEXTURES.TRACKER_TITLE)
    pinnedFrame.titleTexture = titleTexture

    -- Unpin button (pin icon) - Unpins and restores embedded lumber frame
    local unpinBtn = CreateHeaderButton(pinnedFrame, 24, "Unpin (return to embedded)")
    unpinBtn:SetPoint("TOPRIGHT", -6, -6)
    local unpinIcon = unpinBtn:CreateTexture(nil, "ARTWORK")
    unpinIcon:SetSize(16, 16)
    unpinIcon:SetPoint("CENTER", 0, 0)
    unpinIcon:SetTexture(CONFIG.ADDON_TEXTURES.PIN)
    unpinIcon:SetDesaturated(true)
    unpinIcon:SetVertexColor(0.7, 0.7, 0.7, 0.8)  -- Desaturated when pinned (click to unpin)
    unpinBtn.icon = unpinIcon
    unpinBtn:SetScript("OnClick", function()
        HDG_Settings.lumberPinned = false
        HDG_LumberTracker.HidePinned()
        -- Restore embedded lumber frame in shopping panel
        if HDG_UI and HDG_UI.UpdateLumberLayout then
            HDG_UI.UpdateLumberLayout()
        end
        -- Update config tab checkbox to reflect change
        if HDG_ConfigTab and HDG_ConfigTab.UpdateLumberPinCheckbox then
            HDG_ConfigTab.UpdateLumberPinCheckbox()
        end
        print("|cFFfcd34d[Housing Decor Guide]|r Lumber tracker unpinned")
    end)
    pinnedFrame.unpinBtn = unpinBtn

    -- Expand button (+/-) - toggles main window
    local expandBtn = CreateHeaderButton(pinnedFrame, 24)  -- No static tooltip
    expandBtn:SetPoint("TOPRIGHT", unpinBtn, "TOPLEFT", -4, 0)
    local expandText = expandBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    expandText:SetPoint("CENTER", 0, 0)
    expandText:SetText("|cFFe2e8f0+|r")
    expandBtn.text = expandText

    -- Helper to check if main window is visible
    local function IsMainWindowVisible()
        return mainFrame and mainFrame:IsShown() or (HDG_UI and HDG_UI.IsVisible and HDG_UI.IsVisible())
    end

    -- Update button appearance based on main window state
    local function UpdateExpandButton()
        if IsMainWindowVisible() then
            expandText:SetText("|cFFe2e8f0-|r")
        else
            expandText:SetText("|cFFe2e8f0+|r")
        end
    end
    expandBtn.UpdateState = UpdateExpandButton

    -- Dynamic tooltip
    local function ShowExpandTooltip(btn)
        GameTooltip:SetOwner(btn, HDG_UIHelpers.TOOLTIP_ANCHOR)
        if IsMainWindowVisible() then
            GameTooltip:SetText(L["TIP_CLOSE_MAIN_WINDOW"] or "Close Main Window", 1, 1, 1)
        else
            GameTooltip:SetText(L["TIP_OPEN_MAIN_WINDOW"] or "Open Main Window", 1, 1, 1)
        end
        GameTooltip:Show()
    end

    expandBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(unpack(HDG_UIHelpers.BTN_HOVER))
        ShowExpandTooltip(self)
    end)
    expandBtn:SetScript("OnClick", function(self)
        -- Use HDG_UI.Toggle which handles window creation if needed
        if HDG_UI and HDG_UI.Toggle then
            HDG_UI.Toggle()
        end
        -- Update button state after toggle
        C_Timer.After(0.05, function()
            UpdateExpandButton()
            -- Update tooltip if still hovering
            if GameTooltip:IsOwned(self) then
                ShowExpandTooltip(self)
            end
        end)
    end)
    pinnedFrame.expandBtn = expandBtn

    -- Materials toggle button
    local materialsBtn = CreateHeaderButton(pinnedFrame, 24)
    materialsBtn:SetPoint("TOPRIGHT", expandBtn, "TOPLEFT", -4, 0)
    local materialsIcon = materialsBtn:CreateTexture(nil, "ARTWORK")
    materialsIcon:SetSize(14, 14)
    materialsIcon:SetPoint("CENTER", 0, 0)
    materialsIcon:SetTexture("Interface\\MINIMAP\\TRACKING\\Banker")  -- Bag-like icon
    materialsBtn.icon = materialsIcon

    local function UpdateMaterialsIcon()
        if HDG_Settings.showLumberMaterials then
            materialsIcon:SetDesaturated(false)
            materialsIcon:SetVertexColor(1, 1, 1, 1)
        else
            materialsIcon:SetDesaturated(true)
            materialsIcon:SetVertexColor(0.6, 0.6, 0.6, 0.7)
        end
    end
    materialsBtn.UpdateIcon = UpdateMaterialsIcon

    local function ShowMaterialsTooltip(btn)
        GameTooltip:SetOwner(btn, HDG_UIHelpers.TOOLTIP_ANCHOR)
        -- Check if currently farming (materials hidden during farming)
        if activeFarmingLumber then
            GameTooltip:SetText(L["TIP_MATERIALS_UNAVAILABLE"] or "Materials Unavailable", 0.6, 0.6, 0.6)
            GameTooltip:AddLine("Materials panel is hidden while farming lumber", 1, 0.5, 0.5)
        elseif HDG_Settings.showLumberMaterials then
            GameTooltip:SetText(L["TIP_HIDE_MATERIALS"] or "Hide Materials", 1, 1, 1)
            GameTooltip:AddLine("Click to hide required materials for crafting queue", 0.7, 0.7, 0.7)
        else
            GameTooltip:SetText(L["TIP_SHOW_MATERIALS"] or "Show Materials", 1, 1, 1)
            GameTooltip:AddLine("Click to show required materials for crafting queue", 0.7, 0.7, 0.7)
        end
        GameTooltip:Show()
    end

    materialsBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(unpack(HDG_UIHelpers.BTN_HOVER))
        ShowMaterialsTooltip(self)
    end)
    materialsBtn:SetScript("OnClick", function(self)
        -- Ignore clicks while farming (materials unavailable)
        if activeFarmingLumber then return end
        HDG_Settings.showLumberMaterials = not HDG_Settings.showLumberMaterials
        UpdateMaterialsIcon()
        HDG_LumberTracker.RefreshPinned()
        -- Update tooltip to reflect new state
        ShowMaterialsTooltip(self)
    end)
    UpdateMaterialsIcon()
    pinnedFrame.materialsBtn = materialsBtn

    -- Static lumber section (not scrollable)
    local lumberPanel = CreateFrame("Frame", nil, pinnedFrame, "BackdropTemplate")
    lumberPanel:SetPoint("TOPLEFT", 6, -34)
    lumberPanel:SetPoint("TOPRIGHT", -6, -34)
    lumberPanel:SetHeight(GetLumberPanelHeight())
    lumberPanel:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    lumberPanel:SetBackdropColor(unpack(CONFIG.COLORS.PANEL_BG))
    lumberPanel:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    pinnedFrame.lumberPanel = lumberPanel

    -- Lumber content area (static, no scroll)
    local lumberContent = CreateFrame("Frame", nil, lumberPanel)
    lumberContent:SetPoint("TOPLEFT", 4, -4)
    lumberContent:SetPoint("TOPRIGHT", -4, -4)
    lumberContent:SetHeight(GetLumberContentHeight())
    pinnedFrame.lumberContent = lumberContent

    -- Materials section (scrollable, only visible when enabled)
    local materialsPanel = CreateFrame("Frame", nil, pinnedFrame, "BackdropTemplate")
    materialsPanel:SetPoint("TOPLEFT", lumberPanel, "BOTTOMLEFT", 0, -4)
    materialsPanel:SetPoint("TOPRIGHT", lumberPanel, "BOTTOMRIGHT", 0, -4)
    materialsPanel:SetHeight(PINNED_MATERIALS_HEIGHT)  -- Fixed height for materials section
    materialsPanel:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    materialsPanel:SetBackdropColor(unpack(CONFIG.COLORS.PANEL_BG))
    materialsPanel:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    pinnedFrame.materialsPanel = materialsPanel

    -- Materials header (styled like recipes tab headers)
    local materialsHeaderBg = CreateFrame("Frame", nil, materialsPanel, "BackdropTemplate")
    materialsHeaderBg:SetPoint("TOPLEFT", 1, -1)
    materialsHeaderBg:SetPoint("TOPRIGHT", -1, -1)
    materialsHeaderBg:SetHeight(22)
    materialsHeaderBg:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    materialsHeaderBg:SetBackdropColor(0.1, 0.1, 0.1, 0.8)

    local requiredMatsLabel = L["HEADER_REQUIRED_MATERIALS"] or "Required Materials"
    local rawMatsLabel = L["HEADER_RAW_MATERIALS"] or "Raw Materials"
    local directLabel = L["BTN_DIRECT"] or "Direct"
    local rawLabel = L["BTN_RAW"] or "Raw"

    local materialsTitle = materialsHeaderBg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    materialsTitle:SetPoint("LEFT", 8, 0)
    materialsTitle:SetText("|cFFFFFFFF" .. requiredMatsLabel .. "|r")
    pinnedFrame.materialsHeader = materialsHeaderBg
    pinnedFrame.materialsTitle = materialsTitle

    -- Direct/Raw toggle buttons (like Recipes tab)
    local toggleContainer = CreateFrame("Frame", nil, materialsHeaderBg, "BackdropTemplate")
    toggleContainer:SetSize(80, 16)
    toggleContainer:SetPoint("RIGHT", -6, 0)
    toggleContainer:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    toggleContainer:SetBackdropColor(0.05, 0.05, 0.05, 0.8)

    local directBtn = CreateFrame("Button", nil, toggleContainer, "BackdropTemplate")
    directBtn:SetSize(38, 14)
    directBtn:SetPoint("LEFT", 1, 0)
    directBtn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })

    local directText = directBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    directText:SetPoint("CENTER", 0, 0)
    directText:SetText(directLabel)
    directText:SetFont(directText:GetFont(), 9, "")

    local rawBtn = CreateFrame("Button", nil, toggleContainer, "BackdropTemplate")
    rawBtn:SetSize(38, 14)
    rawBtn:SetPoint("LEFT", directBtn, "RIGHT", 1, 0)
    rawBtn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })

    local rawText = rawBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    rawText:SetPoint("CENTER", 0, 0)
    rawText:SetText(rawLabel)
    rawText:SetFont(rawText:GetFont(), 9, "")

    -- Update toggle button appearance based on state
    local function UpdatePinnedToggleAppearance()
        local showRaw = HDG_Settings.showRawMaterials
        if showRaw then
            directBtn:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
            directText:SetTextColor(0.6, 0.6, 0.6, 1)
            rawBtn:SetBackdropColor(0.08, 0.45, 0.45, 1.0)
            rawText:SetTextColor(1, 1, 1, 1)
            materialsTitle:SetText("|cFFFFFFFF" .. rawMatsLabel .. "|r")
        else
            directBtn:SetBackdropColor(0.08, 0.45, 0.45, 1.0)
            directText:SetTextColor(1, 1, 1, 1)
            rawBtn:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
            rawText:SetTextColor(0.6, 0.6, 0.6, 1)
            materialsTitle:SetText("|cFFFFFFFF" .. requiredMatsLabel .. "|r")
        end
    end

    directBtn:SetScript("OnClick", function()
        HDG_Settings.showRawMaterials = false
        UpdatePinnedToggleAppearance()
        HDG_LumberTracker.RefreshPinned()
        -- Also update main RecipesTab toggle appearance and materials
        if HDG_RecipesTab then
            if HDG_RecipesTab.mainFrame and HDG_RecipesTab.mainFrame.materialsToggleUpdate then
                HDG_RecipesTab.mainFrame.materialsToggleUpdate()
            end
            if HDG_RecipesTab.RefreshMaterials then
                HDG_RecipesTab:RefreshMaterials()
            end
        end
    end)

    rawBtn:SetScript("OnClick", function()
        HDG_Settings.showRawMaterials = true
        UpdatePinnedToggleAppearance()
        HDG_LumberTracker.RefreshPinned()
        -- Also update main RecipesTab toggle appearance and materials
        if HDG_RecipesTab then
            if HDG_RecipesTab.mainFrame and HDG_RecipesTab.mainFrame.materialsToggleUpdate then
                HDG_RecipesTab.mainFrame.materialsToggleUpdate()
            end
            if HDG_RecipesTab.RefreshMaterials then
                HDG_RecipesTab:RefreshMaterials()
            end
        end
    end)

    directBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0Direct Materials|r", 1, 1, 1)
        GameTooltip:AddLine("Show materials as listed in recipes", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("Crafted reagents shown as-is", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)
    directBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

    rawBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0Raw Materials (Power Craft)|r", 1, 1, 1)
        GameTooltip:AddLine("Expand crafted reagents to base materials", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("Shows what you actually need to gather/buy", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)
    rawBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

    pinnedFrame.materialsToggleUpdate = UpdatePinnedToggleAppearance
    UpdatePinnedToggleAppearance()

    -- ScrollFrame for materials only
    local scrollFrame = CreateFrame("ScrollFrame", "HDG_PinnedMaterialsScroll", materialsPanel, "UIPanelScrollFrameTemplate")
    scrollFrame:SetPoint("TOPLEFT", 4, -26)
    scrollFrame:SetPoint("BOTTOMRIGHT", -22, 48)  -- Leave room for button and cost footer
    pinnedFrame.scrollFrame = scrollFrame

    -- Style scrollbar using helper
    HDG_UIHelpers.StyleScrollBar(scrollFrame)

    -- Scrollable materials content
    local materialsContent = CreateFrame("Frame", nil, scrollFrame)
    materialsContent:SetSize(PINNED_WIDTH - 36, 1)  -- Height set dynamically
    scrollFrame:SetScrollChild(materialsContent)
    pinnedFrame.materialsContent = materialsContent

    -- Cost footer (inside materials panel at bottom)
    local costText = materialsPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    costText:SetPoint("BOTTOMLEFT", 10, 6)
    costText:SetPoint("BOTTOMRIGHT", -10, 6)
    costText:SetJustifyH("LEFT")
    pinnedFrame.costText = costText

    pinnedFrame:Hide()  -- Start hidden
    return pinnedFrame
end

-- Show the pinned lumber widget
function HDG_LumberTracker.ShowPinned()
    if not pinnedFrame then
        HDG_LumberTracker.CreatePinnedFrame()
    end

    HDG_Settings.lumberPinned = true
    pinnedFrame:Show()
    HDG_LumberTracker.RefreshPinned()
end

-- Hide the pinned lumber widget
function HDG_LumberTracker.HidePinned()
    if pinnedFrame then
        pinnedFrame:Hide()
    end
end

-- Check if pinned widget is visible
function HDG_LumberTracker.IsPinnedVisible()
    return pinnedFrame and pinnedFrame:IsShown()
end

-- Restore pinned state on login (called from HDG_UI after settings load)
function HDG_LumberTracker.RestorePinnedState()
    if HDG_Settings.lumberPinned then
        HDG_LumberTracker.ShowPinned()
    end
end

-- ============================================================================
-- REFRESH FUNCTIONS
-- ============================================================================

function HDG_LumberTracker.Refresh()
    if not lumberContent then return end
    HDG_UIHelpers.ClearScrollContent(lumberContent)

    -- Use local lumber calculation functions
    local shoppingLumber = HDG_LumberTracker.CalculateShoppingListLumber(HDG_Settings.shoppingList)
    local allLumber = HDG_LumberTracker.CalculateLumberNeeds(privateDB.professions or {})
    local expansionOrder = HDG_ExpansionData.LUMBER_ORDER

    local yOffset = 0
    local hasAnyLumber = false
    local isFarming = (activeFarmingLumber ~= nil)
    local focusMode = HDG_Settings.lumberFocusMode and isFarming

    for _, lumberId in ipairs(expansionOrder) do
        -- Skip non-active lumber when in focus mode
        if focusMode and lumberId ~= activeFarmingLumber then
            -- Skip this lumber row
        else
            local shoppingData = shoppingLumber[lumberId]
            local allData = allLumber[lumberId]

            hasAnyLumber = true
            local lumberInfo = LUMBER_TYPES[lumberId]

            yOffset = CreateLumberRow(lumberContent, lumberId, lumberInfo, shoppingData, allData, yOffset)
        end
    end

    if not hasAnyLumber then
        local emptyText = lumberContent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        emptyText:SetPoint("CENTER", 0, 0)
        emptyText:SetText("|cFF888888" .. (L["STATUS_ALL_LUMBER_COLLECTED"] or "All lumber collected!") .. "|r")
    end

    -- Also refresh pinned widget if visible
    if HDG_LumberTracker.IsPinnedVisible() then
        HDG_LumberTracker.RefreshPinned()
    end
end

-- Calculate non-lumber materials from shopping list
local function CalculateNonLumberMaterials(shoppingList)
    local materials = {}

    for _, item in ipairs(shoppingList or {}) do
        if item.reagents then
            for reagentKey, reagent in pairs(item.reagents) do
                local reagentID = reagent.id or reagentKey
                -- Skip lumber types
                if not LUMBER_TYPES[reagentID] then
                    if not materials[reagentID] then
                        local reagentName = "Unknown"
                        local reagentSource = "Unknown"

                        -- Try to get name from ReagentsDB
                        if HDG_ReagentsDB and HDG_ReagentsDB[reagentID] then
                            reagentSource = HDG_ReagentsDB[reagentID]
                        end

                        -- Get item info using shared cache (same as materials panel)
                        local itemName = Data().GetCachedItemName(reagentID)
                        if itemName then
                            reagentName = itemName
                        end

                        materials[reagentID] = {
                            id = reagentID,
                            name = reagentName,
                            source = reagentSource,
                            expansion = item.expansion,  -- Track expansion for coloring
                            needed = 0,
                            have = Data().GetItemCount(reagentID)
                        }
                    end
                    local reagentAmount = reagent.amount or reagent.qty or reagent.count or 1
                    materials[reagentID].needed = materials[reagentID].needed + (reagentAmount * (item.quantity or 1))
                end
            end
        end
    end

    return materials
end

-- Calculate RAW non-lumber materials from shopping list (uses HDG_PowerCrafting)
local function CalculateRawNonLumberMaterials(shoppingList)
    local materials = {}

    for _, item in ipairs(shoppingList or {}) do
        if item.reagents and HDG_PowerCrafting then
            -- Convert shopping list reagents format to HDG_DecorDB format for Power Crafting
            local reagentsForGraph = {}
            for _, reagent in ipairs(item.reagents) do
                local reagentID = reagent.id
                local reagentQty = reagent.amount or reagent.qty or 1
                reagentsForGraph[reagentID] = { name = reagent.name, qty = reagentQty }
            end

            -- Build a pseudo-recipe for the graph builder
            local pseudoRecipe = { reagents = reagentsForGraph }

            -- Build dependency graph to get raw materials
            local graph = HDG_PowerCrafting.BuildDependencyGraph(pseudoRecipe)
            if graph and graph.rawMaterials then
                for rawID, rawData in pairs(graph.rawMaterials) do
                    -- Skip lumber types
                    if not LUMBER_TYPES[rawID] then
                        if not materials[rawID] then
                            materials[rawID] = {
                                id = rawID,
                                name = rawData.name or "Unknown",
                                source = rawData.source or "Unknown",
                                expansion = item.expansion,
                                needed = 0,
                                have = Data().GetItemCount(rawID)
                            }
                        end
                        materials[rawID].needed = materials[rawID].needed + (rawData.qty * (item.quantity or 1))
                    end
                end
            end
        elseif item.reagents then
            -- Fallback to direct materials if PowerCrafting not available
            for _, reagent in ipairs(item.reagents) do
                local reagentID = reagent.id
                if not LUMBER_TYPES[reagentID] then
                    if not materials[reagentID] then
                        local reagentName = "Unknown"
                        local reagentSource = "Unknown"

                        if HDG_ReagentsDB and HDG_ReagentsDB[reagentID] then
                            reagentSource = HDG_ReagentsDB[reagentID]
                        end

                        local itemName = Data().GetCachedItemName(reagentID)
                        if itemName then
                            reagentName = itemName
                        end

                        materials[reagentID] = {
                            id = reagentID,
                            name = reagentName,
                            source = reagentSource,
                            expansion = item.expansion,
                            needed = 0,
                            have = Data().GetItemCount(reagentID)
                        }
                    end
                    local reagentAmount = reagent.amount or reagent.qty or reagent.count or 1
                    materials[reagentID].needed = materials[reagentID].needed + (reagentAmount * (item.quantity or 1))
                end
            end
        end
    end

    return materials
end

-- Get progress bar color based on percentage
local function GetMaterialProgressColor(percentage)
    local tealR, tealG, tealB = 0.18, 0.80, 0.44
    if percentage >= 100 then
        return 0.17, 0.48, 0.48, 1.0
    elseif percentage >= 1 then
        local opacity = 0.3 + (percentage / 100) * 0.4
        return tealR, tealG, tealB, opacity
    else
        return tealR, tealG, tealB, 0.15
    end
end

-- Create a material row styled like the recipe tab (with progress bar)
local function CreateMaterialRow(parent, reagentId, matInfo, yOffset, rowWidth)
    local ROW_HEIGHT = 18
    rowWidth = rowWidth or (PINNED_WIDTH - 24)

    -- Create StatusBar row like recipe tab
    local row = CreateFrame("StatusBar", nil, parent, "BackdropTemplate")
    row:SetSize(rowWidth, ROW_HEIGHT)
    row:SetPoint("TOPLEFT", 0, yOffset)
    row:SetStatusBarTexture(CONFIG.TEXTURES.STATUSBAR)
    row:SetMinMaxValues(0, matInfo.needed)
    row:SetValue(math.min(matInfo.have, matInfo.needed))

    local percentage = matInfo.needed > 0 and (matInfo.have / matInfo.needed * 100) or 0
    row:SetStatusBarColor(GetMaterialProgressColor(percentage))

    -- Background with expansion color tint (like recipe tab)
    local expColor = HDG_ExpansionData.GetColor(matInfo.expansion)
    if expColor then
        row:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
        row:SetBackdropColor(expColor[1], expColor[2], expColor[3], 0.15)
    else
        row:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
        row:SetBackdropColor(0.05, 0.05, 0.08, 0.8)
    end

    -- Item name (localized)
    local matName = HDG_Data.GetLocalizedName(reagentId, matInfo.name) or matInfo.name or "Unknown"
    local nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    nameText:SetPoint("LEFT", 4, 0)
    nameText:SetWidth(rowWidth - 70)
    nameText:SetJustifyH("LEFT")
    nameText:SetWordWrap(false)
    nameText:SetText(matName)
    local nameFont = nameText:GetFont()
    if nameFont then nameText:SetFont(nameFont, 10, "") end

    if matInfo.have >= matInfo.needed then
        nameText:SetTextColor(0.53, 1, 0.53)  -- Green for complete
    else
        nameText:SetTextColor(1, 0.82, 0.5)   -- Gold for incomplete
    end

    -- Count text
    local countStr
    if matInfo.have > matInfo.needed then
        local excess = matInfo.have - matInfo.needed
        countStr = string.format("%s/%s+|cFF88FF88%s|r",
            BreakUpLargeNumbers(matInfo.needed),
            BreakUpLargeNumbers(matInfo.needed),
            BreakUpLargeNumbers(excess))
    else
        countStr = string.format("%s/%s",
            BreakUpLargeNumbers(matInfo.have),
            BreakUpLargeNumbers(matInfo.needed))
    end

    local countText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    countText:SetPoint("RIGHT", -4, 0)
    countText:SetText(countStr)
    countText:SetFont(countText:GetFont(), 10, "")

    -- Click handler for AH integration
    local clickButton = CreateFrame("Button", nil, row)
    clickButton:SetAllPoints(row)
    clickButton:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    clickButton:SetScript("OnClick", function(_, button)
        if button == "LeftButton" and not IsShiftKeyDown() and not IsControlKeyDown() then
            if not AuctionHouseFrame or not AuctionHouseFrame:IsShown() then
                print("|cFFfcd34d[Housing Decor Guide]|r Please open the Auction House first")
            elseif AuctionHouseFrame.SearchBar and AuctionHouseFrame.SearchBar.SearchBox then
                AuctionHouseFrame.SearchBar.SearchBox:SetText(matName)
                print("|cFFfcd34d[Housing Decor Guide]|r Set AH search to: " .. matName)
            end
        elseif button == "LeftButton" and IsShiftKeyDown() then
            if C_AddOns.IsAddOnLoaded("Auctionator") and Auctionator and Auctionator.API and Auctionator.API.v1 and Auctionator.API.v1.MultiSearch then
                Auctionator.API.v1.MultiSearch("HousingDecorGuide", {matName})
                print("|cFFfcd34d[Housing Decor Guide]|r Searching Auctionator for: " .. matName)
            end
        elseif button == "RightButton" then
            if C_AddOns.IsAddOnLoaded("Auctionator") and Auctionator and Auctionator.API and Auctionator.API.v1 then
                local callerID = "HousingDecorGuide"
                local listName = "Housing Decor"
                local existingItems = {}
                if Auctionator.API.v1.GetShoppingListItems then
                    local success, items = pcall(Auctionator.API.v1.GetShoppingListItems, callerID, listName)
                    if success and items then existingItems = items end
                end
                local exists = false
                for _, existingItem in ipairs(existingItems) do
                    if existingItem == matName then exists = true; break end
                end
                if not exists then table.insert(existingItems, matName) end
                if Auctionator.API.v1.CreateShoppingList then
                    Auctionator.API.v1.CreateShoppingList(callerID, listName, existingItems)
                    print("|cFFfcd34d[Housing Decor Guide]|r Added '" .. matName .. "' to Auctionator list")
                end
            else
                print("|cFFfcd34d[Housing Decor Guide]|r Install Auctionator for search list integration")
            end
        end
    end)

    clickButton:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:SetItemByID(reagentId)
        GameTooltip:AddLine(" ")
        GameTooltip:AddDoubleLine("Source:", matInfo.source or "Unknown", 1, 1, 1, 0.7, 0.7, 0.7)
        GameTooltip:AddDoubleLine("Have:", BreakUpLargeNumbers(matInfo.have), 1, 1, 1, 1, 1, 1)
        GameTooltip:AddDoubleLine("Need:", BreakUpLargeNumbers(matInfo.needed), 1, 1, 1, 1, 1, 1)
        if matInfo.have >= matInfo.needed then
            GameTooltip:AddLine("|cFF88FF88Have enough!|r")
        else
            local shortfall = matInfo.needed - matInfo.have
            GameTooltip:AddLine(string.format("|cFFFF5555Need %s more|r", BreakUpLargeNumbers(shortfall)))
        end
        -- Show price if available
        local price = Data().GetItemPrice and Data().GetItemPrice(reagentId)
        if price and price > 0 then
            GameTooltip:AddLine(" ")
            local priceStr = Data().FormatGold and Data().FormatGold(price) or tostring(price)
            GameTooltip:AddDoubleLine("Unit Price:", priceStr, 1, 1, 1, 1, 0.82, 0.2)
            if matInfo.have < matInfo.needed then
                local stillNeeded = matInfo.needed - matInfo.have
                local totalCost = price * stillNeeded
                local costStr = Data().FormatGold and Data().FormatGold(totalCost) or tostring(totalCost)
                GameTooltip:AddDoubleLine("Est. Cost:", costStr, 1, 1, 1, 1, 0.82, 0.2)
            end
        end
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFF00FF00Click|r to set AH search text", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("|cFF00FF00Shift-Click|r to search Auctionator", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("|cFF00FF00Right-Click|r to add to Auctionator list", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)
    clickButton:SetScript("OnLeave", function() GameTooltip:Hide() end)

    return yOffset - (ROW_HEIGHT + 1)
end

-- Create a tracked reagent row with X button for removal
local function CreateTrackedReagentRow(parent, reagentId, matInfo, yOffset, rowWidth)
    local ROW_HEIGHT = 18
    rowWidth = rowWidth or (PINNED_WIDTH - 24)

    -- Create row frame
    local row = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    row:SetSize(rowWidth, ROW_HEIGHT)
    row:SetPoint("TOPLEFT", 0, yOffset)
    row:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    row:SetBackdropColor(0.08, 0.35, 0.33, 0.3)  -- Teal tint for tracked items

    -- Item name
    local matName = matInfo.name or "Unknown"
    local nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    nameText:SetPoint("LEFT", 4, 0)
    nameText:SetWidth(rowWidth - 85)  -- Leave room for count and X button
    nameText:SetJustifyH("LEFT")
    nameText:SetWordWrap(false)
    nameText:SetText(matName)
    nameText:SetFont(nameText:GetFont(), 10, "")
    nameText:SetTextColor(0.08, 0.72, 0.65)  -- Teal color for tracked items

    -- Count text (just show current count, or count/target if target set)
    local countStr
    if matInfo.needed > 0 then
        if matInfo.have >= matInfo.needed then
            local excess = matInfo.have - matInfo.needed
            countStr = string.format("|cFF88FF88%s|r/%s", BreakUpLargeNumbers(matInfo.have), BreakUpLargeNumbers(matInfo.needed))
        else
            countStr = string.format("%s/%s", BreakUpLargeNumbers(matInfo.have), BreakUpLargeNumbers(matInfo.needed))
        end
    else
        countStr = string.format("|cFFe2e8f0%s|r", BreakUpLargeNumbers(matInfo.have))
    end

    local countText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    countText:SetPoint("RIGHT", -20, 0)
    countText:SetText(countStr)
    countText:SetFont(countText:GetFont(), 10, "")

    -- X button to remove from tracking
    local removeBtn = CreateFrame("Button", nil, row)
    removeBtn:SetSize(14, 14)
    removeBtn:SetPoint("RIGHT", -3, 0)
    local removeTex = removeBtn:CreateTexture(nil, "ARTWORK")
    removeTex:SetAllPoints()
    removeTex:SetTexture("Interface\\Buttons\\UI-StopButton")
    removeTex:SetVertexColor(0.6, 0.6, 0.6, 0.8)
    removeBtn:SetScript("OnEnter", function(self)
        removeTex:SetVertexColor(1, 0.3, 0.3, 1)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(L["BTN_STOP_TRACKING"] or "Stop Tracking", 1, 0.3, 0.3)
        GameTooltip:AddLine("Click to remove from tracked reagents", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)
    removeBtn:SetScript("OnLeave", function()
        removeTex:SetVertexColor(0.6, 0.6, 0.6, 0.8)
        GameTooltip:Hide()
    end)
    removeBtn:SetScript("OnClick", function()
        HDG_LumberTracker.UntrackReagent(reagentId)
    end)

    -- Click handler for row (AH search)
    local clickButton = CreateFrame("Button", nil, row)
    clickButton:SetPoint("TOPLEFT", 0, 0)
    clickButton:SetPoint("BOTTOMRIGHT", removeBtn, "BOTTOMLEFT", -2, 0)
    clickButton:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    clickButton:SetScript("OnClick", function(_, button)
        if button == "LeftButton" and not IsShiftKeyDown() then
            if not AuctionHouseFrame or not AuctionHouseFrame:IsShown() then
                print("|cFFfcd34d[Housing Decor Guide]|r Please open the Auction House first")
            elseif AuctionHouseFrame.SearchBar and AuctionHouseFrame.SearchBar.SearchBox then
                AuctionHouseFrame.SearchBar.SearchBox:SetText(matName)
                print("|cFFfcd34d[Housing Decor Guide]|r Set AH search to: " .. matName)
            end
        elseif button == "LeftButton" and IsShiftKeyDown() then
            if C_AddOns.IsAddOnLoaded("Auctionator") and Auctionator and Auctionator.API and Auctionator.API.v1 and Auctionator.API.v1.MultiSearch then
                Auctionator.API.v1.MultiSearch("HousingDecorGuide", {matName})
                print("|cFFfcd34d[Housing Decor Guide]|r Searching Auctionator for: " .. matName)
            end
        end
    end)

    clickButton:SetScript("OnEnter", function(self)
        row:SetBackdropColor(0.1, 0.4, 0.38, 0.5)  -- Lighter on hover
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:SetItemByID(reagentId)
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFF14b8a6Tracked Reagent|r", 0.08, 0.72, 0.65)
        GameTooltip:AddDoubleLine("Have:", BreakUpLargeNumbers(matInfo.have), 1, 1, 1, 1, 1, 1)
        if matInfo.needed > 0 then
            GameTooltip:AddDoubleLine("Target:", BreakUpLargeNumbers(matInfo.needed), 1, 1, 1, 1, 1, 1)
            if matInfo.have >= matInfo.needed then
                GameTooltip:AddLine("|cFF88FF88Target reached!|r")
            else
                local shortfall = matInfo.needed - matInfo.have
                GameTooltip:AddLine(string.format("|cFFFF5555Need %s more|r", BreakUpLargeNumbers(shortfall)))
            end
        end
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFF00FF00Click|r to set AH search text", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("|cFF00FF00Shift-Click|r to search Auctionator", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("|cFFFF5555X button|r to stop tracking", 0.7, 0.7, 0.7)
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("Stays tracked even if recipe removed", 0.5, 0.5, 0.5)
        GameTooltip:Show()
    end)
    clickButton:SetScript("OnLeave", function()
        row:SetBackdropColor(0.08, 0.35, 0.33, 0.3)
        GameTooltip:Hide()
    end)

    return yOffset - (ROW_HEIGHT + 1)
end

-- Refresh the pinned lumber widget
function HDG_LumberTracker.RefreshPinned()
    if not pinnedFrame or not pinnedFrame.lumberContent then return end

    -- Clear lumber content (static section)
    HDG_UIHelpers.ClearScrollContent(pinnedFrame.lumberContent)

    local shoppingLumber = HDG_LumberTracker.CalculateShoppingListLumber(HDG_Settings.shoppingList)
    local allLumber = HDG_LumberTracker.CalculateLumberNeeds(privateDB.professions or {})
    local expansionOrder = HDG_ExpansionData.LUMBER_ORDER

    local yOffset = 0
    local isFarming = (activeFarmingLumber ~= nil)
    local focusMode = HDG_Settings.lumberFocusMode and isFarming

    -- Show lumber types (static, not scrollable)
    -- In focus mode, only show the actively farmed lumber
    for _, lumberId in ipairs(expansionOrder) do
        -- Skip non-active lumber when in focus mode
        if focusMode and lumberId ~= activeFarmingLumber then
            -- Skip this lumber row
        else
            local shoppingData = shoppingLumber[lumberId]
            local allData = allLumber[lumberId]
            local lumberInfo = LUMBER_TYPES[lumberId]

            yOffset = CreateLumberRow(pinnedFrame.lumberContent, lumberId, lumberInfo, shoppingData, allData, yOffset)
        end
    end

    -- Calculate lumber panel height based on content (accounts for expanded farming row)
    local lumberContentHeight = math.abs(yOffset)
    local lumberPanelHeight = lumberContentHeight + PINNED_PANEL_PADDING

    -- Update content and panel heights dynamically
    if pinnedFrame.lumberContent then
        pinnedFrame.lumberContent:SetHeight(lumberContentHeight)
    end
    if pinnedFrame.lumberPanel then
        pinnedFrame.lumberPanel:SetHeight(lumberPanelHeight)
    end

    -- Handle materials section visibility and content
    -- Hide materials when actively farming lumber
    local showMaterials = HDG_Settings.showLumberMaterials and not isFarming
    local totalCost = 0
    local missingPrices = 0

    if pinnedFrame.materialsPanel then
        if showMaterials then
            pinnedFrame.materialsPanel:Show()

            -- Update toggle button appearance
            if pinnedFrame.materialsToggleUpdate then
                pinnedFrame.materialsToggleUpdate()
            end

            -- Clear and refresh materials content
            if pinnedFrame.materialsContent then
                HDG_UIHelpers.ClearScrollContent(pinnedFrame.materialsContent)

                local matYOffset = 0
                local rowWidth = PINNED_WIDTH - 36  -- Account for scrollbar

                -- Get all non-lumber materials from shopping list (Direct or Raw based on setting)
                local materials
                if HDG_Settings.showRawMaterials then
                    materials = CalculateRawNonLumberMaterials(HDG_Settings.shoppingList)
                else
                    materials = CalculateNonLumberMaterials(HDG_Settings.shoppingList)
                end

                if next(materials) then
                    -- Sort materials: incomplete first, then alphabetically
                    local sortedMaterials = {}
                    for reagentId, matInfo in pairs(materials) do
                        table.insert(sortedMaterials, { id = reagentId, info = matInfo })
                    end
                    table.sort(sortedMaterials, function(a, b)
                        local aComplete = a.info.have >= a.info.needed
                        local bComplete = b.info.have >= b.info.needed
                        if aComplete ~= bComplete then
                            return not aComplete  -- Incomplete items first
                        end
                        return (a.info.name or "") < (b.info.name or "")
                    end)

                    for _, mat in ipairs(sortedMaterials) do
                        matYOffset = CreateMaterialRow(pinnedFrame.materialsContent, mat.id, mat.info, matYOffset, rowWidth)

                        -- Calculate pricing
                        local price = Data().GetItemPrice and Data().GetItemPrice(mat.id)
                        if price and price > 0 then
                            local stillNeeded = math.max(0, mat.info.needed - mat.info.have)
                            totalCost = totalCost + (price * stillNeeded)
                        elseif mat.info.have < mat.info.needed then
                            missingPrices = missingPrices + 1
                        end
                    end
                else
                    local emptyText = pinnedFrame.materialsContent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                    emptyText:SetPoint("TOPLEFT", 4, matYOffset)
                    emptyText:SetText("|cFF888888" .. (L["STATUS_NO_MATERIALS_QUEUED"] or "No materials in crafting queue") .. "|r")
                    matYOffset = matYOffset - 14
                end

                -- Add tracked reagents section if any are being tracked
                local trackedReagents = HDG_Settings.trackedReagents or {}
                if next(trackedReagents) then
                    -- Add separator line
                    matYOffset = matYOffset - 6
                    local separator = pinnedFrame.materialsContent:CreateTexture(nil, "ARTWORK")
                    separator:SetColorTexture(0.3, 0.3, 0.3, 0.6)
                    separator:SetSize(rowWidth, 1)
                    separator:SetPoint("TOPLEFT", 4, matYOffset)
                    matYOffset = matYOffset - 6

                    -- Add header
                    local trackedHeader = pinnedFrame.materialsContent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                    trackedHeader:SetPoint("TOPLEFT", 4, matYOffset)
                    trackedHeader:SetText("|cFF14b8a6" .. (L["HEADER_TRACKED_REAGENTS"] or "Tracked Reagents") .. "|r")
                    matYOffset = matYOffset - 14

                    -- Sort tracked reagents by name
                    local sortedTracked = {}
                    for reagentId, trackInfo in pairs(trackedReagents) do
                        local have = Data().GetItemCount and Data().GetItemCount(reagentId) or 0
                        table.insert(sortedTracked, {
                            id = reagentId,
                            info = {
                                name = trackInfo.name,
                                have = have,
                                needed = trackInfo.targetQty or 0,
                                source = "Tracked",
                                isTracked = true
                            }
                        })
                    end
                    table.sort(sortedTracked, function(a, b)
                        return (a.info.name or "") < (b.info.name or "")
                    end)

                    -- Render tracked reagent rows
                    for _, tracked in ipairs(sortedTracked) do
                        matYOffset = CreateTrackedReagentRow(pinnedFrame.materialsContent, tracked.id, tracked.info, matYOffset, rowWidth)
                    end
                end

                -- Set scroll content height based on actual content
                local contentHeight = math.abs(matYOffset) + 4
                pinnedFrame.materialsContent:SetHeight(contentHeight)
            end

            -- Set expanded height (header + lumber panel + materials panel + footer padding)
            pinnedFrame:SetHeight(PINNED_HEADER_HEIGHT + lumberPanelHeight + PINNED_MATERIALS_HEIGHT + PINNED_FOOTER_PADDING)
        else
            pinnedFrame.materialsPanel:Hide()
            -- Set compact height (header + lumber panel + footer padding)
            pinnedFrame:SetHeight(PINNED_HEADER_HEIGHT + lumberPanelHeight + PINNED_FOOTER_PADDING)
        end
    end

    -- Update materials button icon to show disabled state when farming
    if pinnedFrame.materialsBtn and pinnedFrame.materialsBtn.icon then
        if isFarming then
            pinnedFrame.materialsBtn.icon:SetDesaturated(true)
            pinnedFrame.materialsBtn.icon:SetVertexColor(0.4, 0.4, 0.4, 0.5)
        elseif pinnedFrame.materialsBtn.UpdateIcon then
            pinnedFrame.materialsBtn.UpdateIcon()
        end
    end

    -- Update cost footer (persistent element)
    if pinnedFrame.costText then
        if showMaterials then
            local hasPriceAddon, priceAddonName
            if Data().HasPriceAddon then
                hasPriceAddon, priceAddonName = Data().HasPriceAddon()
            end
            if hasPriceAddon then
                local costStr = Data().FormatGold and Data().FormatGold(totalCost) or tostring(totalCost)
                if missingPrices > 0 then
                    pinnedFrame.costText:SetText(string.format("|cFFe2e8f0Est:|r |cFFfcd34d~%s+|r |cFF888888(%d unknown)|r", costStr, missingPrices))
                elseif totalCost > 0 then
                    pinnedFrame.costText:SetText(string.format("|cFFe2e8f0Est:|r |cFFfcd34d~%s|r |cFF888888(%s)|r", costStr, priceAddonName or "AH"))
                else
                    pinnedFrame.costText:SetText("|cFFe2e8f0Est:|r |cFF88FF88Have all materials!|r")
                end
            else
                pinnedFrame.costText:SetText("|cFF888888Install TSM/Auctionator for prices|r")
            end
            pinnedFrame.costText:Show()
        else
            pinnedFrame.costText:SetText("")
            pinnedFrame.costText:Hide()
        end
    end
end

-- ============================================================================
-- FRAME ACCESSORS
-- ============================================================================

function HDG_LumberTracker.GetFrame()
    return lumberFrame
end

function HDG_LumberTracker.GetPinnedFrame()
    return pinnedFrame
end

-- ============================================================================
-- GATHERING TRACKER API
-- ============================================================================

--- Add a reagent to tracked list
-- @param itemID The item ID to track
-- @param name Optional item name (will be looked up if not provided)
-- @param targetQty Optional target quantity (defaults to shopping list need or 0)
function HDG_LumberTracker.TrackReagent(itemID, name, targetQty)
    if not itemID then return false end

    -- Don't track lumber (already tracked separately)
    if LUMBER_TYPES[itemID] then
        print("|cFFfcd34d[Housing Decor Guide]|r Lumber is already tracked in the Lumber section")
        return false
    end

    -- Initialize trackedReagents if needed
    if not HDG_Settings.trackedReagents then
        HDG_Settings.trackedReagents = {}
    end

    -- Check if already tracked
    if HDG_Settings.trackedReagents[itemID] then
        print("|cFFfcd34d[Housing Decor Guide]|r Already tracking this reagent")
        return false
    end

    -- Get item name if not provided
    local itemName = name
    if not itemName then
        itemName = Data().GetCachedItemName and Data().GetCachedItemName(itemID) or "Unknown"
    end

    -- Add to tracked list
    HDG_Settings.trackedReagents[itemID] = {
        name = itemName,
        targetQty = targetQty or 0,
        addedTime = time()
    }

    print("|cFFfcd34d[Housing Decor Guide]|r Now tracking: " .. itemName)

    -- Refresh pinned widget if visible
    if HDG_LumberTracker.IsPinnedVisible() then
        HDG_LumberTracker.RefreshPinned()
    end

    return true
end

--- Remove a reagent from tracked list
-- @param itemID The item ID to stop tracking
function HDG_LumberTracker.UntrackReagent(itemID)
    if not itemID or not HDG_Settings.trackedReagents then return false end

    local tracked = HDG_Settings.trackedReagents[itemID]
    if not tracked then return false end

    local itemName = tracked.name or "Unknown"
    HDG_Settings.trackedReagents[itemID] = nil

    print("|cFFfcd34d[Housing Decor Guide]|r Stopped tracking: " .. itemName)

    -- Refresh pinned widget if visible
    if HDG_LumberTracker.IsPinnedVisible() then
        HDG_LumberTracker.RefreshPinned()
    end

    return true
end

--- Check if a reagent is being tracked
-- @param itemID The item ID to check
-- @return boolean
function HDG_LumberTracker.IsReagentTracked(itemID)
    if not itemID or not HDG_Settings.trackedReagents then return false end
    return HDG_Settings.trackedReagents[itemID] ~= nil
end

--- Get all tracked reagents
-- @return table of tracked reagents
function HDG_LumberTracker.GetTrackedReagents()
    return HDG_Settings.trackedReagents or {}
end

--- Update target quantity for a tracked reagent
-- @param itemID The item ID
-- @param targetQty The new target quantity
function HDG_LumberTracker.UpdateTrackedTarget(itemID, targetQty)
    if not itemID or not HDG_Settings.trackedReagents then return false end
    if not HDG_Settings.trackedReagents[itemID] then return false end

    HDG_Settings.trackedReagents[itemID].targetQty = targetQty or 0

    -- Refresh pinned widget if visible
    if HDG_LumberTracker.IsPinnedVisible() then
        HDG_LumberTracker.RefreshPinned()
    end

    return true
end
