-- ============================================================================
-- HDG_GoblinTab.lua - Profit Analysis Tab
-- ============================================================================
-- Shows all decor items ranked by profit potential with lumber value analysis.
-- Helps gold-makers identify the most profitable crafts.
-- ============================================================================

local addonName, privateDB = ...

HDG_GoblinTab = {}

-- Local references
local CONFIG
local L = HDG_L or {}  -- Localization
local mainFrame
local goblinFrame

-- State
local profitData = {}           -- Calculated profit data for all items
local filteredData = {}         -- After applying filters
local currentSort = "profit"    -- profit, margin, lumber, sell, name
local sortAscending = false     -- false = descending (highest first)
local professionFilter = "All"
local expansionFilter = "All"
local hideUnprofitable = false
local onlyKnownRecipes = false
local expandedRowKey = nil      -- Which row is currently expanded (recipeKey)
local searchFilter = ""         -- Search filter text

-- Row pool for efficient scrolling
local rowPool = {}
local activeRows = {}

-- UI elements
local scrollFrame, scrollContent
local scanButton, priceSourceText, itemCountText
local expansionPanel            -- The expandable material breakdown panel
local activeDropdownMenu = nil  -- Track active dropdown menu for cleanup
local columnHeaders = {}        -- Track column header buttons for sort indicators
local canScanNow = false        -- Track if scan is available (for tooltips to work when "disabled")

-- Forward declarations
local UpdateScanButton
local ShowExpansionPanel
local pendingItemRequests = {}
local expansionPanelItem = nil
local expansionPanelRow = nil

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

function HDG_GoblinTab.Init(frame)
    mainFrame = frame
    CONFIG = privateDB.CONFIG

    -- Register for AH open/close events to auto-update scan button
    local eventFrame = CreateFrame("Frame")
    eventFrame:RegisterEvent("AUCTION_HOUSE_SHOW")
    eventFrame:RegisterEvent("AUCTION_HOUSE_CLOSED")
    eventFrame:RegisterEvent("ITEM_DATA_LOAD_RESULT")
    eventFrame:SetScript("OnEvent", function(self, event, ...)
        if event == "ITEM_DATA_LOAD_RESULT" then
            -- Item data loaded - refresh expansion panel if we were waiting for this item
            local itemID, success = ...
            if success and pendingItemRequests[itemID] then
                pendingItemRequests[itemID] = nil
                -- Refresh the expansion panel if it's showing
                if expansionPanel and expansionPanel:IsShown() and expansionPanelItem and expansionPanelRow then
                    ShowExpansionPanel(expansionPanelRow, expansionPanelItem)
                end
            end
        else
            -- Update scan button state when AH opens or closes
            if goblinFrame and goblinFrame:IsShown() then
                UpdateScanButton()
            end
        end
    end)
end

-- ============================================================================
-- PROFIT CALCULATION
-- ============================================================================

-- Calculate profit data for all decor items
-- Get correct itemID, preferring ProfessionsDB (more accurate) over DecorDB
local function GetCorrectItemID(recipe)
    -- First try ProfessionsDB using spellID (most accurate source)
    if recipe.spellID and HDG_ProfessionsDB then
        local profData = HDG_ProfessionsDB[recipe.spellID]
        if profData and profData.itemID then
            return profData.itemID
        end
    end
    -- Fallback to DecorDB itemID
    return recipe.itemID
end

local function CalculateProfitData()
    profitData = {}

    if not HDG_DecorDB then return end

    for recipeKey, recipe in pairs(HDG_DecorDB) do
        local itemID = GetCorrectItemID(recipe)
        if itemID then
            local sellPrice, priceSource = HDG_PriceIntegration.GetItemPrice(itemID)

            -- Calculate material cost
            local materialCost = 0
            local lumberQty = 0
            local lumberCost = 0
            local lumberType = nil  -- Store which lumber type this recipe uses
            local hasAllPrices = true

            if recipe.reagents then
                for reagentID, reagentData in pairs(recipe.reagents) do
                    local qty = reagentData.qty or 1

                    -- Check if this is lumber (BoP, so it has 0 AH price)
                    if HDG_ExpansionData.IsLumber(reagentID) then
                        lumberQty = lumberQty + qty
                        lumberType = reagentID  -- Remember which lumber type
                        -- Lumber is BoP, so we don't add its AH cost
                    elseif HDG_ExpansionData.IsBoPReagent(reagentID) then
                        -- Other BoP reagents (e.g., Primal Spirit) - skip AH cost
                    else
                        local matPrice = HDG_PriceIntegration.GetItemPrice(reagentID)
                        if matPrice then
                            materialCost = materialCost + (matPrice * qty)
                        else
                            hasAllPrices = false
                        end
                    end
                end
            end

            -- Calculate profit metrics
            local profit = sellPrice and (sellPrice - materialCost) or nil
            local margin = (sellPrice and sellPrice > 0) and (profit / sellPrice * 100) or nil

            -- Lumber value: what you "earn" per lumber by crafting this item
            -- Formula: (sell_price - non_lumber_material_cost) / lumber_qty
            local lumberValue = nil
            if lumberQty > 0 and sellPrice then
                lumberValue = (sellPrice - materialCost) / lumberQty
            end

            -- Get normalized expansion name for filtering
            local expansion = HDG_ExpansionData.NormalizeToDisplayName(recipe.expansion)

            -- Check if player knows this recipe
            local isKnown = false
            if _G.HDG_Data and _G.HDG_Data.PlayerKnowsRecipeBySpellID then
                isKnown = _G.HDG_Data.PlayerKnowsRecipeBySpellID(recipe.spellID)
            end

            -- Get TSM region sale average (TSM only)
            local regionSaleAvg = HDG_PriceIntegration.GetRegionSaleAvg(itemID)

            -- Calculate TSM margin based on region avg instead of sell price
            local tsmMargin = nil
            if regionSaleAvg and regionSaleAvg > 0 and materialCost then
                tsmMargin = ((regionSaleAvg - materialCost) / regionSaleAvg) * 100
            end

            table.insert(profitData, {
                recipeKey = recipeKey,
                recipe = recipe,
                itemID = itemID,
                name = HDG_Data.GetLocalizedName(itemID, recipe.name),
                profession = recipe.profession or "Unknown",
                expansion = expansion,
                sellPrice = sellPrice,
                materialCost = materialCost,
                profit = profit,
                margin = margin,
                lumberQty = lumberQty,
                lumberType = lumberType,
                lumberValue = lumberValue,
                hasAllPrices = hasAllPrices,
                isKnown = isKnown,
                priceSource = priceSource,
                regionSaleAvg = regionSaleAvg,
                tsmMargin = tsmMargin,
            })
        end
    end
end

