local _, privateDB = ...

-- ============================================================================
-- RECIPES TAB MODULE
-- Manages all 3 panels of the Recipes tab:
--   Left: Recipe/Profession list
--   Middle: Crafting Queue (Shopping)
--   Right: Required Materials
-- Lumber Tracker is in separate module (HDG_LumberTracker.lua) for widget mode
-- Extracted from HDG_UI.lua in v2.0.3 for maintainability
-- ============================================================================

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

local mainFrame = nil

-- Lowercase name cache (avoids repeated :lower() calls during search filtering)
local recipeLowerNameCache = {}

local function GetRecipeLowerName(recipe)
    local key = recipe.recipeKey or recipe.name
    if not key then return "" end
    if not recipeLowerNameCache[key] then
        recipeLowerNameCache[key] = (recipe.name or ""):lower()
    end
    return recipeLowerNameCache[key]
end

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

-- Get recipe from ProfessionsDB by output itemID (v2 format uses recipeID as key)
local function GetRecipeByItemID(itemID)
    if HDG_PowerCrafting and HDG_PowerCrafting.GetRecipeByItemID then
        return HDG_PowerCrafting.GetRecipeByItemID(itemID)
    end
    return nil
end

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

-- Per-character profession filter helpers (uses HDG_Data.GetCharacterKey)

function HDG_RecipesTab.GetProfessionFilter()
    local charKey = HDG_Data.GetCharacterKey()
    if charKey and HDG_Settings.characterProfessionFilter and HDG_Settings.characterProfessionFilter[charKey] then
        return HDG_Settings.characterProfessionFilter[charKey]
    end
    return "All"
end

function HDG_RecipesTab.SetProfessionFilter(professionName)
    local charKey = HDG_Data.GetCharacterKey()
    if charKey then
        HDG_Settings.characterProfessionFilter = HDG_Settings.characterProfessionFilter or {}
        HDG_Settings.characterProfessionFilter[charKey] = professionName
    end
    -- Also set the legacy global for backwards compatibility
    HDG_Settings.professionFilter = professionName
end

-- Use centralized ClearScrollContent from UIHelpers
local function ClearScrollContent(scrollContent)
    HDG_UIHelpers.ClearScrollContent(scrollContent)
end

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

local function GetShoppingItemColor(item)
    if item.expansion then
        local expColor = HDG_ExpansionData.GetColor(item.expansion)
        return { expColor[1] * 0.5, expColor[2] * 0.5, expColor[3] * 0.5, 0.4 }
    end
    return { 0.08, 0.08, 0.08, 0.85 }
end

-- Track which shopping list items are expanded to show subsidiary recipes
-- Key format: "itemID" for top-level, "parentID:itemID" for nested
-- Value is true/false
local expandedShoppingItems = {}

-- Get subsidiary recipes (crafted sub-components) for a shopping list item or intermediate
-- Returns array of { itemID, name, qty, profession, recipeID, slots }
-- Can accept either a shopping list item (with .reagents array) or a ProfessionsDB recipe (with .slots array)
local function GetSubsidiaryRecipes(item, multiplier)
    local subsidiaries = {}
    multiplier = multiplier or 1

    if not item then return subsidiaries end
    if not HDG_ProfessionsDB then return subsidiaries end

    -- Handle both shopping list format (array) and ProfessionsDB format (keyed table)
    local reagentsList = item.reagents
    if not reagentsList then return subsidiaries end

    -- Detect format: array vs keyed table
    local isArray = reagentsList[1] ~= nil

    if isArray then
        -- Shopping list format: array of { id, amount, name }
        for _, reagent in ipairs(reagentsList) do
            local reagentID = reagent.id
            local source = HDG_ReagentsDB and HDG_ReagentsDB[reagentID]

            if source == "Crafted" then
                local recipe = GetRecipeByItemID(reagentID)
                if recipe then
                    local qtyNeeded = (reagent.amount or reagent.qty or 1) * (item.quantity or 1) * multiplier
                    table.insert(subsidiaries, {
                        itemID = reagentID,
                        name = recipe.name or reagent.name or ("Item " .. reagentID),
                        qty = qtyNeeded,
                        profession = recipe.profession,
                        expansion = recipe.expansion,
                        recipeID = recipe.recipeID,
                        slots = recipe.slots,
                    })
                end
            end
        end
    else
        -- ProfessionsDB format: keyed table { [reagentID] = qty }
        for reagentID, qty in pairs(reagentsList) do
            local source = HDG_ReagentsDB and HDG_ReagentsDB[reagentID]

            if source == "Crafted" then
                local recipe = GetRecipeByItemID(reagentID)
                if recipe then
                    local qtyNeeded = qty * multiplier
                    table.insert(subsidiaries, {
                        itemID = reagentID,
                        name = recipe.name or ("Item " .. reagentID),
                        qty = qtyNeeded,
                        profession = recipe.profession,
                        expansion = recipe.expansion,
                        recipeID = recipe.recipeID,
                        slots = recipe.slots,
                    })
                end
            end
        end
    end

    -- Sort by profession then name
    table.sort(subsidiaries, function(a, b)
        if a.profession ~= b.profession then
            return (a.profession or "") < (b.profession or "")
        end
        return (a.name or "") < (b.name or "")
    end)

    return subsidiaries
end

-- Check if item has any subsidiary recipes (works with both formats)
local function HasSubsidiaryRecipes(item)
    if not item or not item.reagents then return false end

    local reagentsList = item.reagents
    local isArray = reagentsList[1] ~= nil

    if isArray then
        for _, reagent in ipairs(reagentsList) do
            local source = HDG_ReagentsDB and HDG_ReagentsDB[reagent.id]
            if source == "Crafted" and GetRecipeByItemID(reagent.id) then
                return true
            end
        end
    else
        for reagentID, _ in pairs(reagentsList) do
            local source = HDG_ReagentsDB and HDG_ReagentsDB[reagentID]
            if source == "Crafted" and GetRecipeByItemID(reagentID) then
                return true
            end
        end
    end
    return false
end

-- ============================================================================
-- MATERIALS FUNCTIONS
-- ============================================================================

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

-- Aggregate materials from shopping list
function HDG_RecipesTab.AggregateMaterials(shoppingList)
    local materials = {}

    for _, item in ipairs(shoppingList) do
        if item.reagents then
            for reagentKey, reagent in pairs(item.reagents) do
                local matID = reagent.id or reagentKey

                -- Skip finishing reagents (optional quality boosters like Illustrious Insight)
                local reagentSource = HDG_ReagentsDB and HDG_ReagentsDB[matID]
                if reagentSource ~= "Ignore" then
                    if not materials[matID] then
                        local matSource = reagent.source
                        if (not matSource or matSource == "Unknown") and HDG_ReagentsDB then
                            matSource = HDG_ReagentsDB[matID] or "Unknown"
                        end

                        materials[matID] = {
                            id = matID,
                            needed = 0,
                            have = Data().GetItemCountWithVariants(matID),
                            sources = {},
                            expansion = item.expansion,
                            source = matSource
                        }
                    end

                    local reagentAmount = reagent.amount or reagent.qty or reagent.count or 1
                    local amountForThis = reagentAmount * item.quantity
                    materials[matID].needed = materials[matID].needed + amountForThis

                    local recipeName = item.name or ("Item " .. (item.id or "?"))
                    if not materials[matID].sources[recipeName] then
                        materials[matID].sources[recipeName] = {
                            count = item.quantity,
                            perCraft = reagentAmount,
                            total = amountForThis
                        }
                    else
                        materials[matID].sources[recipeName].count = materials[matID].sources[recipeName].count +
                            item.quantity
                        materials[matID].sources[recipeName].total = materials[matID].sources[recipeName].total +
                            amountForThis
                    end

                    if item.expansion and materials[matID].expansion ~= item.expansion then
                        materials[matID].expansion = item.expansion
                    end
                end
            end
        end
    end

    return materials
end