-- Apply filters and sorting to profit data
local function ApplyFiltersAndSort()
    filteredData = {}

    for _, item in ipairs(profitData) do
        local include = true

        -- Filter by search text
        if searchFilter and searchFilter ~= "" then
            local searchLower = searchFilter:lower()
            if not string.find(item.name:lower(), searchLower, 1, true) then
                include = false
            end
        end

        -- Filter by profession
        if include and professionFilter ~= "All" and item.profession ~= professionFilter then
            include = false
        end

        -- Filter by expansion
        if include and expansionFilter ~= "All" and item.expansion ~= expansionFilter then
            include = false
        end

        -- Filter out unprofitable
        if include and hideUnprofitable and (not item.profit or item.profit <= 0) then
            include = false
        end

        -- Filter to only known recipes
        if include and onlyKnownRecipes and not item.isKnown then
            include = false
        end

        if include then
            table.insert(filteredData, item)
        end
    end

    -- Sort
    local sortKey = currentSort
    table.sort(filteredData, function(a, b)
        local aVal, bVal

        if sortKey == "profit" then
            aVal = a.profit or -999999999
            bVal = b.profit or -999999999
        elseif sortKey == "margin" then
            aVal = a.margin or -999999999
            bVal = b.margin or -999999999
        elseif sortKey == "lumber" then
            aVal = a.lumberValue or -999999999
            bVal = b.lumberValue or -999999999
        elseif sortKey == "lumberTotal" then
            aVal = (a.lumberValue or 0) * (a.lumberQty or 0)
            bVal = (b.lumberValue or 0) * (b.lumberQty or 0)
            if aVal == 0 then aVal = -999999999 end
            if bVal == 0 then bVal = -999999999 end
        elseif sortKey == "lumberType" then
            -- Sort by lumber type name
            local aInfo = a.lumberType and HDG_ExpansionData.GetLumberInfo(a.lumberType)
            local bInfo = b.lumberType and HDG_ExpansionData.GetLumberInfo(b.lumberType)
            aVal = aInfo and aInfo.name or "zzz"
            bVal = bInfo and bInfo.name or "zzz"
            if sortAscending then
                return aVal < bVal
            else
                return aVal > bVal
            end
        elseif sortKey == "sell" then
            aVal = a.sellPrice or 0
            bVal = b.sellPrice or 0
        elseif sortKey == "cost" then
            aVal = a.materialCost or 0
            bVal = b.materialCost or 0
        elseif sortKey == "name" then
            aVal = a.name or ""
            bVal = b.name or ""
            if sortAscending then
                return aVal < bVal
            else
                return aVal > bVal
            end
        elseif sortKey == "regionSaleAvg" then
            aVal = a.regionSaleAvg or -999999999
            bVal = b.regionSaleAvg or -999999999
        elseif sortKey == "tsmMargin" then
            aVal = a.tsmMargin or -999999999
            bVal = b.tsmMargin or -999999999
        end

        if sortAscending then
            return aVal < bVal
        else
            return aVal > bVal
        end
    end)
end

-- ============================================================================
-- EXPANSION PANEL (Material Breakdown)
-- ============================================================================

-- Calculate direct and raw materials for a recipe
local function CalculateMaterialBreakdown(item)
    local directMaterials = {}
    local rawMaterials = {}
    local directTotal = 0
    local rawTotal = 0

    if not item or not item.recipe or not item.recipe.reagents then
        return directMaterials, rawMaterials, directTotal, rawTotal
    end

    -- Direct materials from the recipe
    for reagentID, reagentData in pairs(item.recipe.reagents) do
        local qty = reagentData.qty or 1
        local price = HDG_PriceIntegration.GetItemPrice(reagentID)
        local totalPrice = price and (price * qty) or 0
        local name = reagentData.name or ("Item " .. reagentID)

        -- Get name from ReagentsDB if available
        if HDG_ReagentsDB and HDG_ReagentsDB[reagentID] then
            name = HDG_ReagentsDB[reagentID].name or name
        end

        table.insert(directMaterials, {
            itemID = reagentID,
            name = name,
            qty = qty,
            unitPrice = price,
            totalPrice = totalPrice,
            isLumber = HDG_ExpansionData.IsLumber(reagentID),
        })

        if not HDG_ExpansionData.IsLumber(reagentID) then
            directTotal = directTotal + totalPrice
        end
    end

    -- Raw materials using PowerCrafting if available
    if HDG_PowerCrafting and HDG_PowerCrafting.BuildDependencyGraph then
        local graph = HDG_PowerCrafting.BuildDependencyGraph(item.recipe)
        if graph and graph.rawMaterials then
            for matID, matData in pairs(graph.rawMaterials) do
                local qty = matData.qty or 1
                local price = HDG_PriceIntegration.GetItemPrice(matID)
                local totalPrice = price and (price * qty) or 0
                local name = matData.name or ("Item " .. matID)

                table.insert(rawMaterials, {
                    itemID = matID,
                    name = name,
                    qty = qty,
                    unitPrice = price,
                    totalPrice = totalPrice,
                    isLumber = HDG_ExpansionData.IsLumber(matID),
                })

                if not HDG_ExpansionData.IsLumber(matID) then
                    rawTotal = rawTotal + totalPrice
                end
            end
        end
    end

    -- Sort by total price descending
    table.sort(directMaterials, function(a, b) return (a.totalPrice or 0) > (b.totalPrice or 0) end)
    table.sort(rawMaterials, function(a, b) return (a.totalPrice or 0) > (b.totalPrice or 0) end)

    return directMaterials, rawMaterials, directTotal, rawTotal
end

-- Request item info and schedule refresh if needed
local function RequestItemInfo(itemID)
    if not itemID then return end
    -- Check if we already have the name
    local name = C_Item and C_Item.GetItemNameByID and C_Item.GetItemNameByID(itemID)
    if not name then
        name = C_Item.GetItemInfo(itemID)
    end
    if name then return end  -- Already cached

    -- Request the item data
    if C_Item and C_Item.RequestLoadItemDataByID then
        pendingItemRequests[itemID] = true
        C_Item.RequestLoadItemDataByID(itemID)
    end
end

-- Create or update the expansion panel
ShowExpansionPanel = function(parentRow, item)
    if not expansionPanel then
        expansionPanel = CreateFrame("Frame", nil, scrollContent, "BackdropTemplate")
        expansionPanel:SetBackdrop({
            bgFile = CONFIG.TEXTURES.WHITE_BG,
            edgeFile = CONFIG.TEXTURES.WHITE_BG,
            edgeSize = 1,
        })
        expansionPanel:SetBackdropColor(0.08, 0.08, 0.10, 0.95)
        expansionPanel:SetBackdropBorderColor(0.2, 0.6, 0.5, 0.8)

        -- Create text elements for materials
        expansionPanel.directTitle = expansionPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        expansionPanel.directTitle:SetPoint("TOPLEFT", 10, -8)
        expansionPanel.directTitle:SetText("|cFFfcd34dDirect Materials|r")

        expansionPanel.directText = expansionPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        expansionPanel.directText:SetPoint("TOPLEFT", 10, -24)
        expansionPanel.directText:SetWidth(260)
        expansionPanel.directText:SetJustifyH("LEFT")

        expansionPanel.rawTitle = expansionPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        expansionPanel.rawTitle:SetPoint("TOPLEFT", 290, -8)
        expansionPanel.rawTitle:SetText("|cFFfcd34dRaw Materials|r")

        expansionPanel.rawText = expansionPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        expansionPanel.rawText:SetPoint("TOPLEFT", 290, -24)
        expansionPanel.rawText:SetWidth(260)
        expansionPanel.rawText:SetJustifyH("LEFT")
    end

    -- Store current item and row for refresh
    expansionPanelItem = item
    expansionPanelRow = parentRow

    -- Calculate materials
    local directMats, rawMats, directTotal, rawTotal = CalculateMaterialBreakdown(item)

    -- Request item info for any uncached items
    pendingItemRequests = {}
    for _, mat in ipairs(directMats) do
        RequestItemInfo(mat.itemID)
    end
    for _, mat in ipairs(rawMats) do
        RequestItemInfo(mat.itemID)
    end

    -- Format direct materials
    local directLines = {}
    for _, mat in ipairs(directMats) do
        local priceStr = mat.unitPrice and HDG_PriceIntegration.FormatGoldOnly(mat.totalPrice) or "?"
        local color = mat.isLumber and "|cFFFFAA00" or "|cFFFFFFFF"
        table.insert(directLines, string.format("%s%dx %s|r  %s", color, mat.qty, mat.name, priceStr))
    end
    table.insert(directLines, string.format("|cFF888888Total:|r |cFF88FF88%s|r", HDG_PriceIntegration.FormatGoldOnly(directTotal)))
    expansionPanel.directText:SetText(table.concat(directLines, "\n"))

    -- Format raw materials
    local rawLines = {}
    for _, mat in ipairs(rawMats) do
        local priceStr = mat.unitPrice and HDG_PriceIntegration.FormatGoldOnly(mat.totalPrice) or "?"
        local color = mat.isLumber and "|cFFFFAA00" or "|cFFFFFFFF"
        table.insert(rawLines, string.format("%s%dx %s|r  %s", color, mat.qty, mat.name, priceStr))
    end
    table.insert(rawLines, string.format("|cFF888888Total:|r |cFF88FF88%s|r", HDG_PriceIntegration.FormatGoldOnly(rawTotal)))
    expansionPanel.rawText:SetText(table.concat(rawLines, "\n"))

    -- Calculate height based on content
    local numLines = math.max(#directMats, #rawMats) + 2  -- +2 for title and total
    local panelHeight = 16 + (numLines * 14)
    panelHeight = math.max(panelHeight, 60)
    panelHeight = math.min(panelHeight, 200)

    expansionPanel:SetHeight(panelHeight)
    expansionPanel:SetPoint("TOPLEFT", parentRow, "BOTTOMLEFT", 0, -2)
    expansionPanel:SetPoint("RIGHT", parentRow, "RIGHT", 0, 0)
    expansionPanel:Show()

    return panelHeight + 4  -- Return height for offset calculation
end

local function HideExpansionPanel()
    expandedRowKey = nil  -- Clear key first
    expansionPanelItem = nil
    expansionPanelRow = nil
    pendingItemRequests = {}
    if expansionPanel then
        expansionPanel:Hide()
        expansionPanel:ClearAllPoints()
    end
end

-- ============================================================================
-- AUCTIONATOR SCAN INTEGRATION
-- ============================================================================

-- Check if we can trigger an Auctionator scan
function HDG_GoblinTab.CanTriggerScan()
    if not Auctionator or not Auctionator.State then return false, "No Auctionator" end
    if not AuctionHouseFrame or not AuctionHouseFrame:IsShown() then return false, "AH Closed" end
    if not Auctionator.State.FullScanFrameRef then return false, "Not Ready" end

    if Auctionator.State.FullScanFrameRef.CanInitiate then
        if Auctionator.State.FullScanFrameRef:CanInitiate() then
            return true, "Ready"
        else
            -- On cooldown - try to get time remaining
            if Auctionator.State.FullScanFrameRef.NextScanMessage then
                return false, Auctionator.State.FullScanFrameRef:NextScanMessage()
            end
            return false, "Cooldown"
        end
    end

    return false, "Unknown"
end

-- Trigger a full AH scan
function HDG_GoblinTab.TriggerScan()
    local canScan, reason = HDG_GoblinTab.CanTriggerScan()
    if canScan then
        Auctionator.State.FullScanFrameRef:InitiateScan()
        if scanButton then
            scanButton.text:SetText("|cFFFFFFFFScanning...|r")
        end
    else
        print("|cFF00FF00Housing Decor Guide:|r Cannot scan: " .. (reason or "Unknown"))
    end
end

-- Register for scan completion callback
local function RegisterScanCallback()
    if Auctionator and Auctionator.API and Auctionator.API.v1 and Auctionator.API.v1.RegisterForDBUpdate then
        Auctionator.API.v1.RegisterForDBUpdate("HousingDecorGuide", function()
            if goblinFrame and goblinFrame:IsShown() then
                HDG_GoblinTab.Refresh()
            end
        end)
    end
end

-- Get time since last Auctionator scan
local function GetAuctionatorScanTime()
    if not Auctionator or not Auctionator.SavedState then
        return nil
    end

    local lastScan = Auctionator.SavedState.TimeOfLastReplicateScan
    if not lastScan then
        return nil
    end

    return time() - lastScan
end

-- Get time since last TSM scan (if available)
local function GetTSMScanTime()
    -- TSM doesn't expose scan time the same way, but we can check if data exists
    -- For now, return nil to indicate "unknown" - TSM syncs data differently
    return nil
end

-- Get time since last scan for the active price addon
local function GetTimeSinceLastScan()
    local preferred = HDG_Settings and HDG_Settings.preferredPriceAddon

    if preferred == "TSM" then
        return GetTSMScanTime()
    elseif preferred == "Auctionator" then
        return GetAuctionatorScanTime()
    end

    -- Default: check based on which addon is available (TSM first)
    if HDG_PriceIntegration.IsTSMAvailable() then
        return GetTSMScanTime()
    end
    return GetAuctionatorScanTime()
end

-- Get the currently active price addon name (respecting user preference)
local function GetActivePriceAddonName()
    local preferred = HDG_Settings and HDG_Settings.preferredPriceAddon

    if preferred == "TSM" and HDG_PriceIntegration.IsTSMAvailable() then
        return "TSM"
    elseif preferred == "Auctionator" and HDG_PriceIntegration.IsAuctionatorAvailable() then
        return "Auctionator"
    end

    -- Default: TSM first if available
    if HDG_PriceIntegration.IsTSMAvailable() then
        return "TSM"
    elseif HDG_PriceIntegration.IsAuctionatorAvailable() then
        return "Auctionator"
    end

    return nil
end

-- Format seconds into human-readable string
local function FormatTimeSince(seconds)
    if not seconds then return "Never" end

    if seconds < 60 then
        return "< 1m ago"
    elseif seconds < 3600 then
        return string.format("%dm ago", math.floor(seconds / 60))
    elseif seconds < 86400 then
        local hours = math.floor(seconds / 3600)
        return string.format("%dh ago", hours)
    else
        local days = math.floor(seconds / 86400)
        return string.format("%dd ago", days)
    end
end

-- Update scan button state
UpdateScanButton = function()
    if not scanButton then return end

    local addonName = GetActivePriceAddonName()

    if not addonName then
        scanButton:Hide()
        if priceSourceText then
            priceSourceText:SetText("|cFFFF5555No price addon|r")
        end
        return
    end

    -- Update price source text with time since last scan
    if priceSourceText then
        local timeSince = GetTimeSinceLastScan()

        if not timeSince then
            -- No scan time available (TSM doesn't expose it) - just show addon name in teal
            priceSourceText:SetText(string.format("|cFF14b8a6%s|r", addonName))
        else
            -- Show time since last scan with color coding
            local timeStr = FormatTimeSince(timeSince)
            local timeColor
            if timeSince < 3600 then  -- < 1 hour
                timeColor = "|cFF88FF88"  -- Green
            elseif timeSince < 86400 then  -- < 1 day
                timeColor = "|cFFFFAA00"  -- Yellow/Orange
            else
                timeColor = "|cFFFF5555"  -- Red
            end
            priceSourceText:SetText(string.format("|cFF888888%s:|r %s%s|r", addonName, timeColor, timeStr))
        end
    end

    -- Check if TSM is selected (no scan needed - TSM syncs from servers)
    -- or if Auctionator is not available for scanning
    if addonName == "TSM" or not Auctionator or not Auctionator.State then
        scanButton:Hide()
        return
    end

    scanButton:Show()

    local canScan, reason = HDG_GoblinTab.CanTriggerScan()
    canScanNow = canScan  -- Store for OnClick handler
    if canScan then
        scanButton.text:SetText("|cFFFFFFFFScan AH|r")
        scanButton:SetBackdropColor(0.08, 0.45, 0.42, 0.9)
    else
        if reason == "AH Closed" then
            scanButton.text:SetText("|cFF888888AH Closed|r")
        else
            scanButton.text:SetText("|cFF888888Cooldown|r")
        end
        scanButton:SetBackdropColor(0.2, 0.2, 0.2, 0.9)
    end
end

-- Update TSM-only column visibility (Region Avg and TSM Margin headers)
local function UpdateTSMColumnVisibility()
    local isTSMActive = GetActivePriceAddonName() == "TSM"

    -- Show/hide the Region Avg column header (TSM only)
    if columnHeaders["regionSaleAvg"] then
        if isTSMActive then
            columnHeaders["regionSaleAvg"]:Show()
        else
            columnHeaders["regionSaleAvg"]:Hide()
        end
    end

    -- Show/hide the TSM Margin column header (TSM only)
    if columnHeaders["tsmMargin"] then
        if isTSMActive then
            columnHeaders["tsmMargin"]:Show()
        else
            columnHeaders["tsmMargin"]:Hide()
        end
    end
end

-- ============================================================================
-- UI CREATION
-- ============================================================================

-- Create a row from the pool or new
local function AcquireRow()
    local row = table.remove(rowPool)
    if not row then
        row = CreateFrame("Button", nil, scrollContent, "BackdropTemplate")
        row:SetHeight(24)
        row:SetBackdrop({
            bgFile = CONFIG.TEXTURES.WHITE_BG,
        })

        -- Item name (left-aligned)
        row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.nameText:SetPoint("LEFT", 8, 0)
        row.nameText:SetWidth(180)
        row.nameText:SetJustifyH("LEFT")
        row.nameText:SetWordWrap(false)

        -- Lumber type (left-aligned, expansion-colored)
        row.lumberTypeText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.lumberTypeText:SetPoint("LEFT", 192, 0)
        row.lumberTypeText:SetWidth(70)
        row.lumberTypeText:SetJustifyH("LEFT")

        -- Per-lumber value ($/Lum)
        row.lumberText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.lumberText:SetPoint("LEFT", 266, 0)
        row.lumberText:SetWidth(60)
        row.lumberText:SetJustifyH("RIGHT")

        -- Total lumber value (LumTot)
        row.lumberTotalText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.lumberTotalText:SetPoint("LEFT", 330, 0)
        row.lumberTotalText:SetWidth(60)
        row.lumberTotalText:SetJustifyH("RIGHT")

        -- Cost
        row.costText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.costText:SetPoint("LEFT", 394, 0)
        row.costText:SetWidth(60)
        row.costText:SetJustifyH("RIGHT")

        -- Sell price
        row.sellText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.sellText:SetPoint("LEFT", 458, 0)
        row.sellText:SetWidth(60)
        row.sellText:SetJustifyH("RIGHT")

        -- Profit
        row.profitText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.profitText:SetPoint("LEFT", 522, 0)
        row.profitText:SetWidth(65)
        row.profitText:SetJustifyH("RIGHT")

        -- Margin
        row.marginText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.marginText:SetPoint("LEFT", 591, 0)
        row.marginText:SetWidth(50)
        row.marginText:SetJustifyH("RIGHT")

        -- Region Avg (TSM only - visibility controlled by UpdateTSMColumnVisibility)
        row.regionAvgText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.regionAvgText:SetPoint("LEFT", 645, 0)
        row.regionAvgText:SetWidth(90)
        row.regionAvgText:SetJustifyH("RIGHT")

        -- TSM Margin (TSM only - visibility controlled by UpdateTSMColumnVisibility)
        row.tsmMarginText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        row.tsmMarginText:SetPoint("LEFT", 739, 0)
        row.tsmMarginText:SetWidth(55)
        row.tsmMarginText:SetJustifyH("RIGHT")

        -- Click handler for expand/collapse, add to queue, AH search
        row:SetScript("OnClick", function(self)
            if IsShiftKeyDown() then
                -- Shift-click: Search AH for this item
                if AuctionHouseFrame and AuctionHouseFrame:IsShown() then
                    local searchBox = AuctionHouseFrame.SearchBar and AuctionHouseFrame.SearchBar.SearchBox
                    if searchBox then
                        local itemName = self.item and self.item.name or ""
                        searchBox:SetText(itemName)
                        -- Trigger the search
                        if AuctionHouseFrame.SearchBar.SearchButton then
                            AuctionHouseFrame.SearchBar.SearchButton:Click()
                        end
                    end
                else
                    print("|cFF00FF00Housing Decor Guide:|r Open the Auction House to search")
                end
            elseif IsControlKeyDown() then
                -- Ctrl-click: Add to crafting queue
                if self.item and HDG_UI and HDG_UI.AddToShoppingList then
                    local item = self.item
                    local recipe = {
                        id = item.itemID,
                        name = item.name,
                        spell = item.recipe.spellID,
                        expansion = item.expansion,
                        profession = item.profession,
                        reagents = {},
                    }
                    if item.recipe.reagents then
                        for reagentID, reagentData in pairs(item.recipe.reagents) do
                            table.insert(recipe.reagents, {
                                id = reagentID,
                                amount = reagentData.qty,
                            })
                        end
                    end
                    HDG_UI.AddToShoppingList(recipe)
                    print("|cFF00FF00Housing Decor Guide:|r Added " .. item.name .. " to crafting queue")
                end
            else
                -- Normal click: Toggle material breakdown expansion
                if self.item then
                    if expandedRowKey == self.item.recipeKey then
                        -- Collapse if clicking the same row
                        HideExpansionPanel()
                        HDG_GoblinTab.Refresh()  -- Re-render to remove expansion space
                    else
                        -- Expand this row
                        expandedRowKey = self.item.recipeKey
                        HDG_GoblinTab.Refresh()  -- Re-render to show expansion
                    end
                end
            end
        end)

        -- Hover effects
        row:SetScript("OnEnter", function(self)
            self:SetBackdropColor(0.25, 0.25, 0.27, 0.8)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            if self.itemID then
                GameTooltip:SetItemByID(self.itemID)
            end
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cFFFFFF00Click|r to expand material breakdown", 0.7, 0.7, 0.7)
            GameTooltip:AddLine("|cFFFFFF00Ctrl-Click|r to add to crafting queue", 0.7, 0.7, 0.7)
            GameTooltip:AddLine("|cFFFFFF00Shift-Click|r to search Auction House", 0.7, 0.7, 0.7)
            GameTooltip:Show()
        end)
        row:SetScript("OnLeave", function(self)
            self:SetBackdropColor(unpack(self.bgColor or {0.12, 0.12, 0.14, 0.5}))
            GameTooltip:Hide()
        end)
    end

    row:Show()
    table.insert(activeRows, row)
    return row
end

-- Release all rows back to pool
local function ReleaseAllRows()
    for _, row in ipairs(activeRows) do
        row:Hide()
        row:ClearAllPoints()
        table.insert(rowPool, row)
    end
    activeRows = {}
end

-- Close any active dropdown menu
local function CloseActiveDropdown()
    if activeDropdownMenu and activeDropdownMenu:IsShown() then
        activeDropdownMenu:Hide()
    end
    activeDropdownMenu = nil
end

-- Filter button options for Goblin tab (taller, lighter inactive text)
local GOBLIN_FILTER_OPTIONS = { height = 18, inactiveTextColor = "D0D0D0" }

-- Create filter dropdown
local function CreateDropdown(parent, label, items, width, onChange)
    local container = CreateFrame("Frame", nil, parent)
    container:SetSize(width + 50, 22)

    local labelText = container:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    labelText:SetPoint("LEFT", 0, 0)
    labelText:SetText(label)

    local dropdown = CreateFrame("Button", nil, container, "BackdropTemplate")
    dropdown:SetSize(width, 20)
    dropdown:SetPoint("LEFT", labelText, "RIGHT", 4, 0)
    dropdown:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    dropdown:SetBackdropColor(0.15, 0.15, 0.16, 0.9)
    dropdown:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)

    dropdown.text = dropdown:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    dropdown.text:SetPoint("LEFT", 6, 0)
    dropdown.text:SetText(items[1] or "All")

    dropdown.arrow = dropdown:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    dropdown.arrow:SetPoint("RIGHT", -6, 0)
    dropdown.arrow:SetText("|cFF888888v|r")

    dropdown.items = items
    dropdown.selected = items[1] or "All"
    dropdown.onChange = onChange

    dropdown:SetScript("OnClick", function(self)
        -- Close any existing dropdown first
        CloseActiveDropdown()

        -- Create dropdown menu
        local menu = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
        menu:SetFrameStrata("TOOLTIP")
        menu:SetBackdrop({
            bgFile = CONFIG.TEXTURES.WHITE_BG,
            edgeFile = CONFIG.TEXTURES.WHITE_BG,
            edgeSize = 1,
        })
        menu:SetBackdropColor(0.12, 0.12, 0.14, 0.98)
        menu:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)

        local menuHeight = #self.items * 20 + 4
        menu:SetSize(width, menuHeight)
        menu:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2)

        -- Track this as the active dropdown
        activeDropdownMenu = menu

        for i, item in ipairs(self.items) do
            local btn = CreateFrame("Button", nil, menu)
            btn:SetSize(width - 4, 18)
            btn:SetPoint("TOPLEFT", 2, -2 - (i-1) * 20)

            btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            btn.text:SetPoint("LEFT", 4, 0)
            btn.text:SetText(item)

            btn:SetScript("OnEnter", function(b)
                b.text:SetTextColor(1, 0.82, 0.2)
            end)
            btn:SetScript("OnLeave", function(b)
                b.text:SetTextColor(1, 1, 1)
            end)
            btn:SetScript("OnClick", function()
                self.selected = item
                self.text:SetText(item)
                menu:Hide()
                activeDropdownMenu = nil
                if self.onChange then
                    self.onChange(item)
                end
            end)
        end

        -- Close menu when clicking elsewhere
        menu:SetScript("OnShow", function()
            menu:SetScript("OnUpdate", function()
                if not MouseIsOver(menu) and not MouseIsOver(self) then
                    if IsMouseButtonDown("LeftButton") or IsMouseButtonDown("RightButton") then
                        menu:Hide()
                        activeDropdownMenu = nil
                    end
                end
            end)
        end)

        menu:SetScript("OnHide", function()
            if activeDropdownMenu == menu then
                activeDropdownMenu = nil
            end
        end)
    end)

    return container, dropdown