-- Aggregate raw materials using Power Crafting (expands crafted reagents)
-- Returns: materials table AND intermediates table (crafted sub-components)
function HDG_RecipesTab.AggregateRawMaterials(shoppingList)
    local materials = {}
    local intermediates = {} -- Crafted sub-components with their raw material children

    for _, item in ipairs(shoppingList) do
        if item.reagents and HDG_PowerCrafting then
            -- Convert shopping list reagents format to HDG_DecorDB format for Power Crafting
            -- Handle both array format (from Recipes tab) and keyed table format (from Preview tab)
            local reagentsForGraph = {}
            for reagentKey, reagent in pairs(item.reagents) do
                -- Array format: reagent = {id=123, name="...", amount=5}
                -- Keyed format: reagentKey = 123, reagent = {name="...", qty=5}
                local reagentID = reagent.id or reagentKey
                local reagentQty = reagent.amount or reagent.qty or 1
                local reagentName = reagent.name or ("Item " .. reagentID)
                reagentsForGraph[reagentID] = { name = reagentName, qty = reagentQty }
            end

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

            -- Build dependency graph
            local graph = HDG_PowerCrafting.BuildDependencyGraph(pseudoRecipe)
            if graph then
                -- Process intermediate crafted items (depth > 0 means it's a sub-component)
                if graph.nodes then
                    for nodeID, node in pairs(graph.nodes) do
                        if node.isCrafted then
                            -- This is a crafted intermediate
                            local qty = node.qty * item.quantity
                            if not intermediates[nodeID] then
                                intermediates[nodeID] = {
                                    id = nodeID,
                                    name = node.name,
                                    needed = 0,
                                    have = Data().GetItemCountWithVariants(nodeID),
                                    source = "Crafted",
                                    expansion = item.expansion,
                                    children = {}, -- Raw materials needed for this
                                    sources = {}
                                }
                            end
                            intermediates[nodeID].needed = intermediates[nodeID].needed + qty

                            -- Track which decor recipes use this intermediate
                            local recipeName = item.name or ("Item " .. (item.id or "?"))
                            if not intermediates[nodeID].sources[recipeName] then
                                intermediates[nodeID].sources[recipeName] = { count = item.quantity, total = qty }
                            end

                            -- Find the raw materials that go into this intermediate
                            local craftRecipe = HDG_PowerCrafting.GetCraftedRecipe(nodeID)
                            if craftRecipe then
                                for subMatID, subQty in pairs(craftRecipe) do
                                    local subSource = HDG_ReagentsDB and HDG_ReagentsDB[subMatID] or "Unknown"
                                    -- Skip finishing reagents (optional quality boosters)
                                    if subSource ~= "Ignore" then
                                        local subName = HDG_PowerCrafting.GetItemName(subMatID)
                                        if not intermediates[nodeID].children[subMatID] then
                                            intermediates[nodeID].children[subMatID] = {
                                                id = subMatID,
                                                name = subName,
                                                perCraft = subQty,
                                                source = subSource
                                            }
                                        end
                                    end
                                end
                                -- Consolidate quality rank variants by name (R1/R2/R3 are alternatives, not additives)
                                local consolidatedChildren = {}
                                for childID, childData in pairs(intermediates[nodeID].children) do
                                    local name = childData.name or "Unknown"
                                    if consolidatedChildren[name] then
                                        -- Same-named items are quality rank alternatives - take max qty
                                        consolidatedChildren[name].perCraft = math.max(
                                            consolidatedChildren[name].perCraft,
                                            childData.perCraft
                                        )
                                    else
                                        consolidatedChildren[name] = {
                                            id = childID,
                                            name = name,
                                            perCraft = childData.perCraft,
                                            source = childData.source
                                        }
                                    end
                                end
                                intermediates[nodeID].children = consolidatedChildren
                            end
                        end
                    end
                end

                -- Process raw materials (leaf nodes)
                if graph.rawMaterials then
                    for matID, rawMat in pairs(graph.rawMaterials) do
                        local qty = rawMat.qty * item.quantity
                        if not materials[matID] then
                            materials[matID] = {
                                id = matID,
                                needed = 0,
                                have = Data().GetItemCountWithVariants(matID),
                                sources = {},
                                expansion = item.expansion,
                                source = rawMat.source
                            }
                        end
                        materials[matID].needed = materials[matID].needed + qty

                        local recipeName = item.name or ("Item " .. (item.id or "?"))
                        if not materials[matID].sources[recipeName] then
                            materials[matID].sources[recipeName] = {
                                count = item.quantity,
                                perCraft = rawMat.qty,
                                total = qty
                            }
                        else
                            materials[matID].sources[recipeName].count = materials[matID].sources[recipeName].count +
                            item.quantity
                            materials[matID].sources[recipeName].total = materials[matID].sources[recipeName].total + qty
                        end
                    end
                end
            else
                -- Fallback to direct materials if graph building fails
                for _, reagent in ipairs(item.reagents) do
                    local matID = reagent.id
                    if not materials[matID] then
                        local matSource = reagent.source
                        if (not matSource or matSource == "Unknown") and HDG_ReagentsDB then
                            matSource = HDG_ReagentsDB[matID] or "Unknown"
                        end
                        materials[matID] = {
                            id = matID,
                            needed = 0,
                            have = Data().GetItemCountWithVariants(matID),
                            sources = {},
                            expansion = item.expansion,
                            source = matSource
                        }
                    end
                    local reagentAmount = reagent.amount or reagent.qty or reagent.count or 1
                    local amountForThis = reagentAmount * item.quantity
                    materials[matID].needed = materials[matID].needed + amountForThis

                    local recipeName = item.name or ("Item " .. (item.id or "?"))
                    if not materials[matID].sources[recipeName] then
                        materials[matID].sources[recipeName] = {
                            count = item.quantity,
                            perCraft = reagentAmount,
                            total = amountForThis
                        }
                    end
                end
            end
        end
    end

    return materials, intermediates
end

-- Filter and sort materials (excludes lumber)
function HDG_RecipesTab.FilterAndSortMaterials(materials)
    local matArray = {}

    for _, mat in pairs(materials) do
        if not LUMBER_IDS[mat.id] then
            table.insert(matArray, mat)
        end
    end

    table.sort(matArray, function(a, b)
        if not a or not b then return false end
        local aName = Data().GetCachedItemName(a.id) or ""
        local bName = Data().GetCachedItemName(b.id) or ""
        return aName < bName
    end)

    return matArray
end

-- Categorize materials by source
function HDG_RecipesTab.CategorizeMaterials(matArray)
    local categories = {
        vendor = { name = L["MAT_FROM_VENDOR"] or "From Vendor", icon = "Interface\\Icons\\INV_Misc_Coin_02", items = {} },
        gather = { name = L["MAT_FROM_GATHERING"] or "From Gathering / AH", icon = "Interface\\Icons\\INV_Misc_Map_01", items = {} },
        craft = { name = L["MAT_FROM_CRAFTING"] or "From Crafting / AH", icon = "Interface\\Icons\\Trade_Engineering", items = {} },
        unknown = { name = "Other", icon = "Interface\\Icons\\INV_Misc_QuestionMark", items = {} }
    }

    local sourceToCategory = {
        ["Vendor"] = "vendor",
        ["Gathering"] = "gather",
        ["Crafted"] = "craft",
        ["Drop"] = "gather",
        ["Farming"] = "gather",
        ["Unknown"] = "unknown",
    }

    for _, mat in ipairs(matArray) do
        local source = mat.source or "Unknown"
        -- Handle "Vendor:XX" format (e.g., "Vendor:20" for vendor price)
        local baseSource = source:match("^([^:]+)") or source
        local categoryKey = sourceToCategory[baseSource] or "unknown"
        table.insert(categories[categoryKey].items, mat)
    end

    -- Sort within categories using centralized expansion order
    for _, category in pairs(categories) do
        table.sort(category.items, function(a, b)
            local aOrder = HDG_ExpansionData.GetSortOrder(a.expansion or "Unknown")
            local bOrder = HDG_ExpansionData.GetSortOrder(b.expansion or "Unknown")
            if aOrder ~= bOrder then return aOrder < bOrder end

            local aName = Data().GetCachedItemName(a.id) or ""
            local bName = Data().GetCachedItemName(b.id) or ""
            return aName < bName
        end)
    end

    return categories
end

-- Check if player can craft a specific recipe with current inventory
-- Returns: canCraft (bool), maxQuantity (number), missingMaterials (table or nil), materialPercentage (number 0-100)
function HDG_RecipesTab.CanCraftRecipe(recipe)
    if not recipe or not recipe.reagents or #recipe.reagents == 0 then
        return false, 0, nil, 0
    end

    local maxCraftable = 999999 -- Start with a very high number
    local missingMats = {}
    local canCraft = true

    -- Track material availability for quantity-weighted percentage calculation
    -- Formula: sum(min(have, need)) / sum(need) * 100
    -- This weights each material by how much is needed, not treating each type equally
    local totalQuantityNeeded = 0
    local totalQuantityHave = 0

    for _, reagent in ipairs(recipe.reagents) do
        -- Use GetItemCountWithVariants to count all quality ranks (R1+R2+R3)
        local have = Data().GetItemCountWithVariants(reagent.id)
        local need = reagent.amount or 1

        -- Accumulate for quantity-weighted percentage (cap have at need, don't count excess)
        totalQuantityNeeded = totalQuantityNeeded + need
        totalQuantityHave = totalQuantityHave + math.min(have, need)

        if have < need then
            -- Missing this material
            canCraft = false
            table.insert(missingMats, {
                id = reagent.id,
                have = have,
                need = need,
                missing = need - have
            })
        else
            -- Fully have this material
            -- Calculate how many times we could craft based on this material
            local possibleFromThisMat = math.floor(have / need)
            if possibleFromThisMat < maxCraftable then
                maxCraftable = possibleFromThisMat
            end
        end
    end

    -- Calculate quantity-weighted material percentage
    local materialPercentage = totalQuantityNeeded > 0 and (totalQuantityHave / totalQuantityNeeded * 100) or 0

    if not canCraft then
        return false, 0, missingMats, materialPercentage
    end

    return true, maxCraftable, nil, 100
end

-- ============================================================================
-- CRAFT CONFIRMATION DIALOG
-- ============================================================================

local craftConfirmFrame = nil

-- Show craft confirmation dialog
function HDG_RecipesTab.ShowCraftConfirmation(item, shoppingListIndex)
    if not item or not item.spell then return end

    local queuedQty = item.quantity or 1
    local _, maxCraftable = HDG_RecipesTab.CanCraftRecipe(item)
    maxCraftable = maxCraftable or 0
    -- Use the lesser of queued and craftable (can't craft more than you have mats for)
    local totalQty = math.min(queuedQty, maxCraftable)
    if totalQty <= 0 then totalQty = 1 end -- Fallback to 1 if something went wrong

    -- Create the confirmation frame if it doesn't exist
    if not craftConfirmFrame then
        craftConfirmFrame = CreateFrame("Frame", "HDG_CraftConfirmDialog", UIParent, "BackdropTemplate")
        craftConfirmFrame:SetSize(380, 165)
        craftConfirmFrame:SetPoint("CENTER", 0, 100)
        craftConfirmFrame:SetFrameStrata("DIALOG")
        craftConfirmFrame:SetBackdrop({
            bgFile = CONFIG.TEXTURES.WHITE_BG,
            edgeFile = CONFIG.TEXTURES.WHITE_BG,
            tile = false,
            edgeSize = 2,
            insets = { left = 0, right = 0, top = 0, bottom = 0 }
        })
        craftConfirmFrame:SetBackdropColor(0.1, 0.1, 0.12, 0.95)
        craftConfirmFrame:SetBackdropBorderColor(0.08, 0.72, 0.65, 1)
        craftConfirmFrame:EnableMouse(true)
        craftConfirmFrame:SetMovable(true)
        craftConfirmFrame:RegisterForDrag("LeftButton")
        craftConfirmFrame:SetScript("OnDragStart", craftConfirmFrame.StartMoving)
        craftConfirmFrame:SetScript("OnDragStop", craftConfirmFrame.StopMovingOrSizing)

        -- Title
        local title = craftConfirmFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
        title:SetPoint("TOP", 0, -12)
        title:SetText("|cFF14b8a6Craft Confirmation|r")
        craftConfirmFrame.title = title

        -- Item text
        local itemText = craftConfirmFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        itemText:SetPoint("TOP", 0, -40)
        itemText:SetWidth(360)
        craftConfirmFrame.itemText = itemText

        -- Info text
        local infoText = craftConfirmFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        infoText:SetPoint("TOP", 0, -62)
        infoText:SetWidth(360)
        infoText:SetTextColor(0.7, 0.7, 0.7)
        craftConfirmFrame.infoText = infoText

        -- API note text
        local apiNote = craftConfirmFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        apiNote:SetPoint("TOP", 0, -80)
        apiNote:SetWidth(360)
        apiNote:SetTextColor(0.5, 0.5, 0.5)
        apiNote:SetText("(Profession window opened - required by Blizzard API)")
        craftConfirmFrame.apiNote = apiNote

        -- Helper function to create styled button
        local function CreateStyledButton(parent, width, color, hoverColor)
            local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
            btn:SetSize(width, 28)
            btn:SetBackdrop({
                bgFile = CONFIG.TEXTURES.WHITE_BG,
                edgeFile = CONFIG.TEXTURES.WHITE_BG,
                tile = false,
                edgeSize = 1,
            })
            btn:SetBackdropColor(unpack(color))
            btn:SetBackdropBorderColor(color[1] + 0.1, color[2] + 0.1, color[3] + 0.1, 1)

            local btnText = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            btnText:SetPoint("CENTER", 0, 0)
            btn.text = btnText

            btn:SetScript("OnEnter", function(self)
                self:SetBackdropColor(unpack(hoverColor))
            end)
            btn:SetScript("OnLeave", function(self)
                self:SetBackdropColor(unpack(color))
            end)
            return btn
        end

        -- Layout: [Craft N] [-] [+] [Craft Max] [Cancel]
        -- Craft button (green) - crafts selected quantity
        local craftBtn = CreateStyledButton(craftConfirmFrame, 70, { 0.15, 0.7, 0.15, 1 }, { 0.2, 0.85, 0.2, 1 })
        craftBtn:SetPoint("BOTTOMLEFT", 12, 15)
        craftBtn.text:SetText("|cFFFFFFFFCraft 1|r")
        craftConfirmFrame.craftBtn = craftBtn

        -- Minus button (orange)
        local minusBtn = CreateStyledButton(craftConfirmFrame, 28, { 0.6, 0.35, 0.1, 1 }, { 0.75, 0.45, 0.15, 1 })
        minusBtn:SetPoint("LEFT", craftBtn, "RIGHT", 6, 0)
        minusBtn.text:SetText("|cFFFFFFFF-|r")
        craftConfirmFrame.minusBtn = minusBtn

        -- Plus button (orange)
        local plusBtn = CreateStyledButton(craftConfirmFrame, 28, { 0.6, 0.35, 0.1, 1 }, { 0.75, 0.45, 0.15, 1 })
        plusBtn:SetPoint("LEFT", minusBtn, "RIGHT", 4, 0)
        plusBtn.text:SetText("|cFFFFFFFF+|r")
        craftConfirmFrame.plusBtn = plusBtn

        -- Craft Max button (teal)
        local craftMaxBtn = CreateStyledButton(craftConfirmFrame, 70, { 0.08, 0.45, 0.40, 1 }, { 0.12, 0.55, 0.50, 1 })
        craftMaxBtn:SetPoint("LEFT", plusBtn, "RIGHT", 6, 0)
        craftConfirmFrame.craftMaxBtn = craftMaxBtn

        -- Cancel button (gray)
        local cancelBtn = CreateStyledButton(craftConfirmFrame, 60, { 0.25, 0.25, 0.25, 1 }, { 0.35, 0.35, 0.35, 1 })
        cancelBtn:SetPoint("BOTTOMRIGHT", -12, 15)
        cancelBtn.text:SetText("|cFFFFFFFFCancel|r")
        cancelBtn:SetScript("OnClick", function()
            craftConfirmFrame:Hide()
        end)

        -- Close on Escape
        craftConfirmFrame:SetScript("OnKeyDown", function(self, key)
            if key == "ESCAPE" then
                self:SetPropagateKeyboardInput(false)
                self:Hide()
            else
                self:SetPropagateKeyboardInput(true)
            end
        end)

        -- Checkbox: Keep profession window open
        local keepOpenCheck = CreateFrame("CheckButton", nil, craftConfirmFrame, "UICheckButtonTemplate")
        keepOpenCheck:SetSize(24, 24)
        keepOpenCheck:SetPoint("TOPLEFT", 12, -95)
        keepOpenCheck:SetChecked(not HDG_Settings.closeProfWindowAfterCraft)
        keepOpenCheck:SetScript("OnClick", function(self)
            HDG_Settings.closeProfWindowAfterCraft = not self:GetChecked()
        end)
        craftConfirmFrame.keepOpenCheck = keepOpenCheck

        local keepOpenLabel = craftConfirmFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        keepOpenLabel:SetPoint("LEFT", keepOpenCheck, "RIGHT", 2, 0)
        keepOpenLabel:SetText("Keep profession window open after craft")
        keepOpenLabel:SetTextColor(0.7, 0.7, 0.7)

        craftConfirmFrame:Hide()
    end

    -- Update checkbox state (in case setting changed elsewhere)
    craftConfirmFrame.keepOpenCheck:SetChecked(not HDG_Settings.closeProfWindowAfterCraft)

    -- Track current selected quantity
    craftConfirmFrame.selectedQty = 1
    craftConfirmFrame.maxQty = totalQty

    -- Helper to update craft button text
    local function UpdateCraftBtnText()
        craftConfirmFrame.craftBtn.text:SetText(string.format("|cFFFFFFFFCraft %d|r", craftConfirmFrame.selectedQty))
    end

    -- Update dialog content
    local localizedItemName = Data().GetLocalizedName(item.id, item.name)
    craftConfirmFrame.itemText:SetText(string.format("|cFFFFFFFF%s|r", localizedItemName))
    -- Show +/- and Max buttons if player can craft more than 1 (based on materials)
    if maxCraftable > 1 then
        -- Show queued and craftable amounts
        local infoStr = string.format("Queued: %d | Can craft: |cFF00FF00%d|r", queuedQty, maxCraftable)
        craftConfirmFrame.infoText:SetText(infoStr)
        -- Max button shows how many you CAN craft (not limited by queue)
        craftConfirmFrame.craftMaxBtn.text:SetText(string.format("|cFFFFFFFFMax %d|r", maxCraftable))
        craftConfirmFrame.craftMaxBtn:Show()
        craftConfirmFrame.minusBtn:Show()
        craftConfirmFrame.plusBtn:Show()
        -- Update maxQty to be based on craftable, not queued
        craftConfirmFrame.maxQty = maxCraftable
    else
        craftConfirmFrame.infoText:SetText("Craft this item?")
        craftConfirmFrame.craftMaxBtn:Hide()
        craftConfirmFrame.minusBtn:Hide()
        craftConfirmFrame.plusBtn:Hide()
        craftConfirmFrame.maxQty = 1
    end

    -- Store item data
    craftConfirmFrame.currentItem = item
    craftConfirmFrame.currentIndex = shoppingListIndex

    -- Reset quantity and update button text
    craftConfirmFrame.selectedQty = 1
    UpdateCraftBtnText()

    -- Set up Craft button click (crafts selected quantity)
    craftConfirmFrame.craftBtn:SetScript("OnClick", function()
        HDG_RecipesTab.ExecuteCraft(item, shoppingListIndex, craftConfirmFrame.selectedQty)
        craftConfirmFrame:Hide()
    end)

    -- Set up minus button (decrement quantity)
    craftConfirmFrame.minusBtn:SetScript("OnClick", function()
        if craftConfirmFrame.selectedQty > 1 then
            craftConfirmFrame.selectedQty = craftConfirmFrame.selectedQty - 1
            UpdateCraftBtnText()
        end
    end)

    -- Set up plus button (increment quantity)
    craftConfirmFrame.plusBtn:SetScript("OnClick", function()
        if craftConfirmFrame.selectedQty < craftConfirmFrame.maxQty then
            craftConfirmFrame.selectedQty = craftConfirmFrame.selectedQty + 1
            UpdateCraftBtnText()
        end
    end)

    -- Set up Craft Max button click (craft all you can based on materials)
    craftConfirmFrame.craftMaxBtn:SetScript("OnClick", function()
        HDG_RecipesTab.ExecuteCraft(item, shoppingListIndex, craftConfirmFrame.maxQty)
        craftConfirmFrame:Hide()
    end)

    craftConfirmFrame:Show()
end

-- Execute the actual crafting
-- quantity parameter allows crafting 1 or all
-- Note: Profession window should already be open when this is called
function HDG_RecipesTab.ExecuteCraft(item, shoppingListIndex, quantity)
    if not item or not item.spell then
        print("|cFFFF6666[HDG]|r Invalid recipe data.")
        return
    end

    -- Default to 1 if not specified
    quantity = quantity or 1
    local recipeID = item.spell

    -- Check if C_TradeSkillUI is available
    if not C_TradeSkillUI then
        print("|cFFFF6666[HDG]|r C_TradeSkillUI not available.")
        return
    end

    -- Check if profession window is open (should be, since we open it before showing confirmation)
    local isTradeSkillOpen = C_TradeSkillUI.IsTradeSkillReady and C_TradeSkillUI.IsTradeSkillReady()
    if not isTradeSkillOpen then
        print("|cFFFFCC00[HDG]|r Profession window not ready. Please try again.")
        return
    end

    -- Set up pending craft tracking for multi-craft scenarios
    -- This tells the event handler how many crafts to expect before closing window
    if privateDB.pendingCraftInfo then
        privateDB.pendingCraftInfo.itemID = item.id
        privateDB.pendingCraftInfo.remaining = quantity
    end

    -- Craft directly - window is already open
    local success, err = pcall(function()
        C_TradeSkillUI.CraftRecipe(recipeID, quantity, {})
    end)
    if success then
        local localizedCraftName = Data().GetLocalizedName(item.id, item.name)
        print(string.format("|cFF14b8a6[HDG]|r Crafting %dx %s...", quantity, localizedCraftName))
    else
        print(string.format("|cFFFF6666[HDG]|r CraftRecipe failed: %s", tostring(err)))
        -- Clear pending tracking on failure
        if privateDB.pendingCraftInfo then
            privateDB.pendingCraftInfo.itemID = nil
            privateDB.pendingCraftInfo.remaining = 0
        end
    end
end

-- Get all craftable recipes for a profession (or all professions)
-- Returns table of { recipe = recipe, maxQuantity = number }
function HDG_RecipesTab.GetCraftableRecipes(professionFilter)
    local craftable = {}

    if not privateDB.professions then return craftable end

    for _, profession in ipairs(privateDB.professions) do
        -- Apply profession filter if specified
        if not professionFilter or professionFilter == "All" or profession.name == professionFilter then
            for _, recipe in ipairs(profession.items) do
                -- Only check recipes the player knows
                local knowsRecipe = Data().PlayerKnowsRecipeBySpellID(recipe.spell)

                if knowsRecipe then
                    local canCraft, maxQty = HDG_RecipesTab.CanCraftRecipe(recipe)
                    if canCraft and maxQty > 0 then
                        table.insert(craftable, {
                            recipe = recipe,
                            profession = profession.name,
                            maxQuantity = maxQty
                        })
                    end
                end
            end
        end
    end

    return craftable
end

-- Check if "What Can I Craft" mode is enabled
function HDG_RecipesTab.IsCraftableFilterEnabled()
    return _G.HDG_Settings and _G.HDG_Settings.craftableFilter == true
end

-- Toggle "What Can I Craft" mode
function HDG_RecipesTab.SetCraftableFilter(enabled)
    if _G.HDG_Settings then
        _G.HDG_Settings.craftableFilter = enabled
    end
end

-- Recipe search filter (session-only, not persisted)
local recipeSearchFilter = ""

function HDG_RecipesTab.GetSearchFilter()
    return recipeSearchFilter
end

function HDG_RecipesTab.SetSearchFilter(text)
    recipeSearchFilter = (text or ""):lower()
end

-- Search reagents filter
function HDG_RecipesTab.IsSearchReagentsEnabled()
    return _G.HDG_Settings and _G.HDG_Settings.searchReagents == true
end

function HDG_RecipesTab.SetSearchReagents(enabled)
    if _G.HDG_Settings then
        _G.HDG_Settings.searchReagents = enabled
    end
end

-- Uncollected only filter
function HDG_RecipesTab.IsUncollectedFilterEnabled()
    return _G.HDG_Settings and _G.HDG_Settings.uncollectedFilter == true
end

function HDG_RecipesTab.SetUncollectedFilter(enabled)
    if _G.HDG_Settings then
        _G.HDG_Settings.uncollectedFilter = enabled
    end
end

-- Check if a recipe matches current filters
function HDG_RecipesTab.RecipeMatchesFilters(recipe)
    -- Search filter
    local search = recipeSearchFilter
    if search and search ~= "" then
        local recipeName = GetRecipeLowerName(recipe)
        local matchesName = recipeName:find(search, 1, true)

        -- Also search reagent names if enabled
        local matchesReagent = false
        if not matchesName and HDG_RecipesTab.IsSearchReagentsEnabled() and recipe.reagents then
            for _, reagent in ipairs(recipe.reagents) do
                -- Use reagent.name from DecorDB (faster, always available)
                local reagentName = reagent.name
                if reagentName then
                    reagentName = reagentName:lower()
                    if reagentName:find(search, 1, true) then
                        matchesReagent = true
                        break
                    end
                end
            end
        end

        if not matchesName and not matchesReagent then
            return false
        end
    end

    -- Uncollected filter
    if HDG_RecipesTab.IsUncollectedFilterEnabled() then
        local isCollected = Data().IsItemCollected(recipe.id)
        if isCollected then
            return false
        end
    end

    return true
end

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

function HDG_RecipesTab:Init(frame)
    mainFrame = frame
end

-- ============================================================================
-- RECIPE RENDERING (Left Panel)
-- ============================================================================

local function UpdateProfessionSectionHeader(profName)
    if mainFrame.profSectionHeader and mainFrame.profSectionText then
        mainFrame.profSectionText:SetText(string.format("|cFFFFFFFF%s|r", profName))
        mainFrame.profSectionHeader:Show()
    end
end

local function HideProfessionSectionHeader()
    if mainFrame.profSectionHeader then
        mainFrame.profSectionHeader:Hide()
    end
end

local function RenderRecipe(recipe, yOffset, showCraftableQty, noProfession, showProfessionIcon)
    local itemName = Data().GetLocalizedName(recipe.id, recipe.name)
    if not itemName or itemName == "Loading..." then itemName = "Item " .. recipe.id end

    local isKnown = Data().PlayerKnowsRecipeBySpellID and Data().PlayerKnowsRecipeBySpellID(recipe.spell) or false
    local isCollected = Data().IsItemCollected(recipe.id)

    -- Check if an alt knows this recipe (only if current char doesn't know it)
    local isAltKnown = false
    local altWhoKnows = nil
    if not isKnown and recipe.spell and HDG_Data.GetCharactersWhoKnowRecipe then
        local altsWhoKnow = HDG_Data.GetCharactersWhoKnowRecipe(recipe.spell, recipe.profession)
        if altsWhoKnow and #altsWhoKnow > 0 then
            -- Find the first alt (not current character)
            for _, altInfo in ipairs(altsWhoKnow) do
                if not altInfo.isCurrent then
                    isAltKnown = true
                    altWhoKnows = altInfo.name -- Full name with realm (e.g., "CharName-Realm")
                    break
                end
            end
        end
    end

    local btn = CreateFrame("Button", nil, mainFrame.recipeContent, "BackdropTemplate")
    btn:SetSize(CONFIG.RECIPE_BUTTON_WIDTH, 18)
    btn:SetPoint("TOPLEFT", 5, yOffset)
    btn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })

    local expColor = HDG_ExpansionData.GetColor(recipe.expansion)
    if expColor then
        btn:SetBackdropColor(expColor[1], expColor[2], expColor[3], 0.25)
    else
        btn:SetBackdropColor(unpack(CONFIG.COLORS.ITEM_UNKNOWN_BG))
    end

    -- Calculate left offset based on whether we show profession icon
    local leftOffset = 3

    -- Profession icon (for All Professions mode)
    if showProfessionIcon and recipe._professionName then
        local profIcon = btn:CreateTexture(nil, "ARTWORK")
        profIcon:SetSize(14, 14)
        profIcon:SetPoint("LEFT", leftOffset, 0)
        profIcon:SetTexture(HDG_ExpansionData.GetProfessionIcon(recipe._professionName))
        -- Custom icons have proper transparency, no mask needed

        leftOffset = leftOffset + 16
    end

    local icon = btn:CreateTexture(nil, "OVERLAY")
    icon:SetSize(12, 12)
    icon:SetPoint("LEFT", leftOffset, 0)
    icon:SetTexture(isCollected and CONFIG.TEXTURES.CHECKMARK or CONFIG.TEXTURES.XMARK)
    leftOffset = leftOffset + 15

    local nameText = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    nameText:SetPoint("LEFT", leftOffset, 0)
    nameText:SetPoint("RIGHT", -55, 0)
    nameText:SetJustifyH("LEFT")
    nameText:SetWordWrap(false)

    -- Recipe name coloring:
    -- Gold (ffd100) = known by current character
    -- White (ffffff) = known by alt but not current character
    -- Light gray (b0b0b0) = not known by anyone
    -- Dark gray (909090) = no profession
    if noProfession then
        nameText:SetText("|cff909090" .. itemName .. "|r")
    elseif isKnown then
        nameText:SetText("|cffffd100" .. itemName .. "|r")
    elseif isAltKnown then
        nameText:SetText("|cffffffff" .. itemName .. "|r")
    else
        nameText:SetText("|cffb0b0b0" .. itemName .. "|r")
    end

    if showCraftableQty then
        local qtyText = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        qtyText:SetPoint("RIGHT", -40, 0)

        if recipe._maxCraftable and recipe._maxCraftable > 0 then
            local maxQty = recipe._maxCraftable
            if maxQty > 99 then
                qtyText:SetText("|cFF00FF00x99+|r")
            else
                qtyText:SetText(string.format("|cFF00FF00x%d|r", maxQty))
            end
        elseif recipe._materialPercent then
            local pct = recipe._materialPercent
            local color = pct >= 75 and "|cFF00FF00" or (pct >= 50 and "|cFFfcd34d" or "|cFFFF5555")
            qtyText:SetText(string.format("%s%.0f%%|r", color, pct))
        end
    end

    local expShort = HDG_ExpansionData.GetShortName(recipe.expansion)
    expColor = HDG_ExpansionData.GetColor(recipe.expansion)
    local expText = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    expText:SetPoint("RIGHT", -5, 0)
    expText:SetText(expShort)
    expText:SetTextColor(unpack(expColor))

    btn:SetScript("OnClick", function()
        HDG_RecipesTab:AddToShoppingList(recipe)
    end)

    btn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:SetItemByID(recipe.id)
        GameTooltip:AddLine(" ")

        -- Show which alt knows this recipe
        if isAltKnown and altWhoKnows then
            GameTooltip:AddLine("|cFFfcd34dKnown by:|r " .. altWhoKnows, 1, 1, 1)
            GameTooltip:AddLine(" ")
        end

        if recipe.reagents and #recipe.reagents > 0 then
            GameTooltip:AddLine("|cFFe2e8f0Materials:|r")
            for _, reagent in ipairs(recipe.reagents) do
                local have = HDG_Data.GetItemCountWithVariants(reagent.id)
                local need = reagent.amount or 1
                local reagentName = Data().GetCachedItemName(reagent.id) or ("Item " .. reagent.id)
                local color = have >= need and "|cFF00FF00" or (have > 0 and "|cFFfcd34d" or "|cFFFF5555")
                GameTooltip:AddDoubleLine("  " .. reagentName, string.format("%s%d/%d|r", color, have, need), 1, 1, 1, 1,
                    1, 1)
            end
        end

        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFFFFFF00Click|r to add to crafting queue", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)

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

    return yOffset - 20
end

local function RenderProfession(profData, yOffset, showInlineHeader, skipFixedHeader)
    local internalProfName = profData.name                                          -- English name for lookups
    local profName = HDG_ExpansionData.GetLocalizedProfessionName(internalProfName) -- Localized for display
    local craftableFilterEnabled = HDG_Data.IsCraftableFilterEnabled()

    -- Pre-filter by search and uncollected filters
    local preFilteredItems = {}
    for _, recipe in ipairs(profData.items) do
        if HDG_RecipesTab.RecipeMatchesFilters(recipe) then
            table.insert(preFilteredItems, recipe)
        end
    end

    -- Skip this profession entirely if no recipes match filters
    if #preFilteredItems == 0 then
        return yOffset
    end

    -- Helper to render inline profession header (used in "All Professions" mode)
    local function renderInlineHeader(headerText)
        if showInlineHeader then
            local inlineHeader = CreateFrame("Frame", nil, mainFrame.recipeContent, "BackdropTemplate")
            inlineHeader:SetSize(CONFIG.RECIPE_BUTTON_WIDTH + 10, 20)
            inlineHeader:SetPoint("TOPLEFT", 0, yOffset)
            inlineHeader:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
            inlineHeader:SetBackdropColor(0.08, 0.25, 0.25, 0.9)

            -- Show recipe count on right side first (so we know its width)
            local countLabel = inlineHeader:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            countLabel:SetPoint("RIGHT", -8, 0)
            local countText = #preFilteredItems .. " recipes"
            countLabel:SetText("|cFF888888" .. countText .. "|r")

            -- Header label with constrained width to avoid overlap
            local headerLabel = inlineHeader:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            headerLabel:SetPoint("LEFT", 8, 0)
            headerLabel:SetPoint("RIGHT", countLabel, "LEFT", -8, 0)
            headerLabel:SetJustifyH("LEFT")
            headerLabel:SetWordWrap(false)
            headerLabel:SetText("|cFFfcd34d" .. headerText .. "|r")

            yOffset = yOffset - 24
        end
    end

    local hasProfession = HDG_Data.PlayerHasProfession(internalProfName)

    if craftableFilterEnabled then
        local tier100, tier75to99, tier50to74, tierBelow50 = {}, {}, {}, {}

        for _, recipe in ipairs(preFilteredItems) do
            local isKnown = HDG_Data.PlayerKnowsRecipeBySpellID(recipe.spell)
            local canCraft, maxQty, _, matPercent = HDG_RecipesTab.CanCraftRecipe(recipe)

            recipe._maxCraftable = maxQty
            recipe._materialPercent = matPercent
            recipe._noProfession = not hasProfession

            if hasProfession and isKnown and canCraft and maxQty > 0 then
                table.insert(tier100, recipe)
            elseif matPercent >= 75 then
                table.insert(tier75to99, recipe)
            elseif matPercent >= 50 then
                table.insert(tier50to74, recipe)
            elseif matPercent > 0 then
                table.insert(tierBelow50, recipe)
            end
        end

        local function sortByPercent(a, b)
            return (a._materialPercent or 0) > (b._materialPercent or 0)
        end
        table.sort(tier75to99, sortByPercent)
        table.sort(tier50to74, sortByPercent)
        table.sort(tierBelow50, sortByPercent)

        if #tier100 + #tier75to99 + #tier50to74 + #tierBelow50 == 0 then
            return yOffset
        end

        -- Show inline header for "All Professions" mode
        local notLearnedLabel = L["QUEUE_NEEDS_TRAINING"] or "Not Learned"
        local headerText = profName
        if not hasProfession then
            headerText = profName .. " (" .. notLearnedLabel .. ")"
        end
        renderInlineHeader(headerText)

        -- Update fixed header for single profession mode
        if not skipFixedHeader then
            local fixedHeaderText = profName .. " (" .. (L["HEADER_MATERIALS"] or "Materials") .. ")"
            if not hasProfession then
                fixedHeaderText = profName .. " |cFF888888(" .. notLearnedLabel .. ")|r"
            end
            UpdateProfessionSectionHeader(fixedHeaderText)
        end

        local function renderTier(tier, label, color)
            if #tier > 0 then
                local tierHeader = mainFrame.recipeContent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                tierHeader:SetPoint("TOPLEFT", 8, yOffset)
                tierHeader:SetText(color .. label .. "|r |cFF888888(" .. #tier .. ")|r")
                yOffset = yOffset - 16
                for _, recipe in ipairs(tier) do
                    yOffset = RenderRecipe(recipe, yOffset, true, recipe._noProfession)
                end
                yOffset = yOffset - 6
            end
        end

        renderTier(tier100, "Ready to Craft", "|cFF00FF00")
        local tierLabel = hasProfession and "Almost Ready (75-99%)" or "Have Most Materials (75-99%)"
        renderTier(tier75to99, tierLabel, "|cFF00FF00")
        renderTier(tier50to74, "Halfway There (50-74%)", "|cFFfcd34d")
        renderTier(tierBelow50, "Need Materials (<50%)", "|cFFFF5555")

        return yOffset - CONFIG.SPACING.PROFESSION_GAP
    end

    -- Non-craftable filter mode: show all recipes
    renderInlineHeader(profName)

    if not skipFixedHeader then
        UpdateProfessionSectionHeader(profName)
    end

    for _, recipe in ipairs(preFilteredItems) do
        yOffset = RenderRecipe(recipe, yOffset, false)
    end

    return yOffset - CONFIG.SPACING.PROFESSION_GAP
end

-- ============================================================================
-- CREATE RECIPE PANEL (Left)
-- ============================================================================

function HDG_RecipesTab:CreateRecipePanel()
    local recipeFrame = CreateFrame("Frame", nil, mainFrame, "BackdropTemplate")
    recipeFrame:SetPoint("TOPLEFT", CONFIG.SPACING.PANEL_PADDING, -(CONFIG.BANNER_HEIGHT + CONFIG.TAB_HEIGHT + 8))
    recipeFrame:SetSize(CONFIG.LEFT_PANEL_WIDTH, CONFIG.PANEL_HEIGHT)
    recipeFrame:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    recipeFrame:SetBackdropColor(0, 0, 0, 0.5)
    recipeFrame:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    mainFrame.recipeFrame = recipeFrame

    -- Header
    local headerBg = CreateFrame("Frame", nil, recipeFrame, "BackdropTemplate")
    headerBg:SetPoint("TOPLEFT", 1, -1)
    headerBg:SetPoint("TOPRIGHT", -1, -1)
    headerBg:SetHeight(22)
    headerBg:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    headerBg:SetBackdropColor(0.1, 0.1, 0.1, 0.8)

    local recipeTitle = headerBg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    recipeTitle:SetPoint("LEFT", 8, 0)
    recipeTitle:SetText("|cFFFFFFFFProfessions|r")
    mainFrame.recipeTitle = recipeTitle

    -- Column header icons with tooltips for totals
    local COL_WIDTH = 60
    local HEADER_OFFSET = 7
    local COLLECTION_COL_LEFT = -6 - COL_WIDTH + HEADER_OFFSET
    local RECIPES_COL_LEFT = -68 - COL_WIDTH + HEADER_OFFSET

    -- Recipes column header (book icon + percentage)
    local recipesHeaderFrame = CreateFrame("Frame", nil, headerBg)
    recipesHeaderFrame:SetSize(50, 20)
    recipesHeaderFrame:SetPoint("RIGHT", RECIPES_COL_LEFT + 50, 0)
    recipesHeaderFrame:EnableMouse(true)
    mainFrame.recipesHeaderFrame = recipesHeaderFrame

    local headerRecipesIcon = recipesHeaderFrame:CreateTexture(nil, "OVERLAY")
    headerRecipesIcon:SetSize(14, 14)
    headerRecipesIcon:SetPoint("LEFT", 0, 0)
    headerRecipesIcon:SetTexture("Interface\\Icons\\INV_Misc_Book_09")
    headerRecipesIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    local headerRecipesPct = recipesHeaderFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    headerRecipesPct:SetPoint("LEFT", headerRecipesIcon, "RIGHT", 2, 0)
    mainFrame.headerRecipesPct = headerRecipesPct

    -- Collection column header (crate icon + percentage)
    local collectionHeaderFrame = CreateFrame("Frame", nil, headerBg)
    collectionHeaderFrame:SetSize(50, 20)
    collectionHeaderFrame:SetPoint("RIGHT", COLLECTION_COL_LEFT + 50, 0)
    collectionHeaderFrame:EnableMouse(true)
    mainFrame.collectionHeaderFrame = collectionHeaderFrame

    local headerCollectionIcon = collectionHeaderFrame:CreateTexture(nil, "OVERLAY")
    headerCollectionIcon:SetSize(14, 14)
    headerCollectionIcon:SetPoint("LEFT", 0, 0)
    headerCollectionIcon:SetTexture("Interface\\Icons\\INV_Crate_01")
    headerCollectionIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    local headerCollectionPct = collectionHeaderFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    headerCollectionPct:SetPoint("LEFT", headerCollectionIcon, "RIGHT", 2, 0)
    mainFrame.headerCollectionPct = headerCollectionPct

    -- Search box row (right below header)
    local searchRow = CreateFrame("Frame", nil, recipeFrame)
    searchRow:SetPoint("TOPLEFT", 8, -24)
    searchRow:SetPoint("TOPRIGHT", -8, -24)
    searchRow:SetHeight(20)

    -- Profession tabs container (pushed down by search row)
    local professionTabs = CreateFrame("Frame", nil, recipeFrame)
    professionTabs:SetPoint("TOPLEFT", 8, -46)
    professionTabs:SetPoint("TOPRIGHT", -8, -46)
    professionTabs:SetHeight(188)
    mainFrame.professionTabs = professionTabs

    -- Search box
    local searchBox = CreateFrame("EditBox", nil, searchRow, "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 recipes...")
    searchBox.placeholder = searchPlaceholder

    -- Clear button (X) for search
    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()
        HDG_RecipesTab.SetSearchFilter("")
        HDG_UI.RefreshUI()
    end)

    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
            HDG_RecipesTab.SetSearchFilter(text)
            HDG_UI.RefreshUI()
        end
    end)
    searchBox:SetScript("OnEscapePressed", function(self)
        self:ClearFocus()
    end)
    searchBox:SetScript("OnEnterPressed", function(self)
        self:ClearFocus()
    end)
    mainFrame.recipeSearchBox = searchBox

    -- Uncollected checkbox
    local uncollectedCheck = CreateFrame("CheckButton", nil, searchRow, "UICheckButtonTemplate")
    uncollectedCheck:SetPoint("LEFT", searchBox, "RIGHT", 4, 0)
    uncollectedCheck:SetSize(20, 20)
    uncollectedCheck:SetChecked(HDG_RecipesTab.IsUncollectedFilterEnabled())

    local uncollectedLabel = searchRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    uncollectedLabel:SetPoint("LEFT", uncollectedCheck, "RIGHT", 0, 0)
    uncollectedLabel:SetText("|cFFe2e8f0" .. (L["CHECK_UNCOLLECTED_ONLY"] or "Uncollected") .. "|r")

    uncollectedCheck:SetScript("OnClick", function(self)
        HDG_RecipesTab.SetUncollectedFilter(self:GetChecked())
        HDG_UI.RefreshUI()
    end)
    uncollectedCheck:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0Show Uncollected Only|r", 1, 1, 1)
        GameTooltip:AddLine("Hide recipes for decor you've already collected", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)
    uncollectedCheck:SetScript("OnLeave", function() GameTooltip:Hide() end)
    mainFrame.uncollectedCheck = uncollectedCheck

    -- Search reagents checkbox
    local searchReagentsCheck = CreateFrame("CheckButton", nil, searchRow, "UICheckButtonTemplate")
    searchReagentsCheck:SetPoint("LEFT", uncollectedLabel, "RIGHT", 4, 0)
    searchReagentsCheck:SetSize(20, 20)
    searchReagentsCheck:SetChecked(HDG_RecipesTab.IsSearchReagentsEnabled())

    local searchReagentsLabel = searchRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    searchReagentsLabel:SetPoint("LEFT", searchReagentsCheck, "RIGHT", 0, 0)
    searchReagentsLabel:SetText("|cFFe2e8f0" .. (L["CHECK_INCLUDE_MATS"] or "+Mats") .. "|r")

    searchReagentsCheck:SetScript("OnClick", function(self)
        HDG_RecipesTab.SetSearchReagents(self:GetChecked())
        HDG_UI.RefreshUI()
    end)
    searchReagentsCheck:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0Search Reagents|r", 1, 1, 1)
        GameTooltip:AddLine("Also search by reagent/material names", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("e.g. search 'bismuth' to find recipes using Bismuth", 0.5, 0.5, 0.5)
        GameTooltip:Show()
    end)
    searchReagentsCheck:SetScript("OnLeave", function() GameTooltip:Hide() end)
    mainFrame.searchReagentsCheck = searchReagentsCheck

    -- Sticky header (offset by search row height)
    local stickyHeader = CreateFrame("Frame", nil, recipeFrame, "BackdropTemplate")
    stickyHeader:SetPoint("TOPLEFT", 8, -258)
    stickyHeader:SetPoint("TOPRIGHT", -26, -258)
    stickyHeader:SetHeight(18)
    stickyHeader:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false, insets = { left = 0, right = 0, top = 0, bottom = 0 } })
    stickyHeader:SetBackdropColor(unpack(CONFIG.COLORS.STICKY_HEADER_BG))
    stickyHeader:Hide()
    mainFrame.stickyHeader = stickyHeader

    local stickyHeaderText = stickyHeader:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    stickyHeaderText:SetPoint("LEFT", 6, 0)
    mainFrame.stickyHeaderText = stickyHeaderText

    local stickyHeaderPercentage = stickyHeader:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    stickyHeaderPercentage:SetPoint("RIGHT", -6, 0)
    mainFrame.stickyHeaderPercentage = stickyHeaderPercentage

    -- Profession section header (offset by search row height)
    local profSectionHeader = CreateFrame("Frame", nil, recipeFrame, "BackdropTemplate")
    profSectionHeader:SetPoint("TOPLEFT", 1, -258)
    profSectionHeader:SetPoint("TOPRIGHT", -1, -258)
    profSectionHeader:SetHeight(22)
    profSectionHeader:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    profSectionHeader:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
    profSectionHeader:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    profSectionHeader:Hide()
    mainFrame.profSectionHeader = profSectionHeader

    local profSectionTextFrame = CreateFrame("Frame", nil, profSectionHeader)
    profSectionTextFrame:SetPoint("LEFT", 8, 0)
    profSectionTextFrame:SetSize(150, 20)

    local profSectionText = profSectionTextFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    profSectionText:SetPoint("LEFT", 0, 0)
    mainFrame.profSectionText = profSectionText

    profSectionTextFrame:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0Profession Progress|r", 1, 1, 1)
        GameTooltip:AddLine("Shows recipes collected / total recipes", 0.7, 0.7, 0.7)
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFFfcd34dWorkflow:|r", 1, 1, 1)
        GameTooltip:AddLine("1. Click recipes to add to Crafting Queue", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("2. Review Required Materials", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("3. Right-click materials for AH search", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)
    profSectionTextFrame:SetScript("OnLeave", function() GameTooltip:Hide() end)

    -- All/Ready toggle
    local btnWidth = 50
    local toggleContainer = CreateFrame("Frame", nil, profSectionHeader)
    toggleContainer:SetPoint("TOPRIGHT", 0, 0)
    toggleContainer:SetPoint("BOTTOMRIGHT", 0, 0)
    toggleContainer:SetWidth(btnWidth * 2)

    local allBtn = CreateFrame("Button", nil, toggleContainer, "BackdropTemplate")
    allBtn:SetPoint("TOPLEFT", 0, 0)
    allBtn:SetPoint("BOTTOMLEFT", 0, 0)
    allBtn:SetWidth(btnWidth)
    allBtn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })

    local allBtnText = allBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    allBtnText:SetPoint("CENTER", 0, 0)
    allBtnText:SetText(L["BTN_ALL"] or "All")

    local allBtnLeftBorder = allBtn:CreateTexture(nil, "OVERLAY")
    allBtnLeftBorder:SetTexture(CONFIG.TEXTURES.WHITE_BG)
    allBtnLeftBorder:SetVertexColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    allBtnLeftBorder:SetPoint("TOPLEFT", 0, 0)
    allBtnLeftBorder:SetPoint("BOTTOMLEFT", 0, 0)
    allBtnLeftBorder:SetWidth(1)
    allBtnLeftBorder:Hide()

    local readyBtn = CreateFrame("Button", nil, toggleContainer, "BackdropTemplate")
    readyBtn:SetPoint("TOPRIGHT", 0, 0)
    readyBtn:SetPoint("BOTTOMRIGHT", 0, 0)
    readyBtn:SetWidth(btnWidth)
    readyBtn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })

    local readyBtnText = readyBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    readyBtnText:SetPoint("CENTER", 0, 0)
    readyBtnText:SetText(L["BTN_READY"] or "Ready")

    local function UpdateCraftableToggle()
        if HDG_Data.IsCraftableFilterEnabled() then
            allBtn:SetBackdropColor(0.05, 0.05, 0.05, 0)
            allBtnText:SetTextColor(0.5, 0.5, 0.5, 1)
            readyBtn:SetBackdropColor(0.08, 0.45, 0.42, 0.9)
            readyBtnText:SetTextColor(1, 1, 1, 1)
            allBtnLeftBorder:Show()
        else
            allBtn:SetBackdropColor(0.08, 0.45, 0.42, 0.9)
            allBtnText:SetTextColor(1, 1, 1, 1)
            readyBtn:SetBackdropColor(0.05, 0.05, 0.05, 0)
            readyBtnText:SetTextColor(0.5, 0.5, 0.5, 1)
            allBtnLeftBorder:Hide()
        end
    end

    allBtn:SetScript("OnClick", function()
        if HDG_Data.IsCraftableFilterEnabled() then
            HDG_Data.SetCraftableFilter(false)
            UpdateCraftableToggle()
            HDG_UI.RefreshUI()
        end
    end)

    readyBtn:SetScript("OnClick", function()
        if not HDG_Data.IsCraftableFilterEnabled() then
            HDG_Data.SetCraftableFilter(true)
            UpdateCraftableToggle()
            HDG_UI.RefreshUI()
        end
    end)

    allBtn:SetScript("OnEnter", function(self)
        if HDG_Data.IsCraftableFilterEnabled() then self:SetBackdropColor(0.15, 0.15, 0.15, 0.9) end
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0Show All Recipes|r", 1, 1, 1)
        GameTooltip:Show()
    end)
    allBtn:SetScript("OnLeave", function()
        UpdateCraftableToggle(); GameTooltip:Hide()
    end)

    readyBtn:SetScript("OnEnter", function(self)
        if not HDG_Data.IsCraftableFilterEnabled() then self:SetBackdropColor(0.15, 0.15, 0.15, 0.9) end
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0Show Ready to Craft|r", 1, 1, 1)
        GameTooltip:AddLine("Filter by material availability", 1, 1, 1)
        GameTooltip:Show()
    end)
    readyBtn:SetScript("OnLeave", function()
        UpdateCraftableToggle(); GameTooltip:Hide()
    end)

    mainFrame.UpdateCraftableButton = UpdateCraftableToggle
    UpdateCraftableToggle()

    -- Scroll frame
    local scroll = CreateFrame("ScrollFrame", nil, recipeFrame, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 8, -284)
    scroll:SetPoint("BOTTOMRIGHT", -26, 8)

    local content = CreateFrame("Frame", nil, scroll)
    content:SetSize(CONFIG.RECIPE_BUTTON_WIDTH, 1)
    scroll:SetScrollChild(content)
    mainFrame.recipeContent = content
    mainFrame.recipeScroll = scroll

    if HDG_UIHelpers and HDG_UIHelpers.StyleScrollBar then
        HDG_UIHelpers.StyleScrollBar(scroll)
    end

    return recipeFrame
end

-- ============================================================================
-- CREATE SHOPPING PANEL (Middle - Crafting Queue)
-- ============================================================================

function HDG_RecipesTab:CreateShoppingPanel()
    local shoppingFrame = CreateFrame("Frame", nil, mainFrame, "BackdropTemplate")
    shoppingFrame:SetPoint("TOPLEFT", mainFrame.recipeFrame, "TOPRIGHT", CONFIG.SPACING.PANEL_GAP, 0)
    shoppingFrame:SetSize(CONFIG.MIDDLE_PANEL_WIDTH, CONFIG.PANEL_HEIGHT)
    shoppingFrame:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    shoppingFrame:SetBackdropColor(0, 0, 0, 0.5)
    shoppingFrame:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    mainFrame.shoppingFrame = shoppingFrame

    -- Header
    local headerBg = CreateFrame("Frame", nil, shoppingFrame, "BackdropTemplate")
    headerBg:SetPoint("TOPLEFT", 1, -1)
    headerBg:SetPoint("TOPRIGHT", -1, -1)
    headerBg:SetHeight(22)
    headerBg:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    headerBg:SetBackdropColor(0.1, 0.1, 0.1, 0.8)

    local craftingQueueLabel = L["HEADER_CRAFTING_QUEUE"] or "Crafting Queue"
    local shoppingTitle = headerBg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    shoppingTitle:SetPoint("CENTER", 0, 0)
    shoppingTitle:SetText("|cFFFFFFFF" .. craftingQueueLabel .. "|r")
    mainFrame.shoppingTitle = shoppingTitle
    mainFrame.craftingQueueLabel = craftingQueueLabel

    headerBg:EnableMouse(true)
    headerBg:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0" .. craftingQueueLabel .. "|r", 1, 1, 1)
        GameTooltip:AddLine("Recipes you want to craft", 0.7, 0.7, 0.7)
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFFfcd34dHow to use:|r", 1, 1, 1)
        GameTooltip:AddLine("Click recipes on the left to add them here", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("Click items here to remove them", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)
    headerBg:SetScript("OnLeave", function() GameTooltip:Hide() end)

    -- Clear button
    local clearBtn = CreateFrame("Button", nil, shoppingFrame)
    clearBtn:SetSize(20, 20)
    clearBtn:SetPoint("TOPRIGHT", -4, -2)
    clearBtn:SetFrameLevel(shoppingFrame:GetFrameLevel() + 10)
    clearBtn:RegisterForClicks("AnyUp")
    local clearIcon = clearBtn:CreateTexture(nil, "ARTWORK")
    clearIcon:SetAllPoints()
    clearIcon:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
    clearBtn:SetScript("OnClick", function()
        HDG_Settings.shoppingList = {}
        HDG_UI.RefreshUI()
    end)
    clearBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFfcd34d" .. (L["BTN_CLEAR"] or "Clear") .. " " .. craftingQueueLabel .. "|r", 1, 1, 1)
        GameTooltip:AddLine("Remove all items from crafting queue", 1, 1, 1)
        GameTooltip:Show()
    end)
    clearBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

    -- Scroll frame - bottom adjusts based on lumber frame visibility
    -- Reserve space for embedded lumber frame: header (24) + rows (lumber count * 18) + padding (8) + margin (8)
    local lumberReservedHeight = 24 + (#HDG_ExpansionData.LUMBER_ORDER * 18) + 8 + 8
    local scroll = CreateFrame("ScrollFrame", nil, shoppingFrame, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 8, -28)
    scroll:SetPoint("BOTTOMRIGHT", -26, lumberReservedHeight)
    mainFrame.shoppingScroll = scroll

    local content = CreateFrame("Frame", nil, scroll)
    content:SetSize(CONFIG.SHOPPING_ITEM_WIDTH, 1)
    scroll:SetScrollChild(content)
    mainFrame.shoppingContent = content

    if HDG_UIHelpers and HDG_UIHelpers.StyleScrollBar then
        HDG_UIHelpers.StyleScrollBar(scroll)
    end

    -- Embedded Lumber Frame (shown when NOT pinned)
    local lumberFrame = CreateFrame("Frame", nil, shoppingFrame, "BackdropTemplate")
    lumberFrame:SetPoint("BOTTOMLEFT", 8, 8)
    lumberFrame:SetPoint("BOTTOMRIGHT", -8, 8)
    -- Dynamic height: header (24) + rows (lumber count * 18) + padding (8)
    local lumberCount = #HDG_ExpansionData.LUMBER_ORDER
    local embeddedLumberHeight = 24 + (lumberCount * 18) + 8
    lumberFrame:SetHeight(embeddedLumberHeight)
    lumberFrame:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    lumberFrame:SetBackdropColor(unpack(CONFIG.COLORS.LUMBER_BG))
    lumberFrame:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    mainFrame.lumberFrame = lumberFrame

    -- Pin button in lumber frame header (top-right)
    local pinBtn = CreateFrame("Button", nil, lumberFrame, "BackdropTemplate")
    pinBtn:SetSize(20, 20)
    pinBtn:SetPoint("TOPRIGHT", -4, -4)
    pinBtn:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    pinBtn:SetBackdropColor(unpack(HDG_UIHelpers.BTN_NORMAL))
    pinBtn:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))

    local pinIcon = pinBtn:CreateTexture(nil, "ARTWORK")
    pinIcon:SetSize(14, 14)
    pinIcon:SetPoint("CENTER", 0, 0)
    pinIcon:SetTexture(CONFIG.ADDON_TEXTURES.PIN)
    pinIcon:SetDesaturated(false)
    pinIcon:SetVertexColor(1, 1, 1, 1) -- Full color when unpinned (click to pin)
    pinBtn.icon = pinIcon

    pinBtn:SetScript("OnClick", function()
        HDG_Settings.lumberPinned = true
        if HDG_LumberTracker then
            HDG_LumberTracker.ShowPinned()
        end
        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 pinned to separate window")
    end)

    pinBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(unpack(HDG_UIHelpers.BTN_HOVER))
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFfcd34dPin Lumber Tracker|r", 1, 1, 1)
        GameTooltip:AddLine("Move to separate floating window", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("Stays visible when addon window closes", 0.5, 0.8, 0.5)
        GameTooltip:Show()
    end)

    pinBtn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(unpack(HDG_UIHelpers.BTN_NORMAL))
        GameTooltip:Hide()
    end)

    lumberFrame.pinBtn = pinBtn

    -- Lumber headers (smaller font) - localized
    local lumberHeader = lumberFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    lumberHeader:SetPoint("TOP", 0, -6)
    lumberHeader:SetPoint("LEFT", 4, 0)
    lumberHeader:SetWidth(CONFIG.LUMBER_COL_1)
    lumberHeader:SetJustifyH("LEFT")
    lumberHeader:SetText("|cFFe2e8f0" .. (L["COL_TYPE"] or "Type") .. "|r")

    local shoppingHeader = lumberFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    shoppingHeader:SetPoint("TOP", 0, -6)
    shoppingHeader:SetPoint("LEFT", 4 + CONFIG.LUMBER_COL_1, 0)
    shoppingHeader:SetWidth(CONFIG.LUMBER_COL_2)
    shoppingHeader:SetJustifyH("CENTER")
    shoppingHeader:SetText("|cFFe2e8f0" .. (L["COL_NEEDED"] or "Needed") .. "|r")

    local allHeader = lumberFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    allHeader:SetPoint("TOP", 0, -6)
    allHeader:SetPoint("LEFT", 4 + CONFIG.LUMBER_COL_1 + CONFIG.LUMBER_COL_2, 0)
    allHeader:SetWidth(CONFIG.LUMBER_COL_3)
    allHeader:SetJustifyH("CENTER")
    allHeader:SetText("|cFFe2e8f0" .. (L["COL_IN_STOCK"] or "In Stock") .. "|r")

    local lumberContent = CreateFrame("Frame", nil, lumberFrame)
    lumberContent:SetPoint("TOPLEFT", 4, -24)
    lumberContent:SetPoint("BOTTOMRIGHT", -4, 4)
    mainFrame.lumberContent = lumberContent

    return shoppingFrame