end

-- Create checkbox
local function CreateCheckbox(parent, label, initialValue, onChange)
    local container = CreateFrame("Button", nil, parent)
    container:SetSize(120, 18)

    local box = container:CreateTexture(nil, "ARTWORK")
    box:SetSize(14, 14)
    box:SetPoint("LEFT", 0, 0)
    box:SetTexture(CONFIG.TEXTURES.WHITE_BG)
    box:SetVertexColor(0.2, 0.2, 0.22, 1)
    container.box = box

    local check = container:CreateTexture(nil, "OVERLAY")
    check:SetSize(12, 12)
    check:SetPoint("CENTER", box, "CENTER", 0, 0)
    check:SetTexture(CONFIG.TEXTURES.CHECKMARK)
    check:SetVertexColor(0.2, 0.8, 0.5, 1)
    container.check = check

    local text = container:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    text:SetPoint("LEFT", box, "RIGHT", 4, 0)
    text:SetText(label)

    container.checked = initialValue
    check:SetShown(initialValue)

    container:SetScript("OnClick", function(self)
        self.checked = not self.checked
        self.check:SetShown(self.checked)
        if onChange then
            onChange(self.checked)
        end
    end)

    return container
end

-- Update sort indicator (text color) on column headers
local function UpdateSortIndicators()
    for _, header in pairs(columnHeaders) do
        if header.sortKey == currentSort then
            -- Active sort column: white text
            header.text:SetTextColor(1, 1, 1)
        else
            -- Inactive columns: gold text
            header.text:SetTextColor(0.99, 0.83, 0.22)
        end
    end