end

-- ============================================================================
-- CREATE MATERIALS PANEL (Right)
-- ============================================================================

function HDG_RecipesTab:CreateMaterialsPanel()
    local materialsFrame = CreateFrame("Frame", nil, mainFrame, "BackdropTemplate")
    materialsFrame:SetPoint("TOPLEFT", mainFrame.shoppingFrame, "TOPRIGHT", CONFIG.SPACING.PANEL_GAP, 0)
    materialsFrame:SetSize(CONFIG.RIGHT_PANEL_WIDTH, CONFIG.PANEL_HEIGHT)
    materialsFrame:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    })
    materialsFrame:SetBackdropColor(0, 0, 0, 0.5)
    materialsFrame:SetBackdropBorderColor(unpack(HDG_UIHelpers.BORDER_COLOR))
    mainFrame.materialsFrame = materialsFrame

    -- Header
    local headerBg = CreateFrame("Frame", nil, materialsFrame, "BackdropTemplate")
    headerBg:SetPoint("TOPLEFT", 1, -1)
    headerBg:SetPoint("TOPRIGHT", -1, -1)
    headerBg:SetHeight(22)
    headerBg:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    headerBg: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 = headerBg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    materialsTitle:SetPoint("LEFT", 8, 0)
    materialsTitle:SetText("|cFFFFFFFF" .. requiredMatsLabel .. "|r")
    mainFrame.materialsTitle = materialsTitle

    -- Power Crafting toggle button (Direct | Raw)
    local toggleContainer = CreateFrame("Frame", nil, headerBg, "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 UpdateToggleAppearance()
        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
        UpdateToggleAppearance()
        HDG_RecipesTab:RefreshMaterials()
        -- Also update pinned lumber widget if visible
        if HDG_LumberTracker and HDG_LumberTracker.IsPinnedVisible and HDG_LumberTracker.IsPinnedVisible() then
            HDG_LumberTracker.RefreshPinned()
        end
    end)

    rawBtn:SetScript("OnClick", function()
        HDG_Settings.showRawMaterials = true
        UpdateToggleAppearance()
        HDG_RecipesTab:RefreshMaterials()
        -- Also update pinned lumber widget if visible
        if HDG_LumberTracker and HDG_LumberTracker.IsPinnedVisible and HDG_LumberTracker.IsPinnedVisible() then
            HDG_LumberTracker.RefreshPinned()
        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)

    mainFrame.materialsToggleUpdate = UpdateToggleAppearance
    UpdateToggleAppearance()

    headerBg:EnableMouse(true)
    headerBg:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine("|cFFe2e8f0Required Materials|r", 1, 1, 1)
        GameTooltip:AddLine("All materials for your Crafting Queue", 0.7, 0.7, 0.7)
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFFfcd34dDirect vs Raw:|r", 1, 1, 1)
        GameTooltip:AddLine("Direct: Materials as listed in recipes", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("Raw: Expand crafted reagents to base materials", 0.7, 0.7, 0.7)
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFFfcd34dHow to use:|r", 1, 1, 1)
        GameTooltip:AddLine("Right-click materials to search Auction House", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("Use 'Add All to Auctionator' for bulk search", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)
    headerBg:SetScript("OnLeave", function() GameTooltip:Hide() end)

    -- Scroll frame
    local scroll = CreateFrame("ScrollFrame", nil, materialsFrame, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 8, -28)
    scroll:SetPoint("BOTTOMRIGHT", -26, 68)

    local content = CreateFrame("Frame", nil, scroll)
    content:SetSize(CONFIG.MATERIAL_ITEM_WIDTH, 1)
    scroll:SetScrollChild(content)
    mainFrame.materialsContent = content
    mainFrame.materialsScroll = scroll

    if HDG_UIHelpers and HDG_UIHelpers.StyleScrollBar then
        HDG_UIHelpers.StyleScrollBar(scroll)
    end

    -- Cost display
    local costText = materialsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    costText:SetPoint("BOTTOM", 0, 10)
    costText:SetText("|cFF888888No price data|r")
    mainFrame.materialsCostText = costText

    -- Add All to Auctionator button
    local addAllBar = CreateFrame("StatusBar", nil, materialsFrame, "BackdropTemplate")
    addAllBar:SetSize(280, 25)
    addAllBar:SetPoint("BOTTOM", costText, "TOP", 0, 5)
    addAllBar:SetStatusBarTexture(CONFIG.TEXTURES.STATUSBAR)
    addAllBar:SetStatusBarColor(unpack(HDG_UIHelpers.STATE_ACTIVE))
    addAllBar:SetMinMaxValues(0, 1)
    addAllBar:SetValue(1)
    addAllBar:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
    addAllBar:SetBackdropColor(0.08, 0.23, 0.23, 0.3)

    local addAllLabel = L["BTN_ADD_ALL_AUCTIONATOR"] or "Add All to Auctionator"
    local buttonText = addAllBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    buttonText:SetPoint("CENTER", 0, 0)
    buttonText:SetText(addAllLabel)
    buttonText:SetFont(buttonText:GetFont(), 12, "")
    buttonText:SetShadowOffset(0, 0)
    buttonText:SetTextColor(1.0, 1.0, 1.0, 1)
    mainFrame.addAllAuctionatorText = buttonText -- Store reference for updating

    local clickButton = CreateFrame("Button", nil, addAllBar)
    clickButton:SetAllPoints(addAllBar)
    clickButton:RegisterForClicks("LeftButtonUp")
    clickButton:SetScript("OnClick", function()
        HDG_RecipesTab:AddAllMaterialsToAuctionator()
    end)
    clickButton:SetScript("OnEnter", function(self)
        addAllBar:SetStatusBarColor(0.15, 0.40, 0.40, 1.0) -- Brighter hover
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:SetText("|cFFfcd34d" .. addAllLabel .. "|r", 1, 1, 1)
        GameTooltip:AddLine("Creates/replaces Auctionator 'Housing Decor' list", 1, 1, 1)
        GameTooltip:AddLine("with materials needed (minus what you own)", 1, 1, 1)
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFFFFAA00Click|r to add all materials at once", 0.7, 0.7, 0.7)
        GameTooltip:AddLine("|cFF888888Note: This will replace the existing list|r", 0.5, 0.5, 0.5)
        GameTooltip:Show()
    end)
    clickButton:SetScript("OnLeave", function()
        addAllBar:SetStatusBarColor(unpack(HDG_UIHelpers.STATE_ACTIVE))
        GameTooltip:Hide()
    end)

    mainFrame.addAllAuctionatorButton = addAllBar
    return materialsFrame
end

-- ============================================================================
-- CREATE ALL PANELS
-- ============================================================================

function HDG_RecipesTab:Create()
    self:CreateRecipePanel()
    self:CreateShoppingPanel()
    self:CreateMaterialsPanel()
end

-- ============================================================================
-- SHOPPING LIST FUNCTIONS
-- ============================================================================

function HDG_RecipesTab:AddToShoppingList(recipe, skipRefresh)
    local found = false
    for _, item in ipairs(HDG_Settings.shoppingList) do
        if item.id == recipe.id then
            item.quantity = item.quantity + 1
            found = true
            break
        end
    end

    if not found then
        table.insert(HDG_Settings.shoppingList, {
            id = recipe.id,
            name = recipe.name,
            spell = recipe.spell,
            reagents = recipe.reagents,
            expansion = recipe.expansion or "Unknown",
            profession = recipe.profession,
            quantity = 1
        })
    end
    if not skipRefresh then
        HDG_UI.RefreshUI()
    end
end

function HDG_RecipesTab:RefreshShoppingList()
    if not mainFrame or not mainFrame.shoppingContent then return end
    ClearScrollContent(mainFrame.shoppingContent)

    -- Update title with item count
    if mainFrame.shoppingTitle and mainFrame.craftingQueueLabel then
        local count = #HDG_Settings.shoppingList
        if count > 0 then
            mainFrame.shoppingTitle:SetText("|cFFFFFFFF" ..
            mainFrame.craftingQueueLabel .. " |cFF14b8a6(" .. count .. ")|r")
        else
            mainFrame.shoppingTitle:SetText("|cFFFFFFFF" .. mainFrame.craftingQueueLabel .. "|r")
        end
    end

    if #HDG_Settings.shoppingList == 0 then return end

    -- Group items by primary crafter
    local currentCharKey = HDG_Data.GetCharacterKey()
    local currentCharShort = currentCharKey:match("^([^-]+)") or currentCharKey
    local itemsByCharacter = {}
    local characterOrder = {} -- Track order: current char first, then alts alphabetically

    for i, item in ipairs(HDG_Settings.shoppingList) do
        local crafters = {}
        if item.spell then
            crafters = HDG_Data.GetCharactersWhoKnowRecipe(item.spell, item.profession)
        end

        -- Determine primary crafter (current char > first alt > unknown)
        local primaryCrafter = "Unknown"
        if #crafters > 0 then
            -- Current character takes priority
            for _, c in ipairs(crafters) do
                if c.isCurrent then
                    primaryCrafter = currentCharKey
                    break
                end
            end
            -- If not current, use first alt
            if primaryCrafter == "Unknown" then
                primaryCrafter = crafters[1].name
            end
        end

        -- Add to group
        if not itemsByCharacter[primaryCrafter] then
            itemsByCharacter[primaryCrafter] = {}
            table.insert(characterOrder, primaryCrafter)
        end
        table.insert(itemsByCharacter[primaryCrafter], { item = item, index = i })
    end

    -- Sort character order: current char first, then alphabetically, "Unknown" last
    table.sort(characterOrder, function(a, b)
        if a == currentCharKey then return true end
        if b == currentCharKey then return false end
        if a == "Unknown" then return false end
        if b == "Unknown" then return true end
        return a < b
    end)

    local yOffset = 0

    -- Render items grouped by character
    for _, charKey in ipairs(characterOrder) do
        local items = itemsByCharacter[charKey]

        -- Section header
        local headerFrame = CreateFrame("Frame", nil, mainFrame.shoppingContent)
        headerFrame:SetSize(CONFIG.SHOPPING_ITEM_WIDTH, 16)
        headerFrame:SetPoint("TOPLEFT", CONFIG.SPACING.ITEM_PADDING, yOffset)

        local headerText = headerFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        headerText:SetPoint("LEFT", 2, 0)
        headerText:SetJustifyH("LEFT")

        local charName = charKey:match("^([^-]+)") or charKey
        local realmName = charKey:match("-(.+)$") or ""
        if charKey == currentCharKey then
            headerText:SetText("|cFF00FF00" .. charName .. "|r |cFF888888- " .. realmName .. "|r |cFFfcd34d(you)|r")
        elseif charKey == "Unknown" then
            headerText:SetText("|cFFFF5555Not Known|r")
        else
            headerText:SetText("|cFFfcd34d" .. charName .. "|r |cFF888888- " .. realmName .. "|r")
        end

        yOffset = yOffset - 18

        -- Render items in this group
        for _, itemData in ipairs(items) do
            local item = itemData.item
            local i = itemData.index

            -- Check if this item has subsidiary recipes (crafted sub-components)
            local hasSubsidiaries = HasSubsidiaryRecipes(item)
            local isExpanded = hasSubsidiaries and expandedShoppingItems[item.id]

            local frame = CreateFrame("Button", nil, mainFrame.shoppingContent, "BackdropTemplate")
            frame:SetSize(CONFIG.SHOPPING_ITEM_WIDTH, CONFIG.SHOPPING_ITEM_HEIGHT)
            frame:SetPoint("TOPLEFT", CONFIG.SPACING.ITEM_PADDING, yOffset)
            frame:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
            frame:SetBackdropColor(unpack(GetShoppingItemColor(item)))

            -- Check if player knows recipe and can craft it
            local isKnown = item.spell and Data().PlayerKnowsRecipeBySpellID and
            Data().PlayerKnowsRecipeBySpellID(item.spell)
            local canCraft, maxCraftable = HDG_RecipesTab.CanCraftRecipe(item)
            maxCraftable = maxCraftable or 0

            -- Always reserve space for hammer icon to maintain consistent alignment
            local CRAFT_BTN_WIDTH = 44                        -- Width for "Craft (n)" button
            local PROF_ICON_SIZE = 16                         -- Profession icon size
            local EXPAND_ICON_WIDTH = hasSubsidiaries and 14 or 0 -- Space for expand/collapse indicator

            -- Expand/collapse indicator for items with subsidiaries
            if hasSubsidiaries then
                local expandIcon = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                expandIcon:SetPoint("LEFT", 2, 0)
                expandIcon:SetText(isExpanded and "|cFFfcd34d-|r" or "|cFFfcd34d+|r")
                expandIcon:SetTextColor(0.98, 0.84, 0.34)
            end

            -- Profession icon (fallback to DB lookup for legacy items without profession field)
            local profession = item.profession
            if not profession and item.id and HDG_DecorDB then
                -- item.id is the itemID, not the DB key - search by itemID
                for _, recipe in pairs(HDG_DecorDB) do
                    if recipe.itemID == item.id then
                        profession = recipe.profession
                        break
                    end
                end
            end
            local profIcon = frame:CreateTexture(nil, "ARTWORK")
            profIcon:SetSize(PROF_ICON_SIZE, PROF_ICON_SIZE)
            profIcon:SetPoint("LEFT", 4 + EXPAND_ICON_WIDTH, 0)
            profIcon:SetTexture(HDG_ExpansionData.GetProfessionIcon(profession))
            -- Custom icons have proper transparency, no mask needed

            local name = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            name:SetPoint("LEFT", 4 + EXPAND_ICON_WIDTH + PROF_ICON_SIZE + 4, 0)
            name:SetPoint("RIGHT", -CRAFT_BTN_WIDTH, 0) -- Always reserve space for craft button area
            name:SetJustifyH("LEFT")
            name:SetTextColor(1, 1, 1)
            local localizedQueueName = Data().GetLocalizedName(item.id, item.name)
            name:SetText(string.format("%dx %s", item.quantity, localizedQueueName))

            -- Craft button (only if known and craftable)
            if isKnown and canCraft then
                -- Determine button text based on craftable count
                local craftText = maxCraftable > 99 and "99+" or tostring(maxCraftable)
                local btnWidth = 34 + (string.len(craftText) > 1 and 6 or 0) + (string.len(craftText) > 2 and 4 or 0)

                local craftBtn = CreateFrame("Button", nil, frame, "BackdropTemplate")
                craftBtn:SetSize(btnWidth, 16)
                craftBtn:SetPoint("RIGHT", -2, 0)

                -- 3D effect with gradient background and glow
                craftBtn:SetBackdrop({
                    bgFile = "Interface\\Buttons\\WHITE8x8",
                    edgeFile = "Interface\\Buttons\\WHITE8x8",
                    edgeSize = 1,
                })
                -- Bright green base color
                craftBtn:SetBackdropColor(0.15, 0.7, 0.15, 1)
                craftBtn:SetBackdropBorderColor(0.1, 0.5, 0.1, 1)

                -- Top highlight for 3D bevel effect
                local highlight = craftBtn:CreateTexture(nil, "ARTWORK")
                highlight:SetTexture("Interface\\Buttons\\WHITE8x8")
                highlight:SetPoint("TOPLEFT", 1, -1)
                highlight:SetPoint("TOPRIGHT", -1, -1)
                highlight:SetHeight(6)
                highlight:SetGradient("VERTICAL", CreateColor(0.5, 1, 0.5, 0.4), CreateColor(0.2, 0.8, 0.2, 0))

                -- Bottom shadow for 3D depth
                local shadow = craftBtn:CreateTexture(nil, "ARTWORK")
                shadow:SetTexture("Interface\\Buttons\\WHITE8x8")
                shadow:SetPoint("BOTTOMLEFT", 1, 1)
                shadow:SetPoint("BOTTOMRIGHT", -1, 1)
                shadow:SetHeight(4)
                shadow:SetGradient("VERTICAL", CreateColor(0, 0.3, 0, 0.6), CreateColor(0.1, 0.5, 0.1, 0))

                -- Glow border effect
                local glow = craftBtn:CreateTexture(nil, "BACKGROUND", nil, -1)
                glow:SetTexture("Interface\\Buttons\\WHITE8x8")
                glow:SetPoint("TOPLEFT", -2, 2)
                glow:SetPoint("BOTTOMRIGHT", 2, -2)
                glow:SetVertexColor(0.3, 1, 0.3, 0.3)

                -- Button text with count
                local btnText = craftBtn:CreateFontString(nil, "OVERLAY")
                btnText:SetFont(STANDARD_TEXT_FONT, 9, "OUTLINE")
                btnText:SetPoint("CENTER", 0, 0)
                btnText:SetText(string.format("|cFFFFFFFF%s|r", craftText))
                btnText:SetShadowOffset(1, -1)
                btnText:SetShadowColor(0, 0, 0, 0.8)

                craftBtn:SetScript("OnEnter", function(self)
                    -- Brighter on hover
                    self:SetBackdropColor(0.2, 0.85, 0.2, 1)
                    self:SetBackdropBorderColor(0.15, 0.65, 0.15, 1)
                    glow:SetVertexColor(0.4, 1, 0.4, 0.5)
                    GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
                    GameTooltip:SetText("|cFF00FF00Craft Now|r", 1, 1, 1)
                    GameTooltip:AddLine(string.format("%s", localizedQueueName), 1, 1, 1)
                    GameTooltip:AddLine(" ")
                    GameTooltip:AddLine(string.format("Queued: |cFFfcd34d%d|r", item.quantity), 0.7, 0.7, 0.7)
                    GameTooltip:AddLine(string.format("Can craft: |cFF00FF00%d|r", maxCraftable), 0.7, 0.7, 0.7)
                    GameTooltip:AddLine(" ")
                    GameTooltip:AddLine("|cFFfcd34dClick|r to craft with confirmation", 0.7, 0.7, 0.7)
                    GameTooltip:AddLine("|cFFfcd34dShift-Click|r to open in profession window", 0.7, 0.7, 0.7)
                    GameTooltip:Show()
                end)
                craftBtn:SetScript("OnLeave", function(self)
                    -- Normal state
                    self:SetBackdropColor(0.15, 0.7, 0.15, 1)
                    self:SetBackdropBorderColor(0.1, 0.5, 0.1, 1)
                    glow:SetVertexColor(0.3, 1, 0.3, 0.3)
                    GameTooltip:Hide()
                end)
                craftBtn:SetScript("OnMouseDown", function(self)
                    -- Pressed state - darker, invert 3D effect
                    self:SetBackdropColor(0.1, 0.5, 0.1, 1)
                    highlight:SetGradient("VERTICAL", CreateColor(0, 0.3, 0, 0.4), CreateColor(0.1, 0.5, 0.1, 0))
                    shadow:SetGradient("VERTICAL", CreateColor(0.3, 0.8, 0.3, 0.3), CreateColor(0.1, 0.5, 0.1, 0))
                    btnText:SetPoint("CENTER", 0, -1)
                end)
                craftBtn:SetScript("OnMouseUp", function(self)
                    -- Reset to normal
                    self:SetBackdropColor(0.2, 0.85, 0.2, 1)
                    highlight:SetGradient("VERTICAL", CreateColor(0.5, 1, 0.5, 0.4), CreateColor(0.2, 0.8, 0.2, 0))
                    shadow:SetGradient("VERTICAL", CreateColor(0, 0.3, 0, 0.6), CreateColor(0.1, 0.5, 0.1, 0))
                    btnText:SetPoint("CENTER", 0, 0)
                end)
                craftBtn:SetScript("OnClick", function()
                    if item.spell then
                        if IsShiftKeyDown() then
                            -- Shift-click: Open in profession window only (no craft)
                            -- Need to call twice: first opens profession, second navigates to recipe
                            if C_TradeSkillUI and C_TradeSkillUI.OpenRecipe then
                                C_TradeSkillUI.OpenRecipe(item.spell)
                                C_Timer.After(0.2, function()
                                    C_TradeSkillUI.OpenRecipe(item.spell)
                                end)
                            end
                        else
                            -- Normal click: Open profession window first, then show confirmation
                            local isTradeSkillOpen = C_TradeSkillUI and C_TradeSkillUI.IsTradeSkillReady and
                            C_TradeSkillUI.IsTradeSkillReady()
                            if isTradeSkillOpen then
                                -- Already open, but call OpenRecipe to navigate to correct recipe
                                C_TradeSkillUI.OpenRecipe(item.spell)
                                C_Timer.After(0.1, function()
                                    HDG_RecipesTab.ShowCraftConfirmation(item, i)
                                end)
                            else
                                -- Open profession window first, then show confirmation
                                if C_TradeSkillUI and C_TradeSkillUI.OpenRecipe then
                                    C_TradeSkillUI.OpenRecipe(item.spell)
                                    C_Timer.After(0.3, function()
                                        -- Call again to navigate to the recipe
                                        C_TradeSkillUI.OpenRecipe(item.spell)
                                        C_Timer.After(0.1, function()
                                            HDG_RecipesTab.ShowCraftConfirmation(item, i)
                                        end)
                                    end)
                                end
                            end
                        end
                    end
                end)
            end

            frame:SetScript("OnClick", function(_, button)
                if button == "RightButton" then
                    if IsShiftKeyDown() then
                        -- Shift+Right-click: Remove all
                        table.remove(HDG_Settings.shoppingList, i)
                        expandedShoppingItems[item.id] = nil -- Clean up expanded state
                        HDG_UI.RefreshUI()
                    elseif hasSubsidiaries then
                        -- Right-click: Toggle subsidiary recipes
                        expandedShoppingItems[item.id] = not expandedShoppingItems[item.id]
                        HDG_UI.RefreshUI()
                    else
                        -- No subsidiaries, right-click removes all
                        table.remove(HDG_Settings.shoppingList, i)
                        HDG_UI.RefreshUI()
                    end
                else
                    -- Left-click: Remove one
                    item.quantity = item.quantity - 1
                    if item.quantity <= 0 then
                        table.remove(HDG_Settings.shoppingList, i)
                        expandedShoppingItems[item.id] = nil -- Clean up expanded state
                    end
                    HDG_UI.RefreshUI()
                end
            end)
            frame:RegisterForClicks("LeftButtonUp", "RightButtonUp")

            frame:SetScript("OnEnter", function(self)
                self:SetBackdropColor(0.3, 0.3, 0.35, 0.8)
                GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)

                -- Build tooltip content function (called after item info loads)
                local function BuildTooltipContent()
                    if item.reagents and #item.reagents > 0 then
                        GameTooltip:AddLine(string.format("|cFFe2e8f0Materials (x%d):|r", item.quantity))
                        for _, reagent in ipairs(item.reagents) do
                            local have = HDG_Data.GetItemCountWithVariants(reagent.id)
                            local need = (reagent.amount or 1) * item.quantity
                            local reagentName = Data().GetCachedItemName(reagent.id) or ("Item " .. reagent.id)
                            local color = have >= need and "|cFF00FF00" or (have > 0 and "|cFFfcd34d" or "|cFFFF5555")
                            GameTooltip:AddDoubleLine("  " .. reagentName, string.format("%s%d/%d|r", color, have, need),
                                1, 1, 1, 1, 1, 1)
                        end
                        GameTooltip:AddLine(" ")
                    end

                    GameTooltip:AddLine("|cFFFFFF00Left-click:|r Remove one", 0.7, 0.7, 0.7)
                    if hasSubsidiaries then
                        GameTooltip:AddLine(
                        "|cFFFFFF00Right-click:|r " .. (isExpanded and "Collapse" or "Show sub-recipes"), 0.7, 0.7, 0.7)
                        GameTooltip:AddLine("|cFFFFFF00Shift+Right-click:|r Remove all", 0.7, 0.7, 0.7)
                    else
                        GameTooltip:AddLine("|cFFFFFF00Right-click:|r Remove all", 0.7, 0.7, 0.7)
                    end
                    GameTooltip:Show()
                end

                -- Use item hyperlink for instant tooltip (avoids async delay)
                local _, itemLink = C_Item.GetItemInfo(item.id)
                if itemLink then
                    GameTooltip:SetHyperlink(itemLink)
                    GameTooltip:AddLine(" ")
                    BuildTooltipContent()
                else
                    -- Item not cached - use SetItemByID and add content after short delay
                    GameTooltip:SetItemByID(item.id)
                    C_Timer.After(0.05, function()
                        if GameTooltip:IsShown() and GameTooltip:GetOwner() == self then
                            GameTooltip:AddLine(" ")
                            BuildTooltipContent()
                        end
                    end)
                end
            end)
            frame:SetScript("OnLeave", function(self)
                self:SetBackdropColor(unpack(GetShoppingItemColor(item)))
                GameTooltip:Hide()
            end)

            yOffset = yOffset - (CONFIG.SHOPPING_ITEM_HEIGHT + 2)

            -- Render subsidiary recipes if expanded
            if isExpanded then
                local subsidiaries = GetSubsidiaryRecipes(item)
                local SUB_INDENT = 16
                local SUB_HEIGHT = 18

                for _, sub in ipairs(subsidiaries) do
                    local subFrame = CreateFrame("Frame", nil, mainFrame.shoppingContent, "BackdropTemplate")
                    subFrame:SetSize(CONFIG.SHOPPING_ITEM_WIDTH - SUB_INDENT, SUB_HEIGHT)
                    subFrame:SetPoint("TOPLEFT", CONFIG.SPACING.ITEM_PADDING + SUB_INDENT, yOffset)
                    subFrame:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
                    subFrame:SetBackdropColor(0.12, 0.12, 0.15, 0.7)

                    -- Profession icon
                    local subProfIcon = subFrame:CreateTexture(nil, "ARTWORK")
                    subProfIcon:SetSize(14, 14)
                    subProfIcon:SetPoint("LEFT", 4, 0)
                    subProfIcon:SetTexture(HDG_ExpansionData.GetProfessionIcon(sub.profession))

                    -- Sub-recipe name
                    local subName = subFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                    subName:SetPoint("LEFT", 22, 0)
                    subName:SetPoint("RIGHT", -35, 0)
                    subName:SetJustifyH("LEFT")
                    subName:SetTextColor(0.75, 0.75, 0.75)
                    subName:SetText(sub.name or ("Item " .. sub.itemID))

                    -- Quantity
                    local subQty = subFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                    subQty:SetPoint("RIGHT", -4, 0)
                    subQty:SetJustifyH("RIGHT")
                    subQty:SetText("x" .. sub.qty)
                    subQty:SetTextColor(0.98, 0.84, 0.34)

                    -- Tooltip with reagents
                    subFrame:EnableMouse(true)
                    subFrame:SetScript("OnEnter", function(self)
                        self:SetBackdropColor(0.2, 0.2, 0.25, 0.9)
                        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
                        GameTooltip:SetText(sub.name or ("Item " .. sub.itemID), 1, 1, 1)
                        if sub.profession then
                            GameTooltip:AddLine(sub.profession .. (sub.expansion and (" - " .. sub.expansion) or ""), 0.7,
                                0.7, 0.7)
                        end
                        if sub.slots and #sub.slots > 0 then
                            GameTooltip:AddLine(" ")
                            GameTooltip:AddLine("Reagents per craft:", 0.98, 0.84, 0.34)
                            for _, slot in ipairs(sub.slots) do
                                -- Only show basic (required) slots
                                if slot.type == "basic" and slot.itemID then
                                    local reagentName = slot.name or
                                    (HDG_PowerCrafting and HDG_PowerCrafting.GetItemName(slot.itemID)) or
                                    ("Item " .. slot.itemID)
                                    GameTooltip:AddDoubleLine("  " .. reagentName, "x" .. (slot.qty or 1), 0.8, 0.8, 0.8,
                                        0.98, 0.84, 0.34)
                                end
                            end
                        end
                        GameTooltip:Show()
                    end)
                    subFrame:SetScript("OnLeave", function(self)
                        self:SetBackdropColor(0.12, 0.12, 0.15, 0.7)
                        GameTooltip:Hide()
                    end)

                    yOffset = yOffset - (SUB_HEIGHT + 1)
                end
            end
        end -- end items loop
    end     -- end character groups loop
    mainFrame.shoppingContent:SetHeight(math.abs(yOffset))
end

-- ============================================================================
-- MATERIALS REFRESH
-- ============================================================================

function HDG_RecipesTab:RefreshMaterials()
    if not mainFrame or not mainFrame.materialsContent then return end
    ClearScrollContent(mainFrame.materialsContent)

    -- Update Auctionator button text based on mode
    if mainFrame.addAllAuctionatorText then
        if HDG_Settings.showRawMaterials and HDG_PowerCrafting then
            mainFrame.addAllAuctionatorText:SetText(L["BTN_ADD_ALL_AUCTIONATOR_RAW"] or
            "Add All to Auctionator (Raw Mats)")
        else
            mainFrame.addAllAuctionatorText:SetText(L["BTN_ADD_ALL_AUCTIONATOR"] or "Add All to Auctionator")
        end
    end

    local materials, intermediates
    if HDG_Settings.showRawMaterials and HDG_PowerCrafting then
        -- Power Crafting mode: expand crafted reagents to raw materials
        materials, intermediates = HDG_RecipesTab.AggregateRawMaterials(HDG_Settings.shoppingList)
    else
        -- Direct mode: show materials as listed in recipes
        materials = HDG_RecipesTab.AggregateMaterials(HDG_Settings.shoppingList)
        intermediates = nil
    end
    local sortedMaterials = HDG_RecipesTab.FilterAndSortMaterials(materials)
    local categorized = HDG_RecipesTab.CategorizeMaterials(sortedMaterials)

    local yOffset = 0
    local categoryOrder = { "vendor", "gather", "craft", "unknown" }

    for _, catKey in ipairs(categoryOrder) do
        local category = categorized[catKey]
        if category and #category.items > 0 then
            local isCollapsed = HDG_Settings.collapsedMaterials[catKey]

            -- Category Header
            local header = CreateFrame("Button", nil, mainFrame.materialsContent, "BackdropTemplate")
            header:SetSize(CONFIG.MATERIAL_ITEM_WIDTH, CONFIG.SPACING.HEADER_HEIGHT)
            header:SetPoint("TOPLEFT", 0, yOffset)
            header:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
            header:SetBackdropColor(unpack(CONFIG.COLORS.HEADER_NORMAL))

            local collapseIcon = header:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
            collapseIcon:SetPoint("LEFT", 6, 0)
            collapseIcon:SetText(isCollapsed and "|cFFfcd34d+|r" or "|cFFfcd34d-|r")

            local icon = header:CreateTexture(nil, "ARTWORK")
            icon:SetSize(14, 14)
            icon:SetPoint("LEFT", 20, 0)
            icon:SetTexture(category.icon)

            local headerText = header:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            headerText:SetPoint("LEFT", 40, 0)
            headerText:SetText(category.name)
            headerText:SetTextColor(unpack(CONFIG.COLORS.HEADER_TEXT))

            header:SetScript("OnClick", function()
                HDG_Settings.collapsedMaterials[catKey] = not HDG_Settings.collapsedMaterials[catKey]
                HDG_RecipesTab:RefreshMaterials()
            end)

            header:SetScript("OnEnter", function(self) self:SetBackdropColor(unpack(CONFIG.COLORS.HEADER_HOVER)) end)
            header:SetScript("OnLeave", function(self) self:SetBackdropColor(unpack(CONFIG.COLORS.HEADER_NORMAL)) end)

            yOffset = yOffset - (CONFIG.SPACING.HEADER_HEIGHT + 2)

            if not isCollapsed then
                for _, mat in ipairs(category.items) do
                    local row = CreateFrame("StatusBar", nil, mainFrame.materialsContent, "BackdropTemplate")
                    row:SetSize(CONFIG.MATERIAL_ITEM_WIDTH, CONFIG.MATERIAL_ITEM_HEIGHT)
                    row:SetPoint("TOPLEFT", 4, yOffset)
                    row:SetStatusBarTexture(CONFIG.TEXTURES.STATUSBAR)
                    row:SetMinMaxValues(0, mat.needed)
                    row:SetValue(math.min(mat.have, mat.needed))

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

                    local expColor = HDG_ExpansionData.GetColor(mat.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(unpack(CONFIG.COLORS.MATERIAL_NEED_BG))
                    end

                    local matName = Data().GetCachedItemName(mat.id)
                    local nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                    nameText:SetPoint("LEFT", 4, 0)
                    nameText:SetWidth(150)
                    nameText:SetJustifyH("LEFT")
                    nameText:SetText(matName)
                    nameText:SetFont(nameText:GetFont(), 10, "")

                    if mat.have >= mat.needed then
                        nameText:SetTextColor(unpack(CONFIG.COLORS.MATERIAL_HAVE_TEXT))
                    else
                        nameText:SetTextColor(unpack(CONFIG.COLORS.MATERIAL_NEED_TEXT))
                    end

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

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

                    -- Store price for total
                    local matPrice = Data().GetItemPrice(mat.id)
                    if matPrice then
                        local stillNeeded = math.max(0, mat.needed - mat.have)
                        mat.estimatedCost = matPrice * stillNeeded
                    end

                    -- Click handler
                    local clickButton = CreateFrame("Button", nil, row)
                    clickButton:SetAllPoints(row)
                    clickButton:RegisterForClicks("LeftButtonUp", "RightButtonUp")
                    clickButton:SetScript("OnClick", function(_, button)
                        local itemName = matName
                        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(itemName)
                                print("|cFFfcd34d[Housing Decor Guide]|r Set AH search text to:", itemName)
                            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", { itemName })
                                print("|cFFfcd34d[Housing Decor Guide]|r Searching Auctionator for:", itemName)
                            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 == itemName then
                                        exists = true; break
                                    end
                                end
                                if not exists then table.insert(existingItems, itemName) end
                                if Auctionator.API.v1.CreateShoppingList then
                                    Auctionator.API.v1.CreateShoppingList(callerID, listName, existingItems)
                                    print("|cFFfcd34d[Housing Decor Guide]|r Added '" ..
                                    itemName .. "' to Auctionator search list: " .. listName)
                                end
                            else
                                print(
                                "|cFFfcd34d[Housing Decor Guide]|r Please install Auctionator for search list integration")
                            end
                        elseif button == "LeftButton" and IsControlKeyDown() then
                            -- Ctrl+Click to track reagent for gathering (only for gathered items)
                            if catKey == "gather" and HDG_LumberTracker and HDG_LumberTracker.TrackReagent then
                                HDG_LumberTracker.TrackReagent(mat.id, itemName, mat.needed)
                            end
                        end
                    end)

                    clickButton:SetScript("OnEnter", function(self)
                        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
                        GameTooltip:SetItemByID(mat.id)
                        GameTooltip:AddLine(" ")
                        GameTooltip:AddLine("Used in:", 1, 1, 1)
                        for recipeName, data in pairs(mat.sources) do
                            GameTooltip:AddDoubleLine(recipeName, string.format("%d (x%d)", data.count, data.perCraft),
                                0.8, 0.8, 0.8, 1, 1, 1)
                        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 in Auctionator/TSM", 0.7, 0.7, 0.7)
                        if catKey == "gather" then
                            GameTooltip:AddLine("|cFF00FF00Ctrl-Click|r to track for gathering", 0.7, 0.7, 0.7)
                        end
                        GameTooltip:AddLine("|cFF00FF00Right-Click|r to add to Auctionator search list", 0.7, 0.7, 0.7)
                        GameTooltip:Show()
                    end)
                    clickButton:SetScript("OnLeave", function() GameTooltip:Hide() end)

                    yOffset = yOffset - (CONFIG.MATERIAL_ITEM_HEIGHT + 1)
                end
                yOffset = yOffset - 6
            end
        end
    end

    -- Show intermediates section in Raw mode (crafted sub-components)
    if intermediates and HDG_Settings.showRawMaterials then
        -- Convert to sorted array
        local intermediateArray = {}
        for _, inter in pairs(intermediates) do
            if inter.needed > 0 then
                table.insert(intermediateArray, inter)
            end
        end

        if #intermediateArray > 0 then
            -- Sort by name
            table.sort(intermediateArray, function(a, b)
                return (a.name or "") < (b.name or "")
            end)

            local isCollapsed = HDG_Settings.collapsedMaterials["intermediates"]

            -- Intermediates Header
            local header = CreateFrame("Button", nil, mainFrame.materialsContent, "BackdropTemplate")
            header:SetSize(CONFIG.MATERIAL_ITEM_WIDTH, CONFIG.SPACING.HEADER_HEIGHT)
            header:SetPoint("TOPLEFT", 0, yOffset)
            header:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
            header:SetBackdropColor(0.15, 0.25, 0.25, 0.9)

            local collapseIcon = header:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
            collapseIcon:SetPoint("LEFT", 6, 0)
            collapseIcon:SetText(isCollapsed and "|cFF14b8a6+|r" or "|cFF14b8a6-|r")

            local icon = header:CreateTexture(nil, "ARTWORK")
            icon:SetSize(14, 14)
            icon:SetPoint("LEFT", 20, 0)
            icon:SetTexture("Interface\\Icons\\Trade_Engineering")

            local headerText = header:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            headerText:SetPoint("LEFT", 40, 0)
            headerText:SetText("|cFF14b8a6Craft These First|r")

            header:SetScript("OnClick", function()
                HDG_Settings.collapsedMaterials["intermediates"] = not HDG_Settings.collapsedMaterials["intermediates"]
                HDG_RecipesTab:RefreshMaterials()
            end)

            header:SetScript("OnEnter", function(self)
                self:SetBackdropColor(0.2, 0.35, 0.35, 0.9)
                GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
                GameTooltip:AddLine("|cFF14b8a6Craft These First|r", 1, 1, 1)
                GameTooltip:AddLine("Intermediate items you need to craft", 0.7, 0.7, 0.7)
                GameTooltip:AddLine("before making the final decor.", 0.7, 0.7, 0.7)
                GameTooltip:Show()
            end)
            header:SetScript("OnLeave", function(self)
                self:SetBackdropColor(0.15, 0.25, 0.25, 0.9)
                GameTooltip:Hide()
            end)

            yOffset = yOffset - (CONFIG.SPACING.HEADER_HEIGHT + 2)

            if not isCollapsed then
                for _, inter in ipairs(intermediateArray) do
                    -- Intermediate item row (parent)
                    local row = CreateFrame("Frame", nil, mainFrame.materialsContent, "BackdropTemplate")
                    row:SetSize(CONFIG.MATERIAL_ITEM_WIDTH, CONFIG.MATERIAL_ITEM_HEIGHT)
                    row:SetPoint("TOPLEFT", 4, yOffset)
                    row:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
                    row:SetBackdropColor(0.1, 0.2, 0.2, 0.6)

                    -- Look up profession from ProfessionsDB for this intermediate
                    local interProfession = nil
                    local interRecipe = GetRecipeByItemID(inter.id)
                    if interRecipe then
                        interProfession = interRecipe.profession
                    end

                    -- Profession icon
                    local PROF_ICON_SIZE = 14
                    local profIcon = row:CreateTexture(nil, "ARTWORK")
                    profIcon:SetSize(PROF_ICON_SIZE, PROF_ICON_SIZE)
                    profIcon:SetPoint("LEFT", 4, 0)
                    profIcon:SetTexture(HDG_ExpansionData.GetProfessionIcon(interProfession))

                    local interName = inter.name or ("Item #" .. inter.id)
                    local nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                    nameText:SetPoint("LEFT", 4 + PROF_ICON_SIZE + 4, 0)
                    nameText:SetWidth(145)
                    nameText:SetJustifyH("LEFT")
                    nameText:SetText(interName)
                    nameText:SetFont(nameText:GetFont(), 10, "")
                    nameText:SetTextColor(0.9, 0.9, 0.9, 1)

                    local countStr = string.format("x%d", inter.needed)
                    local countText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                    countText:SetPoint("RIGHT", -10, 0)
                    countText:SetText(countStr)
                    countText:SetFont(countText:GetFont(), 10, "")
                    countText:SetTextColor(0.08, 0.72, 0.65, 1)

                    row:EnableMouse(true)
                    row:SetScript("OnEnter", function(self)
                        self:SetBackdropColor(0.15, 0.3, 0.3, 0.8)
                        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
                        GameTooltip:SetItemByID(inter.id)
                        GameTooltip:AddLine(" ")
                        GameTooltip:AddLine("|cFF14b8a6Craft " .. inter.needed .. "x|r", 1, 1, 1)

                        -- Show recipe being used and alternatives
                        if HDG_PowerCrafting and HDG_PowerCrafting.GetAllRecipesForItem then
                            local allRecipes = HDG_PowerCrafting.GetAllRecipesForItem(inter.id)
                            if #allRecipes > 0 then
                                -- Show the preferred recipe
                                local preferred = allRecipes[1]
                                if preferred then
                                    local profColor = "|cFFfcd34d" -- Gold for profession
                                    GameTooltip:AddLine(" ")
                                    GameTooltip:AddLine(
                                    "Via: " ..
                                    (preferred.name or "Unknown") ..
                                    " " .. profColor .. "(" .. (preferred.profession or "?") .. ")|r", 0.9, 0.9, 0.9)

                                    -- Show materials for this recipe
                                    if preferred.slots then
                                        for _, slot in ipairs(preferred.slots) do
                                            if slot.type == "basic" and slot.itemID then
                                                local matName = slot.name or HDG_PowerCrafting.GetItemName(slot.itemID) or
                                                ("Item #" .. slot.itemID)
                                                GameTooltip:AddDoubleLine("  " .. matName, "x" .. (slot.qty or 1), 0.7,
                                                    0.7, 0.7, 0.8, 0.8, 0.8)
                                            end
                                        end
                                    end
                                end

                                -- Show alternatives if any
                                if #allRecipes > 1 then
                                    GameTooltip:AddLine(" ")
                                    GameTooltip:AddLine("Alternative Recipes:", 0.6, 0.8, 0.6)
                                    for i = 2, #allRecipes do
                                        local alt = allRecipes[i]
                                        GameTooltip:AddLine(
                                        "  " ..
                                        (alt.name or "Unknown") .. " |cFFfcd34d(" .. (alt.profession or "?") .. ")|r",
                                            0.6, 0.6, 0.6)
                                    end
                                end
                            end
                        elseif inter.children and next(inter.children) then
                            -- Fallback: show children if no recipe info available
                            GameTooltip:AddLine("Requires:", 0.7, 0.7, 0.7)
                            for childID, childData in pairs(inter.children) do
                                if childData.source ~= "Ignore" then
                                    local totalNeeded = childData.perCraft * inter.needed
                                    GameTooltip:AddDoubleLine("  " .. childData.name, "x" .. totalNeeded, 0.8, 0.8, 0.8,
                                        1, 1, 1)
                                end
                            end
                        end
                        GameTooltip:Show()
                    end)
                    row:SetScript("OnLeave", function(self)
                        self:SetBackdropColor(0.1, 0.2, 0.2, 0.6)
                        GameTooltip:Hide()
                    end)

                    yOffset = yOffset - (CONFIG.MATERIAL_ITEM_HEIGHT + 1)

                    -- Show children (raw materials for this intermediate) indented
                    if inter.children and next(inter.children) then
                        for childID, childData in pairs(inter.children) do
                            -- Skip finishing reagents (should already be filtered, but double-check)
                            if childData.source == "Ignore" then
                                -- Skip this child entirely
                            else
                                local childRow = CreateFrame("Frame", nil, mainFrame.materialsContent, "BackdropTemplate")
                                childRow:SetSize(CONFIG.MATERIAL_ITEM_WIDTH - 12, CONFIG.MATERIAL_ITEM_HEIGHT - 2)
                                childRow:SetPoint("TOPLEFT", 16, yOffset)
                                childRow:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })
                                childRow:SetBackdropColor(0.08, 0.12, 0.12, 0.4)

                                local childName = childData.name or ("Item #" .. childID)
                                local childNameText = childRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                                childNameText:SetPoint("LEFT", 8, 0)
                                childNameText:SetWidth(140)
                                childNameText:SetJustifyH("LEFT")
                                childNameText:SetText("- " .. childName)
                                local childNameFont = childNameText:GetFont()
                                if childNameFont then childNameText:SetFont(childNameFont, 9, "") end
                                childNameText:SetTextColor(0.6, 0.6, 0.6, 1)

                                local totalChildNeeded = (tonumber(childData.perCraft) or 1) *
                                (tonumber(inter.needed) or 1)
                                local childCountText = childRow:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                                childCountText:SetPoint("RIGHT", -8, 0)
                                childCountText:SetText("x" .. totalChildNeeded)
                                local childCountFont = childCountText:GetFont()
                                if childCountFont then childCountText:SetFont(childCountFont, 9, "") end
                                childCountText:SetTextColor(0.5, 0.5, 0.5, 1)

                                childRow:EnableMouse(true)
                                local tooltipItemID = childData
                                .id                            -- Use stored ID, not key (key is name after consolidation)
                                childRow:SetScript("OnEnter", function(self)
                                    self:SetBackdropColor(0.12, 0.18, 0.18, 0.6)
                                    GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
                                    if tooltipItemID and type(tooltipItemID) == "number" and tooltipItemID > 0 then
                                        GameTooltip:SetItemByID(tooltipItemID)
                                    else
                                        GameTooltip:SetText(childData.name or "Unknown Item", 1, 1, 1)
                                    end
                                    GameTooltip:Show()
                                end)
                                childRow:SetScript("OnLeave", function(self)
                                    self:SetBackdropColor(0.08, 0.12, 0.12, 0.4)
                                    GameTooltip:Hide()
                                end)

                                yOffset = yOffset - (CONFIG.MATERIAL_ITEM_HEIGHT - 1)
                            end -- end else (not Ignore)
                        end
                    end
                end
                yOffset = yOffset - 6
            end
        end
    end

    if yOffset == 0 then
        local empty = mainFrame.materialsContent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        empty:SetPoint("CENTER", 0, -50)
        empty:SetText("|cFF888888Add recipes to crafting queue|r")
        if mainFrame.materialsCostText then
            mainFrame.materialsCostText:SetText("")
        end
    else
        if mainFrame.materialsCostText then
            local hasPriceAddon, priceAddonName = Data().HasPriceAddon()
            if hasPriceAddon then
                local totalCost = 0
                local missingPrices = 0
                for _, catKey in ipairs(categoryOrder) do
                    local category = categorized[catKey]
                    if category and #category.items > 0 then
                        for _, mat in ipairs(category.items) do
                            if mat.estimatedCost then
                                totalCost = totalCost + mat.estimatedCost
                            elseif mat.have < mat.needed then
                                missingPrices = missingPrices + 1
                            end
                        end
                    end
                end

                local costStr = Data().FormatGold(totalCost)
                if missingPrices > 0 then
                    mainFrame.materialsCostText:SetText(string.format(
                    "|cFFe2e8f0Est. Cost:|r |cFFfcd34d~%s+|r |cFF888888(%d unknown)|r", costStr, missingPrices))
                elseif totalCost > 0 then
                    mainFrame.materialsCostText:SetText(string.format(
                    "|cFFe2e8f0Est. Cost:|r |cFFfcd34d~%s|r |cFF888888(via %s)|r", costStr, priceAddonName))
                else
                    mainFrame.materialsCostText:SetText("|cFFe2e8f0Est. Cost:|r |cFF88FF88Have all materials!|r")
                end
            else
                mainFrame.materialsCostText:SetText("|cFF888888Install TSM/Auctionator for price data|r")
            end
        end
    end

    mainFrame.materialsContent:SetHeight(math.abs(yOffset) + 20)
end

-- ============================================================================
-- ADD ALL MATERIALS TO AUCTIONATOR
-- ============================================================================

function HDG_RecipesTab:AddAllMaterialsToAuctionator()
    if not C_AddOns.IsAddOnLoaded("Auctionator") or not Auctionator or not Auctionator.API or not Auctionator.API.v1 then
        print("|cFFfcd34d[Housing Decor Guide]|r Please install Auctionator for search list integration")
        return
    end

    local materials
    local modeLabel

    -- Check which mode we're in (Direct vs Raw)
    if HDG_Settings.showRawMaterials and HDG_PowerCrafting then
        -- Raw mode: get raw materials only (not intermediates)
        -- In Raw mode, we want the base materials, not crafted sub-components
        materials = HDG_RecipesTab.AggregateRawMaterials(HDG_Settings.shoppingList)
        modeLabel = "raw"
    else
        -- Direct mode: get materials as listed in recipes
        materials = HDG_RecipesTab.AggregateMaterials(HDG_Settings.shoppingList)
        modeLabel = "direct"
    end

    if not materials or next(materials) == nil then
        print("|cFFfcd34d[Housing Decor Guide]|r No materials in crafting queue")
        return
    end

    local callerID = "HousingDecorGuide"
    local listName = "Housing Decor"

    -- Check if ConvertToSearchString is available for quantity support
    local hasQuantitySupport = Auctionator.API.v1.ConvertToSearchString ~= nil

    local sortedMaterials = HDG_RecipesTab.FilterAndSortMaterials(materials)
    local categorized = HDG_RecipesTab.CategorizeMaterials(sortedMaterials)
    local searchStrings = {}
    local addedCount = 0

    -- In Raw mode, skip "craft" category since those are intermediates the player will craft
    -- In Direct mode, include all categories
    local categoryOrder
    if modeLabel == "raw" then
        categoryOrder = { "vendor", "gather", "unknown" } -- Exclude "craft" in raw mode
    else
        categoryOrder = { "vendor", "gather", "craft", "unknown" }
    end

    local skippedCount = 0 -- Track items skipped because player has enough
    for _, catKey in ipairs(categoryOrder) do
        local category = categorized[catKey]
        if category and #category.items > 0 then
            for _, mat in ipairs(category.items) do
                local itemName = Data().GetCachedItemName(mat.id)
                if itemName then
                    -- Calculate how many more the player needs (subtract what they have)
                    local totalNeeded = mat.needed or mat.total or 1
                    local playerHas = Data().GetItemCountWithVariants(mat.id) or 0
                    local stillNeeded = math.max(0, totalNeeded - playerHas)

                    -- Only add to list if player still needs some
                    if stillNeeded > 0 then
                        local searchString
                        if hasQuantitySupport then
                            -- Use the new API with quantity support
                            local term = {
                                searchString = itemName,
                                isExact = true,
                                quantity = stillNeeded,
                            }
                            searchString = Auctionator.API.v1.ConvertToSearchString(callerID, term)
                        else
                            -- Fallback to plain item name
                            searchString = itemName
                        end
                        table.insert(searchStrings, searchString)
                        addedCount = addedCount + 1
                    else
                        skippedCount = skippedCount + 1
                    end
                end
            end
        end
    end

    if Auctionator.API.v1.CreateShoppingList and addedCount > 0 then
        -- CreateShoppingList replaces the list entirely, so we always create fresh with current quantities
        Auctionator.API.v1.CreateShoppingList(callerID, listName, searchStrings)
        local modeText = modeLabel == "raw" and " (raw materials)" or ""
        local qtyText = hasQuantitySupport and " (adjusted for inventory)" or ""
        local skippedText = skippedCount > 0 and string.format(", %d already owned", skippedCount) or ""
        print(string.format("|cFFfcd34d[Housing Decor Guide]|r Added %d material(s)%s to Auctionator search list: %s%s%s",
            addedCount, qtyText, listName, modeText, skippedText))
    elseif addedCount == 0 then
        if skippedCount > 0 then
            print("|cFFfcd34d[Housing Decor Guide]|r You already have all the materials needed!")
        else
            print("|cFFfcd34d[Housing Decor Guide]|r No materials to add to search list")
        end
    end