end

-- Create column header button
local function CreateColumnHeader(parent, text, sortKey, x, width, isLeftAligned)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(width, 20)
    btn:SetPoint("LEFT", x, 0)

    btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    btn.text:SetText(text)
    btn.text:SetTextColor(0.99, 0.83, 0.22)  -- Gold color (inactive)

    if isLeftAligned then
        btn.text:SetPoint("LEFT", 0, 0)
    else
        btn.text:SetPoint("RIGHT", 0, 0)
    end

    btn.sortKey = sortKey

    btn:SetScript("OnClick", function(self)
        if currentSort == self.sortKey then
            sortAscending = not sortAscending
        else
            currentSort = self.sortKey
            sortAscending = false
        end
        UpdateSortIndicators()
        HDG_GoblinTab.Refresh()
    end)

    btn:SetScript("OnEnter", function(self)
        if self.sortKey ~= currentSort then
            self.text:SetTextColor(0.8, 0.8, 0.8)  -- Light gray on hover (inactive columns)
        end
    end)
    btn:SetScript("OnLeave", function(self)
        if self.sortKey == currentSort then
            self.text:SetTextColor(1, 1, 1)  -- White for active sort
        else
            self.text:SetTextColor(0.99, 0.83, 0.22)  -- Gold for inactive
        end
    end)

    -- Track this header for sort indicator updates
    columnHeaders[sortKey] = btn

    return btn
end