end

-- ============================================================================
-- REFRESH PROFESSION TABS
-- ============================================================================

function HDG_RecipesTab:RefreshProfessionTabs()
    if not mainFrame or not mainFrame.professionTabs then return end

    local children = { mainFrame.professionTabs:GetChildren() }
    for _, child in ipairs(children) do child:Hide() end

    local COLLECTION_COL_RIGHT = -6
    local RECIPES_COL_RIGHT = -68
    local COL_WIDTH = 60

    local professionList = { "Alchemy", "Blacksmithing", "Cooking", "Enchanting", "Engineering",
        "Inscription", "Jewelcrafting", "Leatherworking", "Tailoring" }

    -- Get the per-character profession filter
    local currentFilter = HDG_RecipesTab.GetProfessionFilter()

    local yOffset = 0

    -- "All Professions" row at the top
    local allBtn = CreateFrame("Button", nil, mainFrame.professionTabs, "BackdropTemplate")
    allBtn:SetSize(CONFIG.LEFT_PANEL_WIDTH - 16, 18)
    allBtn:SetPoint("TOPLEFT", 0, yOffset)
    allBtn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })

    local isAllSelected = (currentFilter == "All")
    allBtn:SetBackdropColor(unpack(isAllSelected and CONFIG.COLORS.ITEM_SELECTED_BG or CONFIG.COLORS.ITEM_UNKNOWN_BG))

    local allHighlight = allBtn:CreateTexture(nil, "HIGHLIGHT")
    allHighlight:SetAllPoints()
    allHighlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
    allHighlight:SetBlendMode("ADD")
    if isAllSelected then
        allHighlight:SetVertexColor(1, 1, 1, 0.5); allHighlight:Show()
    else allHighlight:Hide() end

    local allProfsLabel = L["FILTER_ALL_PROFESSIONS"] or "All Professions"
    local allText = allBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    allText:SetPoint("LEFT", 6, 0)
    allText:SetText(isAllSelected and ("|cFFfcd34d" .. allProfsLabel .. "|r") or ("|cFFFFFFFF" .. allProfsLabel .. "|r"))

    allBtn:SetScript("OnClick", function()
        HDG_RecipesTab.SetProfessionFilter("All")
        HDG_UI.RefreshUI()
    end)

    allBtn:SetScript("OnEnter", function(self)
        if currentFilter ~= "All" then
            self:SetBackdropColor(unpack(CONFIG.COLORS.ITEM_UNKNOWN_HOVER))
        end
        GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
        GameTooltip:AddLine(allProfsLabel, 1, 0.84, 0)
        GameTooltip:AddLine("|cFF888888Show recipes from all professions|r")
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cFFfcd34dTip:|r Use search to find recipes", 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)

    allBtn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(unpack(currentFilter == "All" and CONFIG.COLORS.ITEM_SELECTED_BG or
        CONFIG.COLORS.ITEM_UNKNOWN_BG))
        GameTooltip:Hide()
    end)

    yOffset = yOffset - 20

    -- Separator line after "All Professions"
    local separator = mainFrame.professionTabs:CreateTexture(nil, "ARTWORK")
    separator:SetHeight(1)
    separator:SetPoint("TOPLEFT", 0, yOffset - 2)
    separator:SetPoint("TOPRIGHT", 0, yOffset - 2)
    separator:SetColorTexture(0.3, 0.3, 0.35, 0.5)
    yOffset = yOffset - 6

    for _, profName in ipairs(professionList) do
        local btn = CreateFrame("Button", nil, mainFrame.professionTabs, "BackdropTemplate")
        btn:SetSize(CONFIG.LEFT_PANEL_WIDTH - 16, 18)
        btn:SetPoint("TOPLEFT", 0, yOffset)
        btn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, tile = false })

        local localizedProfName = HDG_ExpansionData.GetLocalizedProfessionName(profName)
        local hasProf = Data().PlayerHasProfession(profName)
        local recipesKnown, collectedCount, totalCount = 0, 0, 0

        for _, profData in ipairs(privateDB.professions or {}) do
            if profData.name == profName and profData.items then
                for _, recipe in ipairs(profData.items) do
                    totalCount = totalCount + 1
                    if Data().PlayerKnowsRecipeBySpellID and Data().PlayerKnowsRecipeBySpellID(recipe.spell) then
                        recipesKnown = recipesKnown + 1
                    end
                    if Data().IsItemCollected(recipe.id) then
                        collectedCount = collectedCount + 1
                    end
                end
                break
            end
        end

        local recipesPercentage = totalCount > 0 and (recipesKnown / totalCount * 100) or 0
        local collectionPercentage = totalCount > 0 and (collectedCount / totalCount * 100) or 0
        local isSelected = (currentFilter == profName)

        btn:SetBackdropColor(unpack(isSelected and CONFIG.COLORS.ITEM_SELECTED_BG or CONFIG.COLORS.ITEM_UNKNOWN_BG))

        local highlight = btn:CreateTexture(nil, "HIGHLIGHT")
        highlight:SetAllPoints()
        highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
        highlight:SetBlendMode("ADD")
        if isSelected then
            highlight:SetVertexColor(1, 1, 1, 0.5); highlight:Show()
        else highlight:Hide() end

        local text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        text:SetPoint("LEFT", 6, 0)
        text:SetText((isSelected or hasProf) and "|cFFfcd34d" .. localizedProfName .. "|r" or
        "|cFFFFFFFF" .. localizedProfName .. "|r")

        local collectionBadge = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        collectionBadge:SetPoint("RIGHT", COLLECTION_COL_RIGHT, 0)
        collectionBadge:SetWidth(COL_WIDTH)
        collectionBadge:SetJustifyH("LEFT")
        local collectionColor = collectionPercentage >= 100 and "|cFF00FF00" or "|cFFfcd34d"
        collectionBadge:SetText(string.format("%s%d/%d|r", collectionColor, collectedCount, totalCount))

        local recipesBadge = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        recipesBadge:SetPoint("RIGHT", RECIPES_COL_RIGHT, 0)
        recipesBadge:SetWidth(COL_WIDTH)
        recipesBadge:SetJustifyH("LEFT")

        local altKnown, altTotal, altChar = Data().GetBestAltRecipeCount(profName)
        if hasProf then
            local recipesColor = recipesPercentage >= 100 and "|cFF00FF00" or "|cFF14b8a6"
            recipesBadge:SetText(string.format("%s%d/%d|r", recipesColor, recipesKnown, totalCount))
        elseif altKnown > 0 then
            recipesBadge:SetText(string.format("|cFFFFFFFF%d/%d|r", altKnown, altTotal))
            btn.altCharacter = altChar
        else
            recipesBadge:SetText("|cFF444444--|r")
        end

        btn.professionName = profName             -- Internal English name for filtering
        btn.localizedProfName = localizedProfName -- Localized name for display
        btn.hasProf = hasProf
        btn.recipesKnown = recipesKnown
        btn.collectedCount = collectedCount
        btn.totalCount = totalCount
        btn.recipesPercentage = recipesPercentage
        btn.collectionPercentage = collectionPercentage

        btn:SetScript("OnClick", function(self)
            HDG_RecipesTab.SetProfessionFilter(self.professionName)
            HDG_UI.RefreshUI()
        end)

        btn:SetScript("OnEnter", function(self)
            if currentFilter ~= self.professionName then
                self:SetBackdropColor(unpack(CONFIG.COLORS.ITEM_UNKNOWN_HOVER))
            end
            GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
            GameTooltip:AddLine(self.localizedProfName, 1, 0.84, 0)
            GameTooltip:AddLine(self.hasProf and "|cFF88FF88You have this profession|r" or
            "|cFF888888You don't have this profession|r")
            GameTooltip:AddLine(" ")
            if self.hasProf then
                GameTooltip:AddLine(
                string.format("|cFF14b8a6Recipes Known:|r %d / %d (%.0f%%)", self.recipesKnown, self.totalCount,
                    self.recipesPercentage), 1, 1, 1)
            elseif self.altCharacter then
                GameTooltip:AddLine("|cFF666666Known by alts|r", 0.5, 0.5, 0.5)
            end
            GameTooltip:AddLine(
            string.format("|cFFfcd34dDecor Collected:|r %d / %d (%.0f%%)", self.collectedCount, self.totalCount,
                self.collectionPercentage), 1, 1, 1)
            GameTooltip:Show()
        end)

        btn:SetScript("OnLeave", function(self)
            self:SetBackdropColor(unpack(currentFilter == self.professionName and CONFIG.COLORS.ITEM_SELECTED_BG or
            CONFIG.COLORS.ITEM_UNKNOWN_BG))
            GameTooltip:Hide()
        end)

        yOffset = yOffset - 20
    end

    -- Calculate totals for header tooltips (no visible TOTAL row)
    local grandTotalRecipes, grandKnownRecipes, grandTotalDecor, grandCollectedDecor = 0, 0, 0, 0
    for _, profData in ipairs(privateDB.professions or {}) do
        local hasProf = Data().PlayerHasProfession(profData.name)
        local profKnownRecipes = 0
        if profData.items then
            for _, recipe in ipairs(profData.items) do
                grandTotalDecor = grandTotalDecor + 1
                grandTotalRecipes = grandTotalRecipes + 1
                if hasProf and Data().PlayerKnowsRecipeBySpellID and Data().PlayerKnowsRecipeBySpellID(recipe.spell) then
                    profKnownRecipes = profKnownRecipes + 1
                end
                if Data().IsItemCollected(recipe.id) then grandCollectedDecor = grandCollectedDecor + 1 end
            end
        end
        if hasProf then
            grandKnownRecipes = grandKnownRecipes + profKnownRecipes
        else
            local altKnown = Data().GetBestAltRecipeCount(profData.name)
            if altKnown > 0 then grandKnownRecipes = grandKnownRecipes + altKnown end
        end
    end

    local collectionPct = grandTotalDecor > 0 and (grandCollectedDecor / grandTotalDecor * 100) or 0
    local recipesPct = grandTotalRecipes > 0 and (grandKnownRecipes / grandTotalRecipes * 100) or 0

    -- Update header percentages
    if mainFrame.headerCollectionPct then
        mainFrame.headerCollectionPct:SetText(string.format("%s%.0f%%|r",
            collectionPct >= 100 and "|cFF00FF00" or "|cFFfcd34d", collectionPct))
    end
    if mainFrame.headerRecipesPct then
        mainFrame.headerRecipesPct:SetText(string.format("%s%.0f%%|r", recipesPct >= 100 and "|cFF00FF00" or "|cFF14b8a6",
            recipesPct))
    end

    -- Set up tooltips on header frames with totals
    if mainFrame.recipesHeaderFrame then
        mainFrame.recipesHeaderFrame:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
            GameTooltip:AddLine("|cFF14b8a6Recipes Known|r", 1, 1, 1)
            GameTooltip:AddLine(
            string.format("Total: %d / %d (%.0f%%)", grandKnownRecipes, grandTotalRecipes, recipesPct), 0.9, 0.9, 0.9)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("Includes recipes known by you and alts", 0.6, 0.6, 0.6)
            GameTooltip:Show()
        end)
        mainFrame.recipesHeaderFrame:SetScript("OnLeave", function() GameTooltip:Hide() end)
    end

    if mainFrame.collectionHeaderFrame then
        mainFrame.collectionHeaderFrame:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
            GameTooltip:AddLine("|cFFfcd34dDecor Collected|r", 1, 1, 1)
            GameTooltip:AddLine(
            string.format("Total: %d / %d (%.0f%%)", grandCollectedDecor, grandTotalDecor, collectionPct), 0.9, 0.9, 0.9)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("Account-wide decor collection progress", 0.6, 0.6, 0.6)
            GameTooltip:Show()
        end)
        mainFrame.collectionHeaderFrame:SetScript("OnLeave", function() GameTooltip:Hide() end)
    end

    -- Update title based on selection
    if mainFrame.recipeTitle then
        local allProfsLabel = L["FILTER_ALL_PROFESSIONS"] or "All Professions"
        if HDG_RecipesTab.GetProfessionFilter() == "All" then
            mainFrame.recipeTitle:SetText("|cFFFFFFFF" .. allProfsLabel .. "|r")
        else
            mainFrame.recipeTitle:SetText("|cFFFFFFFFProfessions|r")
        end
    end