-- Create the Goblin tab UI
function HDG_GoblinTab.Create()
    if goblinFrame then return end

    goblinFrame = CreateFrame("Frame", "HDG_GoblinFrame", mainFrame, "BackdropTemplate")
    goblinFrame:SetPoint("TOPLEFT", CONFIG.SPACING.PANEL_PADDING, -(CONFIG.BANNER_HEIGHT + CONFIG.TAB_HEIGHT + 8))
    goblinFrame:SetSize(CONFIG.MAIN_FRAME_WIDTH - (CONFIG.SPACING.PANEL_PADDING * 2), CONFIG.PANEL_HEIGHT)
    goblinFrame:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    goblinFrame:SetBackdropColor(unpack(CONFIG.COLORS.PANEL_BG))
    goblinFrame:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    goblinFrame:Hide()
    mainFrame.goblinFrame = goblinFrame

    -- Row 1: Profession filter buttons
    local profRow = CreateFrame("Frame", nil, goblinFrame)
    profRow:SetPoint("TOPLEFT", 8, -6)
    profRow:SetPoint("TOPRIGHT", -8, -6)
    profRow:SetHeight(20)
    goblinFrame.profRow = profRow

    local profLabel = profRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    profLabel:SetPoint("LEFT", 0, 0)
    profLabel:SetText("|cFFB0B0B0Prof:|r")

    -- Profession buttons container (will be populated by RefreshFilterButtons)
    goblinFrame.profButtons = {}

    -- Row 2: Expansion filter buttons
    local expRow = CreateFrame("Frame", nil, goblinFrame)
    expRow:SetPoint("TOPLEFT", 8, -26)
    expRow:SetPoint("TOPRIGHT", -8, -26)
    expRow:SetHeight(20)
    goblinFrame.expRow = expRow

    local expLabel = expRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    expLabel:SetPoint("LEFT", 0, 0)
    expLabel:SetText("|cFFB0B0B0Exp:|r")

    -- Expansion buttons container (will be populated by RefreshFilterButtons)
    goblinFrame.expButtons = {}

    -- Function to refresh filter buttons
    local function RefreshFilterButtons()
        -- Clear existing profession buttons
        for _, btn in ipairs(goblinFrame.profButtons) do
            btn:Hide()
            btn:SetParent(nil)
        end
        goblinFrame.profButtons = {}

        -- Clear existing expansion buttons
        for _, btn in ipairs(goblinFrame.expButtons) do
            btn:Hide()
            btn:SetParent(nil)
        end
        goblinFrame.expButtons = {}

        -- Profession buttons (full names) - use English internally, display localized
        local professions = {"All", "Alchemy", "Blacksmithing", "Cooking", "Enchanting", "Engineering", "Inscription", "Jewelcrafting", "Leatherworking", "Tailoring"}
        local profLocaleKeys = {
            ["All"] = "PROF_ALL",
            ["Alchemy"] = "PROF_ALCHEMY",
            ["Blacksmithing"] = "PROF_BLACKSMITHING",
            ["Cooking"] = "PROF_COOKING",
            ["Enchanting"] = "PROF_ENCHANTING",
            ["Engineering"] = "PROF_ENGINEERING",
            ["Inscription"] = "PROF_INSCRIPTION",
            ["Jewelcrafting"] = "PROF_JEWELCRAFTING",
            ["Leatherworking"] = "PROF_LEATHERWORKING",
            ["Tailoring"] = "PROF_TAILORING",
        }
        local profWidths = {32, 56, 56, 48, 80, 64, 66, 56, 60, 56}
        local xOffset = 30
        for i, prof in ipairs(professions) do
            local isActive = (professionFilter == prof)
            local displayName = L[profLocaleKeys[prof]] or prof
            local btn = HDG_UIHelpers.CreateFilterButton(profRow, displayName, profWidths[i], isActive, function()
                professionFilter = prof  -- Keep English internally for filtering
                RefreshFilterButtons()
                HDG_GoblinTab.Refresh()
            end, GOBLIN_FILTER_OPTIONS)
            btn:SetPoint("LEFT", xOffset, 0)
            btn.fullName = prof  -- Store English name for filter logic
            table.insert(goblinFrame.profButtons, btn)
            xOffset = xOffset + profWidths[i] + 2
        end

        -- Expansion buttons (with expansion colors)
        -- Explicit widths: Classic, TBC, WotLK, Cata, MoP, WoD, Legion, BfA, SL, DF, TWW, MN
        local expWidths = {
            ["Classic"] = 48,
            ["The Burning Crusade"] = 34,
            ["Wrath of the Lich King"] = 46,
            ["Cataclysm"] = 38,
            ["Mists of Pandaria"] = 36,
            ["Warlords of Draenor"] = 38,
            ["Legion"] = 48,
            ["Battle for Azeroth"] = 34,
            ["Shadowlands"] = 28,
            ["Dragonflight"] = 28,
            ["The War Within"] = 38,
            ["Midnight"] = 32,
        }

        local xOffset = 30
        local allActive = (expansionFilter == "All")
        local allBtn = HDG_UIHelpers.CreateFilterButton(expRow, "All", 28, allActive, function()
            expansionFilter = "All"
            RefreshFilterButtons()
            HDG_GoblinTab.Refresh()
        end, GOBLIN_FILTER_OPTIONS)
        allBtn:SetPoint("LEFT", xOffset, 0)
        table.insert(goblinFrame.expButtons, allBtn)
        xOffset = xOffset + 30

        for _, exp in ipairs(HDG_ExpansionData.EXPANSION_ORDER) do
            local abbrev = HDG_ExpansionData.EXPANSION_ABBR[exp] or exp:sub(1,3)
            local isActive = (expansionFilter == exp)
            local expColor = HDG_ExpansionData.GetColor(exp)
            local btnWidth = expWidths[exp] or 36

            local btn = HDG_UIHelpers.CreateFilterButton(expRow, abbrev, btnWidth, isActive, function()
                expansionFilter = exp
                RefreshFilterButtons()
                HDG_GoblinTab.Refresh()
            end, GOBLIN_FILTER_OPTIONS)
            btn:SetPoint("LEFT", xOffset, 0)
            btn.fullName = exp
            btn.expColor = expColor

            -- Apply expansion color to text (brighter for better visibility)
            local colorHex = string.format("%02x%02x%02x", expColor[1] * 255, expColor[2] * 255, expColor[3] * 255)
            if isActive then
                btn.text:SetText("|cFF" .. colorHex .. abbrev .. "|r")
            else
                -- Slightly dimmed version of expansion color when inactive (85% brightness)
                local dimColor = string.format("%02x%02x%02x", math.min(255, expColor[1] * 220), math.min(255, expColor[2] * 220), math.min(255, expColor[3] * 220))
                btn.text:SetText("|cFF" .. dimColor .. abbrev .. "|r")
            end

            -- Tooltip with full expansion name
            btn:SetScript("OnEnter", function(self)
                if not self.isActive then
                    self:SetBackdropColor(unpack(HDG_UIHelpers.STATE_HOVER))
                end
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:AddLine(exp, expColor[1], expColor[2], expColor[3])
                GameTooltip:Show()
            end)
            btn:SetScript("OnLeave", function(self)
                if not self.isActive then
                    self:SetBackdropColor(unpack(HDG_UIHelpers.STATE_INACTIVE))
                end
                GameTooltip:Hide()
            end)

            table.insert(goblinFrame.expButtons, btn)
            xOffset = xOffset + btnWidth + 2
        end
    end

    -- Store reference and call initial refresh
    goblinFrame.RefreshFilterButtons = RefreshFilterButtons
    RefreshFilterButtons()

    -- Price addon toggle (TSM / Auctionator) - on right side of prof row
    local priceToggleContainer = CreateFrame("Frame", nil, profRow, "BackdropTemplate")
    priceToggleContainer:SetSize(100, 20)
    priceToggleContainer:SetPoint("RIGHT", -8, 0)
    priceToggleContainer:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    priceToggleContainer:SetBackdropColor(0.05, 0.05, 0.05, 0.8)

    local tsmBtn = CreateFrame("Button", nil, priceToggleContainer, "BackdropTemplate")
    tsmBtn:SetSize(48, 18)
    tsmBtn:SetPoint("LEFT", 1, 0)
    tsmBtn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    local tsmText = tsmBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    tsmText:SetPoint("CENTER", 0, 0)
    tsmText:SetText("TSM")

    local aucBtn = CreateFrame("Button", nil, priceToggleContainer, "BackdropTemplate")
    aucBtn:SetSize(48, 18)
    aucBtn:SetPoint("LEFT", tsmBtn, "RIGHT", 1, 0)
    aucBtn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    local aucText = aucBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    aucText:SetPoint("CENTER", 0, 0)
    aucText:SetText("Auc")

    -- Update toggle appearance based on current preference
    local function UpdatePriceToggle()
        local availableAddons = HDG_PriceIntegration.GetAvailableAddons()
        local hasTSM = HDG_PriceIntegration.IsTSMAvailable()
        local hasAuc = HDG_PriceIntegration.IsAuctionatorAvailable()

        -- Hide toggle if only one or no addons available
        if #availableAddons < 2 then
            priceToggleContainer:Hide()
            return
        end
        priceToggleContainer:Show()

        -- Determine active addon
        local preferred = HDG_Settings and HDG_Settings.preferredPriceAddon
        local active = preferred
        if not active then
            -- Default: TSM first
            active = hasTSM and "TSM" or (hasAuc and "Auctionator" or nil)
        end

        -- Update button appearance
        if active == "TSM" then
            tsmBtn:SetBackdropColor(0.08, 0.45, 0.42, 0.9)
            tsmText:SetTextColor(1, 1, 1)
            aucBtn:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
            aucText:SetTextColor(0.6, 0.6, 0.6)
        else
            tsmBtn:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
            tsmText:SetTextColor(0.6, 0.6, 0.6)
            aucBtn:SetBackdropColor(0.08, 0.45, 0.42, 0.9)
            aucText:SetTextColor(1, 1, 1)
        end
    end

    tsmBtn:SetScript("OnClick", function()
        if HDG_PriceIntegration.IsTSMAvailable() then
            HDG_Settings.preferredPriceAddon = "TSM"
            UpdatePriceToggle()
            UpdateTSMColumnVisibility()
            HDG_GoblinTab.Refresh()
        end
    end)
    tsmBtn:SetScript("OnEnter", function(self)
        if HDG_Settings.preferredPriceAddon ~= "TSM" then
            self:SetBackdropColor(0.15, 0.15, 0.15, 0.9)
        end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:AddLine("Use TSM Prices")
        GameTooltip:AddLine("TradeSkillMaster market data", 1, 1, 1)
        GameTooltip:Show()
    end)
    tsmBtn:SetScript("OnLeave", function()
        UpdatePriceToggle()
        GameTooltip:Hide()
    end)

    aucBtn:SetScript("OnClick", function()
        if HDG_PriceIntegration.IsAuctionatorAvailable() then
            HDG_Settings.preferredPriceAddon = "Auctionator"
            UpdatePriceToggle()
            UpdateTSMColumnVisibility()
            HDG_GoblinTab.Refresh()
        end
    end)
    aucBtn:SetScript("OnEnter", function(self)
        if HDG_Settings.preferredPriceAddon ~= "Auctionator" then
            self:SetBackdropColor(0.15, 0.15, 0.15, 0.9)
        end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:AddLine("Use Auctionator Prices")
        GameTooltip:AddLine("Auctionator auction data", 1, 1, 1)
        GameTooltip:Show()
    end)
    aucBtn:SetScript("OnLeave", function()
        UpdatePriceToggle()
        GameTooltip:Hide()
    end)

    -- Store reference for refresh
    goblinFrame.UpdatePriceToggle = UpdatePriceToggle

    -- Initial update
    UpdatePriceToggle()

    -- Scan button (for Auctionator) - positioned at right of expRow
    scanButton = CreateFrame("Button", nil, expRow, "BackdropTemplate")
    scanButton:SetSize(70, 18)
    scanButton:SetPoint("RIGHT", expRow, "RIGHT", -8, 0)

    -- Price source text - to the left of scan button
    priceSourceText = expRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    priceSourceText:SetPoint("RIGHT", scanButton, "LEFT", -6, 0)
    priceSourceText:SetJustifyH("RIGHT")
    priceSourceText:SetText("|cFF888888Prices: Loading...|r")

    scanButton:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    scanButton:SetBackdropColor(0.08, 0.45, 0.42, 0.9)
    scanButton:SetBackdropBorderColor(0.1, 0.6, 0.55, 1)
    scanButton.text = scanButton:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    scanButton.text:SetPoint("CENTER", 0, 0)
    scanButton.text:SetText("|cFFFFFFFFScan AH|r")
    scanButton:SetScript("OnClick", function()
        if canScanNow then
            HDG_GoblinTab.TriggerScan()
        end
    end)
    scanButton:SetScript("OnEnter", function(self)
        self:SetBackdropColor(0.1, 0.55, 0.5, 1)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:AddLine("Auctionator Scan")
        GameTooltip:AddLine("Open the Auction House window to trigger a full scan.", 1, 1, 1, true)
        GameTooltip:AddLine(" ")
        local canScan, reason = HDG_GoblinTab.CanTriggerScan()
        if canScan then
            GameTooltip:AddLine("Click to scan now", 0.5, 1, 0.5)
        else
            GameTooltip:AddLine(reason or "Cannot scan", 1, 0.3, 0.3)
        end
        GameTooltip:Show()
    end)
    scanButton:SetScript("OnLeave", function(self)
        local canScan = HDG_GoblinTab.CanTriggerScan()
        if canScan then
            self:SetBackdropColor(0.08, 0.45, 0.42, 0.9)
        else
            self:SetBackdropColor(0.2, 0.2, 0.2, 0.9)
        end
        GameTooltip:Hide()
    end)

    -- Filter row (search box + checkboxes)
    local filterRow = CreateFrame("Frame", nil, goblinFrame)
    filterRow:SetPoint("TOPLEFT", 8, -48)
    filterRow:SetPoint("TOPRIGHT", -8, -48)
    filterRow:SetHeight(20)

    -- Search box
    local searchBox = CreateFrame("EditBox", nil, filterRow, "BackdropTemplate")
    searchBox:SetPoint("LEFT", 0, 0)
    searchBox:SetSize(115, 20)
    searchBox:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false, edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    searchBox:SetBackdropColor(0.05, 0.05, 0.07, 1)
    searchBox:SetBackdropBorderColor(0.3, 0.3, 0.35, 0.8)
    searchBox:SetFontObject("GameFontHighlightSmall")
    searchBox:SetAutoFocus(false)
    searchBox:SetTextInsets(6, 20, 0, 0)
    searchBox:SetMaxLetters(50)

    -- Search placeholder text
    local searchPlaceholder = searchBox:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
    searchPlaceholder:SetPoint("LEFT", 6, 0)
    searchPlaceholder:SetText("Search...")
    searchBox.placeholder = searchPlaceholder

    -- Search clear button (X)
    local searchClear = CreateFrame("Button", nil, searchBox)
    searchClear:SetSize(14, 14)
    searchClear:SetPoint("RIGHT", -3, 0)
    searchClear:Hide()
    local searchClearTex = searchClear:CreateTexture(nil, "ARTWORK")
    searchClearTex:SetAllPoints()
    searchClearTex:SetTexture("Interface\\Buttons\\UI-StopButton")
    searchClear:SetScript("OnClick", function()
        searchBox:SetText("")
        searchBox:ClearFocus()
        searchFilter = ""
        HDG_GoblinTab.Refresh()
    end)

    -- Search text change handler
    searchBox:SetScript("OnTextChanged", function(self, userInput)
        local text = self:GetText()
        if text and text ~= "" then
            searchPlaceholder:Hide()
            searchClear:Show()
        else
            searchPlaceholder:Show()
            searchClear:Hide()
        end
        if userInput then
            searchFilter = text
            HDG_GoblinTab.Refresh()
        end
    end)
    searchBox:SetScript("OnEscapePressed", function(self)
        self:ClearFocus()
    end)
    searchBox:SetScript("OnEnterPressed", function(self)
        self:ClearFocus()
    end)

    -- Hide unprofitable checkbox (moved right for search box)
    local hideUnprofitableCheck = CreateCheckbox(filterRow, L["CHECK_HIDE_UNPROFITABLE"] or "Hide unprofitable", false, function(checked)
        hideUnprofitable = checked
        HDG_GoblinTab.Refresh()
    end)
    hideUnprofitableCheck:SetPoint("LEFT", searchBox, "RIGHT", 12, 0)

    -- Only known recipes checkbox (for current character)
    local onlyKnownCheck = CreateCheckbox(filterRow, L["CHECK_ONLY_KNOWN"] or "Only known (this char)", false, function(checked)
        onlyKnownRecipes = checked
        HDG_GoblinTab.Refresh()
    end)
    onlyKnownCheck:SetPoint("LEFT", hideUnprofitableCheck, "RIGHT", 16, 0)

    -- Item count text (shows filtered/total) - positioned above Margin column
    itemCountText = filterRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    itemCountText:SetPoint("RIGHT", filterRow, "RIGHT", -8, 0)
    itemCountText:SetJustifyH("RIGHT")
    itemCountText:SetText("")

    -- Column headers
    local columnHeaderRow = CreateFrame("Frame", nil, goblinFrame, "BackdropTemplate")
    columnHeaderRow:SetPoint("TOPLEFT", 8, -72)
    columnHeaderRow:SetPoint("TOPRIGHT", -8, -72)
    columnHeaderRow:SetHeight(22)
    columnHeaderRow:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
    })
    columnHeaderRow:SetBackdropColor(0.15, 0.15, 0.17, 1)

    -- Panel width ~826px usable (870 - 16 padding - 28 scrollbar)
    CreateColumnHeader(columnHeaderRow, "Item Name", "name", 8, 180, true)       -- Left-aligned
    CreateColumnHeader(columnHeaderRow, "Lumber", "lumberType", 192, 70, true)   -- Left-aligned (lumber type)

    -- Divider before $/Lum
    local divider1 = columnHeaderRow:CreateTexture(nil, "ARTWORK")
    divider1:SetColorTexture(0.4, 0.4, 0.4, 0.6)
    divider1:SetSize(1, 18)
    divider1:SetPoint("LEFT", 262, 0)

    CreateColumnHeader(columnHeaderRow, "$/Lum", "lumber", 266, 60, false)       -- Per-lumber value
    CreateColumnHeader(columnHeaderRow, "Total", "lumberTotal", 330, 60, false)  -- Total lumber value

    -- Divider after Total
    local divider2 = columnHeaderRow:CreateTexture(nil, "ARTWORK")
    divider2:SetColorTexture(0.4, 0.4, 0.4, 0.6)
    divider2:SetSize(1, 18)
    divider2:SetPoint("LEFT", 392, 0)

    CreateColumnHeader(columnHeaderRow, "Cost", "cost", 394, 60, false)          -- Right-aligned
    CreateColumnHeader(columnHeaderRow, "Sell", "sell", 458, 60, false)          -- Right-aligned
    CreateColumnHeader(columnHeaderRow, "Profit", "profit", 522, 65, false)      -- Right-aligned
    CreateColumnHeader(columnHeaderRow, "%", "margin", 591, 50, false)           -- Right-aligned

    -- Divider after %
    local divider3 = columnHeaderRow:CreateTexture(nil, "ARTWORK")
    divider3:SetColorTexture(0.4, 0.4, 0.4, 0.6)
    divider3:SetSize(1, 18)
    divider3:SetPoint("LEFT", 643, 0)

    -- TSM-only columns (visibility controlled by UpdateTSMColumnVisibility)
    local regionAvgHeader = CreateColumnHeader(columnHeaderRow, "Region Avg", "regionSaleAvg", 645, 90, false)
    columnHeaders["regionSaleAvg"] = regionAvgHeader

    local tsmMarginHeader = CreateColumnHeader(columnHeaderRow, "TSM %", "tsmMargin", 739, 55, false)
    columnHeaders["tsmMargin"] = tsmMarginHeader

    -- Set initial sort indicator
    UpdateSortIndicators()

    -- Set initial TSM column visibility
    UpdateTSMColumnVisibility()

    -- Scroll frame for item list
    scrollFrame = CreateFrame("ScrollFrame", "HDG_GoblinScrollFrame", goblinFrame, "UIPanelScrollFrameTemplate")
    scrollFrame:SetPoint("TOPLEFT", 8, -96)
    scrollFrame:SetPoint("BOTTOMRIGHT", -28, 8)

    scrollContent = CreateFrame("Frame", nil, scrollFrame)
    scrollContent:SetSize(scrollFrame:GetWidth(), 1)
    scrollFrame:SetScrollChild(scrollContent)

    -- Style scrollbar
    local scrollBar = scrollFrame.ScrollBar or _G["HDG_GoblinScrollFrameScrollBar"]
    if scrollBar then
        scrollBar:SetPoint("TOPLEFT", scrollFrame, "TOPRIGHT", 4, -16)
        scrollBar:SetPoint("BOTTOMLEFT", scrollFrame, "BOTTOMRIGHT", 4, 16)
    end

    -- Register for Auctionator scan callbacks
    RegisterScanCallback()
end

-- ============================================================================
-- REFRESH / RENDER
-- ============================================================================

function HDG_GoblinTab.Refresh()
    if not goblinFrame or not goblinFrame:IsShown() then return end

    -- Update scan button state
    UpdateScanButton()

    -- Calculate fresh data
    CalculateProfitData()
    ApplyFiltersAndSort()

    -- Update item count (teal color, white/teal when filtered)
    if itemCountText then
        local filteredCount = #filteredData
        local totalCount = #profitData
        if filteredCount == totalCount then
            itemCountText:SetText(string.format("|cFF14b8a6%d items|r", totalCount))
        else
            itemCountText:SetText(string.format("|cFFFFFFFF%d|r |cFF14b8a6/ %d items|r", filteredCount, totalCount))
        end
    end

    -- Release old rows and hide expansion panel temporarily
    ReleaseAllRows()
    if expansionPanel then
        expansionPanel:Hide()
    end

    -- Render rows
    local yOffset = 0
    for i, item in ipairs(filteredData) do
        local row = AcquireRow()
        row:SetPoint("TOPLEFT", 0, yOffset)
        row:SetPoint("RIGHT", 0, 0)

        -- Alternate row colors
        local bgColor = (i % 2 == 0) and {0.14, 0.14, 0.16, 0.7} or {0.12, 0.12, 0.14, 0.5}
        row.bgColor = bgColor
        row:SetBackdropColor(unpack(bgColor))

        row.itemID = item.itemID
        row.item = item

        -- Color name by expansion (consistent for all items)
        local expColor = HDG_ExpansionData.GetColor(item.expansion)
        local nameColor = string.format("|cFF%02x%02x%02x",
            expColor[1] * 255, expColor[2] * 255, expColor[3] * 255)
        row.nameText:SetText(nameColor .. item.name .. "|r")

        -- Lumber type (expansion-colored localized name + inventory count)
        if item.lumberType then
            local lumberInfo = HDG_ExpansionData.GetLumberInfo(item.lumberType)
            if lumberInfo then
                local lumberExpColor = HDG_ExpansionData.GetColor(lumberInfo.expansion)
                local lumberColorHex = string.format("|cFF%02x%02x%02x",
                    lumberExpColor[1] * 255, lumberExpColor[2] * 255, lumberExpColor[3] * 255)
                -- Get localized material name (strips lumber/wood words)
                local lumberDisplayName = HDG_ExpansionData.GetLumberMaterialName(item.lumberType) or lumberInfo.shortName or lumberInfo.name
                -- Get inventory count
                local invCount = _G.HDG_Data and _G.HDG_Data.GetItemCount(item.lumberType) or 0
                local countText = invCount > 0 and string.format(" |cFF888888(%d)|r", invCount) or ""
                row.lumberTypeText:SetText(lumberColorHex .. lumberDisplayName .. "|r" .. countText)
            else
                row.lumberTypeText:SetText("|cFF555555-|r")
            end
        else
            row.lumberTypeText:SetText("|cFF555555-|r")
        end

        -- Sell price
        if item.sellPrice then
            row.sellText:SetText(HDG_PriceIntegration.FormatGoldOnly(item.sellPrice))
        else
            row.sellText:SetText("|cFF555555?|r")
        end

        -- Material cost
        if item.materialCost and item.materialCost > 0 then
            row.costText:SetText(HDG_PriceIntegration.FormatGoldOnly(item.materialCost))
        else
            row.costText:SetText("|cFF555555-|r")
        end

        -- Profit (colored)
        if item.profit then
            local profitColor = item.profit >= 0 and "|cFF88FF88+" or "|cFFFF5555"
            row.profitText:SetText(profitColor .. HDG_PriceIntegration.FormatGoldOnly(math.abs(item.profit)) .. "|r")
        else
            row.profitText:SetText("|cFF555555?|r")
        end

        -- Margin
        if item.margin then
            local marginColor = item.margin >= 0 and "|cFF88FF88" or "|cFFFF5555"
            row.marginText:SetText(marginColor .. string.format("%.0f%%", item.margin) .. "|r")
        else
            row.marginText:SetText("|cFF555555-|r")
        end

        -- Region Avg (TSM only)
        local isTSMActive = GetActivePriceAddonName() == "TSM"
        if isTSMActive then
            if item.regionSaleAvg then
                row.regionAvgText:SetText("|cFF88CCFF" .. HDG_PriceIntegration.FormatGoldOnly(item.regionSaleAvg) .. "|r")
            else
                row.regionAvgText:SetText("|cFF555555-|r")
            end
            row.regionAvgText:Show()

            -- TSM Margin (based on region avg) - same color as Region Avg column
            if item.tsmMargin then
                row.tsmMarginText:SetText("|cFF88CCFF" .. string.format("%.0f%%", item.tsmMargin) .. "|r")
            else
                row.tsmMarginText:SetText("|cFF555555-|r")
            end
            row.tsmMarginText:Show()
        else
            row.regionAvgText:Hide()
            row.tsmMarginText:Hide()
        end

        -- Per-lumber value ($/Lum)
        if item.lumberValue and item.lumberQty > 0 then
            local lumberColor = item.lumberValue >= 0 and "|cFFFFAA00" or "|cFFFF5555"
            row.lumberText:SetText(lumberColor .. HDG_PriceIntegration.FormatGoldOnly(item.lumberValue) .. "|r")
        else
            row.lumberText:SetText("|cFF555555-|r")
        end

        -- Total lumber value (LumTot = per-lumber × qty)
        if item.lumberValue and item.lumberQty > 0 then
            local totalLumberValue = item.lumberValue * item.lumberQty
            local lumberColor = totalLumberValue >= 0 and "|cFFFFAA00" or "|cFFFF5555"
            row.lumberTotalText:SetText(lumberColor .. HDG_PriceIntegration.FormatGoldOnly(totalLumberValue) .. "|r")
        else
            row.lumberTotalText:SetText("|cFF555555-|r")
        end

        yOffset = yOffset - 26

        -- Show expansion panel if this row is expanded
        if expandedRowKey == item.recipeKey then
            local panelHeight = ShowExpansionPanel(row, item)
            yOffset = yOffset - panelHeight
        end
    end

    -- Hide expansion panel if expanded row is no longer visible
    if expansionPanel and expandedRowKey then
        local found = false
        for _, item in ipairs(filteredData) do
            if item.recipeKey == expandedRowKey then
                found = true
                break
            end
        end
        if not found then
            HideExpansionPanel()
        end
    end

    -- Update scroll content height
    scrollContent:SetHeight(math.abs(yOffset) + 10)
end

-- Show the tab
function HDG_GoblinTab.Show()
    if not goblinFrame then
        HDG_GoblinTab.Create()
    end
    goblinFrame:Show()
    HDG_GoblinTab.Refresh()
end

-- Hide the tab
function HDG_GoblinTab.Hide()
    -- Close any open dropdown menus
    CloseActiveDropdown()
    -- Hide expansion panel
    HideExpansionPanel()
    if goblinFrame then
        goblinFrame:Hide()
    end
end

-- Export to privateDB for HDG_UI access
privateDB.HDG_GoblinTab = HDG_GoblinTab