end

-- ============================================================================
-- REFRESH RECIPES (content area)
-- ============================================================================

function HDG_RecipesTab:RefreshRecipes()
    if not mainFrame or not mainFrame.recipeContent then return end

    if mainFrame.UpdateCraftableButton then mainFrame.UpdateCraftableButton() end

    ClearScrollContent(mainFrame.recipeContent)
    HideProfessionSectionHeader()
    self:RefreshProfessionTabs()

    local yOffset = 0
    local playerProfessions = Data().GetPlayerProfessions()
    local isAllMode = (HDG_RecipesTab.GetProfessionFilter() == "All")
    local isCraftableMode = HDG_Data.IsCraftableFilterEnabled()

    if #playerProfessions == 1 then
        -- Single profession selected - show header with profession name
        UpdateProfessionSectionHeader(playerProfessions[1].name)
        if mainFrame.stickyHeader then mainFrame.stickyHeader:Hide() end
    else
        -- Multiple professions (All mode) - show header with localized "All Professions"
        local allProfsLabel = L["FILTER_ALL_PROFESSIONS"] or "All Professions"
        UpdateProfessionSectionHeader(allProfsLabel)
        if mainFrame.stickyHeader then mainFrame.stickyHeader:Hide() end
    end

    -- Special case: All Professions + Ready mode = unified tier list (no profession headers)
    if isAllMode and isCraftableMode then
        -- Collect all recipes from all professions into unified tiers
        local tier100, tier75to99, tier50to74, tierBelow50 = {}, {}, {}, {}

        for _, profData in ipairs(playerProfessions) do
            local profName = profData.name
            local hasProfession = HDG_Data.PlayerHasProfession(profName)

            for _, recipe in ipairs(profData.items) do
                if HDG_RecipesTab.RecipeMatchesFilters(recipe) then
                    local isKnown = HDG_Data.PlayerKnowsRecipeBySpellID(recipe.spell)
                    local canCraft, maxQty, _, matPercent = HDG_RecipesTab.CanCraftRecipe(recipe)

                    recipe._maxCraftable = maxQty
                    recipe._materialPercent = matPercent
                    recipe._noProfession = not hasProfession
                    recipe._professionName = profName -- Track which profession for display

                    if hasProfession and isKnown and canCraft and maxQty > 0 then
                        table.insert(tier100, recipe)
                    elseif matPercent >= 75 then
                        table.insert(tier75to99, recipe)
                    elseif matPercent >= 50 then
                        table.insert(tier50to74, recipe)
                    elseif matPercent > 0 then
                        table.insert(tierBelow50, recipe)
                    end
                end
            end
        end

        local function sortByPercent(a, b)
            return (a._materialPercent or 0) > (b._materialPercent or 0)
        end
        table.sort(tier75to99, sortByPercent)
        table.sort(tier50to74, sortByPercent)
        table.sort(tierBelow50, sortByPercent)

        -- Render unified tiers
        local function renderTier(tier, label, color)
            if #tier > 0 then
                local tierHeader = mainFrame.recipeContent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                tierHeader:SetPoint("TOPLEFT", 8, yOffset)
                tierHeader:SetText(color .. label .. "|r |cFF888888(" .. #tier .. ")|r")
                yOffset = yOffset - 16
                for _, recipe in ipairs(tier) do
                    yOffset = RenderRecipe(recipe, yOffset, true, recipe._noProfession, true) -- showProfessionIcon = true
                end
                yOffset = yOffset - 6
            end
        end

        renderTier(tier100, "Ready to Craft", "|cFF00FF00")
        renderTier(tier75to99, "Almost Ready (75-99%)", "|cFF00FF00")
        renderTier(tier50to74, "Halfway There (50-74%)", "|cFFfcd34d")
        renderTier(tierBelow50, "Need Materials (<50%)", "|cFFFF5555")
    else
        -- Normal mode: render per profession
        for _, profData in ipairs(playerProfessions) do
            -- In "All" mode without craftable filter: show inline headers
            -- In single profession mode: skip inline headers, show fixed header
            local showInlineHeader = isAllMode and not isCraftableMode
            local skipFixedHeader = isAllMode
            yOffset = RenderProfession(profData, yOffset, showInlineHeader, skipFixedHeader)
        end
    end

    mainFrame.recipeContent:SetHeight(math.abs(yOffset) + 20)
end

-- ============================================================================
-- MAIN REFRESH (called by HDG_UI.RefreshUI)
-- ============================================================================

function HDG_RecipesTab:Refresh()
    self:RefreshRecipes()
    self:RefreshShoppingList()
    self:RefreshMaterials()
end

-- ============================================================================
-- SHOW/HIDE
-- ============================================================================

function HDG_RecipesTab:Show()
    if mainFrame.recipeFrame then mainFrame.recipeFrame:Show() end
    if mainFrame.shoppingFrame then mainFrame.shoppingFrame:Show() end
    if mainFrame.materialsFrame then mainFrame.materialsFrame:Show() end
    -- Initialize lumber layout based on pin state
    if HDG_UI and HDG_UI.UpdateLumberLayout then
        HDG_UI.UpdateLumberLayout()
    end
    self:Refresh()
end

function HDG_RecipesTab:Hide()
    if mainFrame.recipeFrame then mainFrame.recipeFrame:Hide() end
    if mainFrame.shoppingFrame then mainFrame.shoppingFrame:Hide() end
    if mainFrame.materialsFrame then mainFrame.materialsFrame:Hide() end
end

-- ============================================================================
-- EXPORT TO GLOBAL
-- ============================================================================

_G.HDG_RecipesTab = HDG_RecipesTab

return HDG_RecipesTab
