local addonName, privateDB = ...

-- ============================================================================
-- ALTS TAB MODULE
-- Displays character profession data across all alts
-- Extracted from HDG_UI.lua in v2.0.3 for maintainability
-- ============================================================================

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

local mainFrame = nil
local HDG = _G.HousingDecorGuide
local function GetScheme() return HDG.Theme.GetCurrentScheme() end

-- ============================================================================
-- ROW POOLS (avoid destroying/recreating frames on every Refresh)
-- ============================================================================

local EXPANSION_SLOTS = 12

local noProfCollapsed = true

local profRowPool = HDG_UIHelpers.CreateRowPool(function(parent)
    local row = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    row:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })

    row.profLabel = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.profLabel:SetPoint("LEFT", 8, 0)
    row.profLabel:SetJustifyH("LEFT")

    row.skillLabels = {}
    row.skillBtns = {}
    for i = 1, EXPANSION_SLOTS do
        local btn = CreateFrame("Button", nil, row)
        btn:SetSize(60, 18)
        local label = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        label:SetPoint("CENTER", 0, 0)
        label:SetJustifyH("CENTER")
        row.skillBtns[i] = btn
        row.skillLabels[i] = label
    end

    row.scanLabel = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.scanLabel:SetPoint("LEFT", 108, 0)
    row.scanLabel:SetJustifyH("LEFT")
    row.scanLabel:Hide()
    return row
end)

local headerRowPool = HDG_UIHelpers.CreateRowPool(function(parent)
    local row = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    row:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })

    row.label = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.label:SetPoint("LEFT", 8, 0)
    row.label:SetJustifyH("LEFT")

    row.expLabels = {}
    for i = 1, EXPANSION_SLOTS do
        row.expLabels[i] = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        row.expLabels[i]:SetJustifyH("CENTER")
    end
    return row
end)

local charHeaderPool = HDG_UIHelpers.CreateRowPool(function(parent)
    local row = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    row:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })

    row.label = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.label:SetPoint("LEFT", 8, 0)

    row.lumberIcon = row:CreateTexture(nil, "OVERLAY")
    row.lumberIcon:SetSize(16, 16)
    row.lumberIcon:SetPoint("RIGHT", row, "RIGHT", -155, 0)

    row.lumberText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.lumberText:SetPoint("LEFT", row.lumberIcon, "RIGHT", 4, 0)
    return row
end)

local dividerPool = HDG_UIHelpers.CreateRowPool(function(parent)
    return parent:CreateTexture(nil, "ARTWORK")
end)

local fontStringPool = HDG_UIHelpers.CreateRowPool(function(parent)
    return parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
end)

-- ============================================================================
-- THEME HELPERS (delegating to ThemeEngine canonical helpers)
-- ============================================================================

local function RegisterWidget(w, t) HDG.Theme.RegisterWidget(w, t) end
local ApplyFont = function(fs, s, ft) HDG.Theme.ApplyFont(fs, s, ft) end

-- Cache for recipe counts per profession/expansion
-- recipeCountCache[profName][expDisplayName] = { total = n, knownByChar = { [charKey] = count } }
local recipeCountCache = nil
local recipeCountCacheTime = 0
local RECIPE_CACHE_TTL = 60  -- Refresh every 60 seconds

-- Build recipe count cache from HDG_DecorDB
local function BuildRecipeCountCache()
    local now = GetTime()
    if recipeCountCache and (now - recipeCountCacheTime) < RECIPE_CACHE_TTL then
        return recipeCountCache
    end

    recipeCountCache = {}

    if not HDG_DecorDB then
        return recipeCountCache
    end

    -- Count total recipes per profession/expansion
    -- Note: DecorDB is keyed by recipeID, spellID is inside the recipe object
    for recipeID, recipe in pairs(HDG_DecorDB) do
        local profName = recipe.profession
        local expansion = recipe.expansion
        local spellID = recipe.spellID  -- Use the actual spellID from the recipe
        if profName and expansion and spellID then
            -- Normalize expansion name (e.g., "Outland Alchemy" -> "The Burning Crusade")
            local expDisplay = HDG_ExpansionData.NormalizeToDisplayName(expansion)

            if not recipeCountCache[profName] then
                recipeCountCache[profName] = {}
            end
            if not recipeCountCache[profName][expDisplay] then
                recipeCountCache[profName][expDisplay] = { total = 0, recipes = {} }
            end

            recipeCountCache[profName][expDisplay].total = recipeCountCache[profName][expDisplay].total + 1
            table.insert(recipeCountCache[profName][expDisplay].recipes, spellID)
        end
    end

    recipeCountCacheTime = now
    return recipeCountCache
end

-- Get known recipe count for any character (uses stored knownRecipes data)
-- Returns: known (number or nil if no data), total
local function GetKnownRecipeCountForChar(charKey, profName, expDisplayName)
    local cache = BuildRecipeCountCache()
    if not cache[profName] or not cache[profName][expDisplayName] then
        return nil, 0
    end

    local data = cache[profName][expDisplayName]
    local total = data.total

    -- Get stored known recipes for this character
    local charData = HDG_Settings.characters and HDG_Settings.characters[charKey]
    if not charData or not charData.professions or not charData.professions[profName] then
        return nil, total  -- nil = no data for this char/prof
    end

    local profData = charData.professions[profName]
    if not profData.knownRecipes then
        return nil, total  -- nil = no recipe data stored
    end

    -- Count how many of the decor recipes this character knows
    local known = 0
    for _, spellID in ipairs(data.recipes) do
        if profData.knownRecipes[spellID] then
            known = known + 1
        end
    end

    return known, total
end

-- Convenience: Get known recipe count for current character
local function GetKnownRecipeCount(profName, expDisplayName)
    local currentChar = HDG_Data.GetCharacterKey()
    return GetKnownRecipeCountForChar(currentChar, profName, expDisplayName)
end

-- Get best known recipe count across all characters
-- Returns: bestKnown (number or nil), total, bestCharKey
local function GetBestKnownRecipeCount(profName, expDisplayName)
    local cache = BuildRecipeCountCache()
    if not cache[profName] or not cache[profName][expDisplayName] then
        return nil, 0, nil
    end

    local data = cache[profName][expDisplayName]
    local total = data.total
    local bestKnown = nil
    local bestChar = nil

    -- Check all characters
    if HDG_Settings.characters then
        for charKey, charData in pairs(HDG_Settings.characters) do
            if charData.professions and charData.professions[profName] then
                local profData = charData.professions[profName]
                if profData.knownRecipes then
                    local known = 0
                    for _, spellID in ipairs(data.recipes) do
                        if profData.knownRecipes[spellID] then
                            known = known + 1
                        end
                    end
                    if bestKnown == nil or known > bestKnown then
                        bestKnown = known
                        bestChar = charKey
                    end
                end
            end
        end
    end

    return bestKnown, total, bestChar
end

-- Get total recipe count (no knowledge check)
local function GetTotalRecipeCount(profName, expDisplayName)
    local cache = BuildRecipeCountCache()
    if not cache[profName] or not cache[profName][expDisplayName] then
        return 0
    end
    return cache[profName][expDisplayName].total
end

-- Helper: Look up skill data from SavedVariables by display name
-- Handles legacy data where API names (Outland, Northrend, etc.) were stored
local function GetSkillDataByDisplayName(skillLevels, displayName)
    if not skillLevels then return nil end

    -- Try display name first (new format)
    if skillLevels[displayName] then
        return skillLevels[displayName]
    end

    -- Fall back to checking all stored keys and normalizing them
    for storedName, data in pairs(skillLevels) do
        local normalizedStored = HDG_ExpansionData.NormalizeApiName(storedName)
        if normalizedStored == displayName then
            return data
        end
    end

    return nil
end

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

function HDG_AltsTab.Init(frame)
    mainFrame = frame
end

-- ============================================================================
-- CREATE PANEL
-- ============================================================================

function HDG_AltsTab.Create()
    if not mainFrame then return end

    -- Use factory to create panel with header and scroll
    local layout = HDG.UI:TabPanel(mainFrame, {
        title = "Your Characters",
        hasHeaderBar = true,
        hasScroll = true,
    })

    local charactersFrame = layout.frame
    charactersFrame:Hide()
    mainFrame.charactersFrame = charactersFrame
    mainFrame.charactersContent = layout.scrollChild

    -- View mode toggle buttons (Skills | Recipes) - in header bar
    local btnWidth = 60
    local btnHeight = 18
    local scheme = GetScheme()

    local recipesBtn = CreateFrame("Button", nil, layout.headerBg, "BackdropTemplate")
    recipesBtn:SetPoint("RIGHT", -8, 0)
    recipesBtn:SetSize(btnWidth, btnHeight)
    recipesBtn:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    recipesBtn.text = recipesBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    recipesBtn.text:SetPoint("CENTER", 0, 0)
    ApplyFont(recipesBtn.text, scheme, "small")
    recipesBtn.text:SetText("Recipes")
    RegisterWidget(recipesBtn, "ToggleButton")

    local skillsBtn = CreateFrame("Button", nil, layout.headerBg, "BackdropTemplate")
    skillsBtn:SetPoint("RIGHT", recipesBtn, "LEFT", -2, 0)
    skillsBtn:SetSize(btnWidth, btnHeight)
    skillsBtn:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    skillsBtn.text = skillsBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    skillsBtn.text:SetPoint("CENTER", 0, 0)
    ApplyFont(skillsBtn.text, scheme, "small")
    skillsBtn.text:SetText("Skills")
    RegisterWidget(skillsBtn, "ToggleButton")

    local function UpdateToggleAppearance()
        local viewMode = HDG_Settings.altsViewMode or "skills"
        local tScheme = GetScheme()
        if viewMode == "skills" then
            if tScheme then
                skillsBtn:SetBackdropColor(tScheme.accent.r, tScheme.accent.g, tScheme.accent.b, 1)
                skillsBtn:SetBackdropBorderColor(tScheme.accent.r, tScheme.accent.g, tScheme.accent.b, 1)
                recipesBtn:SetBackdropColor(tScheme.panel.r, tScheme.panel.g, tScheme.panel.b, 0.8)
                recipesBtn:SetBackdropBorderColor(tScheme.border.r, tScheme.border.g, tScheme.border.b, 1)
                skillsBtn.text:SetTextColor(tScheme.text.r, tScheme.text.g, tScheme.text.b)
                recipesBtn.text:SetTextColor(tScheme.text_dim.r, tScheme.text_dim.g, tScheme.text_dim.b)
            end
        else
            if tScheme then
                recipesBtn:SetBackdropColor(tScheme.accent.r, tScheme.accent.g, tScheme.accent.b, 1)
                recipesBtn:SetBackdropBorderColor(tScheme.accent.r, tScheme.accent.g, tScheme.accent.b, 1)
                skillsBtn:SetBackdropColor(tScheme.panel.r, tScheme.panel.g, tScheme.panel.b, 0.8)
                skillsBtn:SetBackdropBorderColor(tScheme.border.r, tScheme.border.g, tScheme.border.b, 1)
                recipesBtn.text:SetTextColor(tScheme.text.r, tScheme.text.g, tScheme.text.b)
                skillsBtn.text:SetTextColor(tScheme.text_dim.r, tScheme.text_dim.g, tScheme.text_dim.b)
            end
        end
    end

    skillsBtn:SetScript("OnClick", function()
        HDG_Settings.altsViewMode = "skills"
        UpdateToggleAppearance()
        HDG_AltsTab.Refresh()
    end)

    recipesBtn:SetScript("OnClick", function()
        HDG_Settings.altsViewMode = "recipes"
        UpdateToggleAppearance()
        HDG_AltsTab.Refresh()
    end)

    UpdateToggleAppearance()
    mainFrame.altsSkillsBtn = skillsBtn
    mainFrame.altsRecipesBtn = recipesBtn

    return charactersFrame
end

-- ============================================================================
-- REFRESH PANEL
-- ============================================================================

function HDG_AltsTab.Refresh()
    if not mainFrame or not mainFrame.charactersContent then return end

    local content = mainFrame.charactersContent

    profRowPool:ReleaseAll(function(row)
        row.scanLabel:Hide()
        for i = 1, EXPANSION_SLOTS do
            row.skillBtns[i]:SetScript("OnEnter", nil)
            row.skillBtns[i]:SetScript("OnLeave", nil)
        end
    end)
    headerRowPool:ReleaseAll()
    charHeaderPool:ReleaseAll()
    dividerPool:ReleaseAll()
    fontStringPool:ReleaseAll()

    local yOffset = 0
    local EXPANSION_COL_WIDTH = 60
    local PROF_COL_WIDTH = 100

    -- Get expansion order and abbreviations from HDG_Data
    local expansionOrder = HDG_Data.EXPANSION_ORDER or {
        "Classic", "The Burning Crusade", "Wrath of the Lich King", "Cataclysm",
        "Mists of Pandaria", "Warlords of Draenor", "Legion", "Battle for Azeroth",
        "Shadowlands", "Dragonflight", "The War Within"
    }
    local expansionAbbr = HDG_Data.EXPANSION_ABBR or {}
    local numExpansions = #expansionOrder

    -- Crafting professions first, then gathering at the bottom
    local professionOrder = { "Alchemy", "Blacksmithing", "Cooking", "Enchanting", "Engineering",
        "Inscription", "Jewelcrafting", "Leatherworking", "Tailoring", "Herbalism", "Mining", "Skinning" }
    local gatheringProfessions = { ["Herbalism"] = true, ["Mining"] = true, ["Skinning"] = true }
    local firstGatheringProf = "Herbalism"

    -- Get all characters from saved data
    local characters = HDG_Settings.characters or {}
    local currentChar = HDG_Data.GetCharacterKey()
    local charList = {}

    -- Helper: check if character has any professions with scanned skill data
    local function hasProfessions(charKey)
        local charData = characters[charKey]
        if not charData or not charData.professions then return false end
        for _, profData in pairs(charData.professions) do
            if profData.skillLevels and next(profData.skillLevels) then
                return true
            end
        end
        return false
    end

    -- Sort characters: current first, then those with professions, then alphabetically
    for charKey, _ in pairs(characters) do
        table.insert(charList, charKey)
    end
    table.sort(charList, function(a, b)
        if a == currentChar then return true end
        if b == currentChar then return false end
        local aHasProf = hasProfessions(a)
        local bHasProf = hasProfessions(b)
        if aHasProf ~= bHasProf then
            return aHasProf
        end
        return a < b
    end)

    -- If no characters yet, show message
    if #charList == 0 then
        local noDataScheme = GetScheme()
        local noDataText = fontStringPool:Acquire(content)
        noDataText:SetPoint("TOPLEFT", 8, yOffset)
        noDataText:SetText(HDG.Theme.C_Dim() .. (L["STATUS_NO_CHARACTER_DATA"] or "No character data yet. Open a profession window to scan skill levels.") .. "|r")
        ApplyFont(noDataText, noDataScheme, "body")
        content:SetHeight(40)
        return
    end

    -- ========================================================================
    -- ACCOUNT SUMMARY GRID
    -- ========================================================================

    -- Build account-wide best skills data
    local accountBest = {}
    for _, profName in ipairs(professionOrder) do
        accountBest[profName] = {}
    end

    for charKey, charData in pairs(characters) do
        if charData.professions then
            for profName, profData in pairs(charData.professions) do
                if profData.skillLevels then
                    for expName, skillData in pairs(profData.skillLevels) do
                        local normalizedExpName = HDG_ExpansionData.NormalizeApiName(expName)
                        local current = skillData.current or 0
                        local max = skillData.max or 0
                        if not accountBest[profName] then
                            accountBest[profName] = {}
                        end
                        local existing = accountBest[profName][normalizedExpName]
                        if not existing then
                            accountBest[profName][normalizedExpName] = {
                                current = current,
                                max = max,
                                chars = { { name = charKey, current = current, max = max } }
                            }
                        else
                            table.insert(existing.chars, { name = charKey, current = current, max = max })
                            if current > existing.current then
                                existing.current = current
                                existing.max = max
                            end
                        end
                    end
                end
            end
        end
    end

    local viewMode = HDG_Settings.altsViewMode or "skills"

    -- Summary header
    local summaryScheme = GetScheme()
    local summaryHeader = fontStringPool:Acquire(content)
    summaryHeader:SetPoint("TOPLEFT", 8, yOffset)
    ApplyFont(summaryHeader, summaryScheme, "body")
    if viewMode == "skills" then
        summaryHeader:SetText(HDG.Theme.C_Warning() .. "Account Summary|r " .. HDG.Theme.C_Dim() .. "(best skill per profession)|r")
    else
        summaryHeader:SetText(HDG.Theme.C_Warning() .. "Account Summary|r " .. HDG.Theme.C_Dim() .. "(known decor recipes)|r")
    end
    yOffset = yOffset - 20

    -- Expansion header row for summary
    local expHeader = headerRowPool:Acquire(content)
    expHeader:SetPoint("TOPLEFT", 0, yOffset)
    expHeader:SetSize(CONFIG.MAIN_FRAME_WIDTH - 60, 18)
    local expScheme = GetScheme()
    if expScheme then
        expHeader:SetBackdropColor(expScheme.panel.r, expScheme.panel.g, expScheme.panel.b, 0.9)
    else
        expHeader:SetBackdropColor(0.1, 0.15, 0.15, 0.9)
    end

    expHeader.label:SetWidth(PROF_COL_WIDTH)
    ApplyFont(expHeader.label, expScheme, "small")
    expHeader.label:SetText(HDG.Theme.C_Text() .. "Profession|r")

    local xOffset = PROF_COL_WIDTH + 8
    for i, expName in ipairs(expansionOrder) do
        local expLabel = expHeader.expLabels[i]
        expLabel:ClearAllPoints()
        expLabel:SetPoint("LEFT", xOffset, 0)
        expLabel:SetWidth(EXPANSION_COL_WIDTH)
        expLabel:SetText(HDG.Theme.C_Text() .. (expansionAbbr[expName] or expName) .. "|r")
        ApplyFont(expLabel, expScheme, "small")
        expLabel:Show()
        xOffset = xOffset + EXPANSION_COL_WIDTH
    end
    for i = numExpansions + 1, EXPANSION_SLOTS do
        expHeader.expLabels[i]:Hide()
    end
    yOffset = yOffset - 18

    -- Summary profession rows
    for _, profName in ipairs(professionOrder) do
        local localizedProfName = HDG_ExpansionData.GetLocalizedProfessionName(profName)

        -- Add small divider line before gathering professions
        if profName == firstGatheringProf then
            local gatheringDivider = dividerPool:Acquire(content)
            gatheringDivider:SetPoint("TOPLEFT", 8, yOffset - 2)
            gatheringDivider:SetSize(CONFIG.MAIN_FRAME_WIDTH - 76, 1)
            local gdScheme = GetScheme()
            if gdScheme then
                gatheringDivider:SetColorTexture(gdScheme.border.r, gdScheme.border.g, gdScheme.border.b, 0.8)
            else
                gatheringDivider:SetColorTexture(0.25, 0.35, 0.35, 0.8)
            end
            yOffset = yOffset - 6
        end

        local profRow = profRowPool:Acquire(content)
        profRow:SetPoint("TOPLEFT", 0, yOffset)
        profRow:SetSize(CONFIG.MAIN_FRAME_WIDTH - 60, 18)
        local profScheme = GetScheme()
        if profScheme then
            profRow:SetBackdropColor(profScheme.bg.r, profScheme.bg.g, profScheme.bg.b, 0.4)
        else
            profRow:SetBackdropColor(0.05, 0.05, 0.05, 0.4)
        end

        profRow.profLabel:SetWidth(PROF_COL_WIDTH)
        ApplyFont(profRow.profLabel, profScheme, "small")

        local profColor
        if viewMode == "recipes" then
            local totalKnown = 0
            local totalRecipes = 0
            local hasAnyRecipeData = false
            for _, expNameCheck in ipairs(expansionOrder) do
                local known, total = GetBestKnownRecipeCount(profName, expNameCheck)
                totalRecipes = totalRecipes + total
                if known then
                    hasAnyRecipeData = true
                    totalKnown = totalKnown + known
                end
            end

            if totalRecipes == 0 then
                profColor = HDG.Theme.C_Text()
            elseif not hasAnyRecipeData then
                profColor = HDG.Theme.C_Text()
            elseif totalKnown == 0 then
                profColor = HDG.Theme.C_Dim()
            elseif totalKnown >= totalRecipes then
                profColor = HDG.Theme.C_Accent()
            else
                profColor = HDG.Theme.C_Warning()
            end
        else
            local anyBelowThreshold = false
            local hasAnyData = false
            for _, expNameCheck in ipairs(expansionOrder) do
                local bestData = accountBest[profName] and accountBest[profName][expNameCheck]
                if bestData then
                    hasAnyData = true
                    local current = bestData.current or 0
                    local decorThreshold = HDG_GetDecorThreshold and HDG_GetDecorThreshold(profName, expNameCheck) or 0
                    if decorThreshold > 0 and current < decorThreshold then
                        anyBelowThreshold = true
                        break
                    end
                end
            end

            if not hasAnyData then
                profColor = HDG.Theme.C_Text()
            elseif anyBelowThreshold then
                profColor = HDG.Theme.C_Warning()
            else
                profColor = HDG.Theme.C_Accent()
            end
        end
        profRow.profLabel:SetText(profColor .. localizedProfName .. "|r")

        xOffset = PROF_COL_WIDTH + 8
        for i, expName in ipairs(expansionOrder) do
            local bestData = accountBest[profName] and accountBest[profName][expName]
            local skillBtn = profRow.skillBtns[i]
            local skillLabel = profRow.skillLabels[i]

            skillBtn:ClearAllPoints()
            skillBtn:SetPoint("LEFT", xOffset, 0)
            skillBtn:SetSize(EXPANSION_COL_WIDTH, 18)
            skillBtn:Show()

            skillLabel:ClearAllPoints()
            skillLabel:SetPoint("CENTER", 0, 0)
            skillLabel:SetWidth(EXPANSION_COL_WIDTH)
            ApplyFont(skillLabel, profScheme, "small")

            if viewMode == "recipes" then
                local known, total, bestChar = GetBestKnownRecipeCount(profName, expName)

                local color
                if total == 0 then
                    color = HDG.Theme.C_Dim()
                    skillLabel:SetText(color .. "-|r")
                elseif known == nil then
                    skillLabel:SetText(HDG.Theme.C_Dim() .. string.format("?/%d|r", total))
                elseif known == 0 then
                    color = HDG.Theme.C_Dim()
                    skillLabel:SetText(string.format("%s0/%d|r", color, total))
                elseif known >= total then
                    color = HDG.Theme.C_Accent()
                    skillLabel:SetText(string.format("%s%d/%d|r", color, known, total))
                else
                    color = HDG.Theme.C_Warning()
                    skillLabel:SetText(string.format("%s%d/%d|r", color, known, total))
                end

                skillBtn:SetScript("OnEnter", function(self)
                    GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
                    GameTooltip:AddLine(localizedProfName .. " - " .. (expansionAbbr[expName] or expName), 1, 0.82, 0.3)
                    if total > 0 then
                        if known == nil then
                            GameTooltip:AddLine(HDG.Theme.C_Dim() .. "Open profession window to scan recipes|r")
                        else
                            GameTooltip:AddLine(string.format("Best: %d / %d decor recipes", known, total))
                            if bestChar then
                                local charColor = (bestChar == currentChar) and HDG.Theme.C_Warning() or HDG.Theme.C_Text()
                                GameTooltip:AddLine(charColor .. bestChar .. "|r")
                            end
                            if known >= total then
                                GameTooltip:AddLine(HDG.Theme.C_Accent() .. "All recipes learned!|r")
                            elseif known > 0 then
                                GameTooltip:AddLine(HDG.Theme.C_Warning() .. string.format("%d recipes remaining|r", total - known))
                            end
                        end
                    else
                        GameTooltip:AddLine(HDG.Theme.C_Dim() .. "No decor recipes for this expansion|r")
                    end
                    GameTooltip:Show()
                end)
                skillBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            elseif bestData then
                local current = bestData.current or 0
                local max = bestData.max or 0
                local decorThreshold = HDG_GetDecorThreshold and HDG_GetDecorThreshold(profName, expName) or nil
                decorThreshold = decorThreshold or 0

                local color
                if decorThreshold == 0 then
                    color = HDG.Theme.C_Dim()
                elseif current >= decorThreshold then
                    color = HDG.Theme.C_Accent()
                else
                    color = HDG.Theme.C_Warning()
                end
                skillLabel:SetText(string.format("%s%d|r", color, current))

                skillBtn:SetScript("OnEnter", function(self)
                    GameTooltip:SetOwner(self, HDG_UIHelpers.TOOLTIP_ANCHOR)
                    GameTooltip:AddLine(localizedProfName .. " - " .. (expansionAbbr[expName] or expName), 1, 0.82, 0.3)
                    if decorThreshold and decorThreshold > 0 then
                        local thresholdMet = current >= decorThreshold
                        local statusColor = thresholdMet and HDG.Theme.C_Accent() or HDG.Theme.C_Warning()
                        local statusText = thresholdMet and "Can learn decor recipes" or "Below decor threshold"
                        GameTooltip:AddLine(statusColor .. statusText .. "|r")
                        GameTooltip:AddLine(HDG.Theme.C_Dim() .. string.format("Decor recipes require %d+ skill|r", decorThreshold))
                    else
                        GameTooltip:AddLine(HDG.Theme.C_Dim() .. "No decor recipes for this expansion|r")
                    end
                    GameTooltip:AddLine(" ")
                    table.sort(bestData.chars, function(a, b) return a.current > b.current end)
                    for _, charInfo in ipairs(bestData.chars) do
                        local charColor = (charInfo.name == currentChar) and HDG.Theme.C_Warning() or HDG.Theme.C_Text()
                        local skillColor = HDG.Theme.C_Dim()
                        GameTooltip:AddDoubleLine(
                            charColor .. charInfo.name .. "|r",
                            skillColor .. charInfo.current .. "/" .. charInfo.max .. "|r",
                            1, 1, 1, 1, 1, 1
                        )
                    end
                    GameTooltip:Show()
                end)
                skillBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            else
                skillLabel:SetText(HDG.Theme.C_Dim() .. "-|r")
            end

            xOffset = xOffset + EXPANSION_COL_WIDTH
        end
        -- Hide unused expansion slots
        for i = numExpansions + 1, EXPANSION_SLOTS do
            profRow.skillBtns[i]:Hide()
        end

        yOffset = yOffset - 18
    end

    -- Color legend
    yOffset = yOffset - 12
    local legendScheme = GetScheme()
    local legendText = fontStringPool:Acquire(content)
    legendText:SetPoint("TOPLEFT", 8, yOffset)
    ApplyFont(legendText, legendScheme, "small")
    if viewMode == "recipes" then
        legendText:SetText(HDG.Theme.C_Accent() .. "All learned|r   " .. HDG.Theme.C_Warning() .. "Partial|r   " .. HDG.Theme.C_Dim() .. "None / No recipes|r")
    else
        legendText:SetText(HDG.Theme.C_Accent() .. "Can learn decor|r   " .. HDG.Theme.C_Warning() .. "Below threshold|r   " .. HDG.Theme.C_Dim() .. "No decor recipes|r")
    end
    yOffset = yOffset - 16

    -- Divider
    yOffset = yOffset - 8
    local divider = dividerPool:Acquire(content)
    divider:SetPoint("TOPLEFT", 8, yOffset)
    divider:SetSize(CONFIG.MAIN_FRAME_WIDTH - 76, 1)
    local divScheme = GetScheme()
    if divScheme then
        divider:SetColorTexture(divScheme.border.r, divScheme.border.g, divScheme.border.b, 0.5)
    else
        divider:SetColorTexture(0.3, 0.3, 0.3, 0.5)
    end
    yOffset = yOffset - 10

    -- ========================================================================
    -- CHARACTER DETAILS (per-character breakdown)
    -- ========================================================================

    local detailsScheme = GetScheme()
    local detailsHeader = fontStringPool:Acquire(content)
    detailsHeader:SetPoint("TOPLEFT", 8, yOffset)
    ApplyFont(detailsHeader, detailsScheme, "body")
    detailsHeader:SetText(HDG.Theme.C_Warning() .. "Character Details|r")
    yOffset = yOffset - 24

    -- Split characters into those with and without professions
    local charsWithProfs = {}
    local charsWithoutProfs = {}
    for _, charKey in ipairs(charList) do
        if hasProfessions(charKey) then
            table.insert(charsWithProfs, charKey)
        else
            table.insert(charsWithoutProfs, charKey)
        end
    end

    -- Process characters with professions
    for _, charKey in ipairs(charsWithProfs) do
        local charData = characters[charKey]
        local isCurrentChar = (charKey == currentChar)

        -- Character header with name
        local charHeader = charHeaderPool:Acquire(content)
        charHeader:SetPoint("TOPLEFT", 0, yOffset)
        charHeader:SetSize(CONFIG.MAIN_FRAME_WIDTH - 60, 22)
        local chScheme = GetScheme()
        if chScheme then
            charHeader:SetBackdropColor(chScheme.panel.r, chScheme.panel.g, chScheme.panel.b, 0.9)
        else
            charHeader:SetBackdropColor(0.1, 0.15, 0.15, 0.9)
        end

        ApplyFont(charHeader.label, chScheme, "body")
        local charColor = isCurrentChar and HDG.Theme.C_Warning() or HDG.Theme.C_Text()
        local currentIndicator = isCurrentChar and " (current)" or ""
        charHeader.label:SetText(charColor .. charKey .. currentIndicator .. "|r")

        -- Find Lumber indicator (use spell icon for Find Lumber)
        local lumberTex = (C_Spell and C_Spell.GetSpellTexture and C_Spell.GetSpellTexture(1256697)) or "Interface\\Icons\\INV_Tradeskillitem_03"
        charHeader.lumberIcon:SetTexture(lumberTex)
        ApplyFont(charHeader.lumberText, chScheme, "small")
        if charData.knowsFindLumber == true then
            charHeader.lumberIcon:SetDesaturated(false)
            charHeader.lumberIcon:SetAlpha(1.0)
            charHeader.lumberText:SetText(HDG.Theme.C_Success() .. "Find Lumber known|r")
        elseif charData.knowsFindLumber == false then
            charHeader.lumberIcon:SetDesaturated(true)
            charHeader.lumberIcon:SetAlpha(0.4)
            charHeader.lumberText:SetText(HDG.Theme.C_Error() .. "Find Lumber not learned|r")
        else
            charHeader.lumberIcon:SetDesaturated(true)
            charHeader.lumberIcon:SetAlpha(0.3)
            charHeader.lumberText:SetText(HDG.Theme.C_Dim() .. "Log in to check|r")
        end

        yOffset = yOffset - 24

        -- Expansion header row (abbreviations)
        local charExpHeader = headerRowPool:Acquire(content)
        charExpHeader:SetPoint("TOPLEFT", 0, yOffset)
        charExpHeader:SetSize(CONFIG.MAIN_FRAME_WIDTH - 60, 18)
        local cehScheme = GetScheme()
        if cehScheme then
            charExpHeader:SetBackdropColor(cehScheme.panel.r, cehScheme.panel.g, cehScheme.panel.b, 0.9)
        else
            charExpHeader:SetBackdropColor(0.1, 0.15, 0.15, 0.9)
        end

        charExpHeader.label:SetWidth(PROF_COL_WIDTH)
        ApplyFont(charExpHeader.label, cehScheme, "small")
        charExpHeader.label:SetText(HDG.Theme.C_Text() .. "Profession|r")

        xOffset = PROF_COL_WIDTH + 8
        for i, expName in ipairs(expansionOrder) do
            local expLabel = charExpHeader.expLabels[i]
            expLabel:ClearAllPoints()
            expLabel:SetPoint("LEFT", xOffset, 0)
            expLabel:SetWidth(EXPANSION_COL_WIDTH)
            expLabel:SetText(HDG.Theme.C_Text() .. (expansionAbbr[expName] or expName) .. "|r")
            ApplyFont(expLabel, cehScheme, "small")
            expLabel:Show()
            xOffset = xOffset + EXPANSION_COL_WIDTH
        end
        for i = numExpansions + 1, EXPANSION_SLOTS do
            charExpHeader.expLabels[i]:Hide()
        end

        yOffset = yOffset - 18

        -- Profession skill level rows for this character
        local charHasProfessions = false
        local gatheringDividerShown = false
        for _, profName in ipairs(professionOrder) do
            local localizedProfNameChar = HDG_ExpansionData.GetLocalizedProfessionName(profName)
            local profData = charData.professions and charData.professions[profName]
            if profData then
                -- Add small divider line before gathering professions (only once per character)
                if not gatheringDividerShown and gatheringProfessions[profName] then
                    local gatheringDivider = dividerPool:Acquire(content)
                    gatheringDivider:SetPoint("TOPLEFT", 8, yOffset - 2)
                    gatheringDivider:SetSize(CONFIG.MAIN_FRAME_WIDTH - 76, 1)
                    local gd2Scheme = GetScheme()
                    if gd2Scheme then
                        gatheringDivider:SetColorTexture(gd2Scheme.border.r, gd2Scheme.border.g, gd2Scheme.border.b, 0.8)
                    else
                        gatheringDivider:SetColorTexture(0.25, 0.35, 0.35, 0.8)
                    end
                    yOffset = yOffset - 6
                    gatheringDividerShown = true
                end
                charHasProfessions = true
                local hasSkillData = profData.skillLevels and next(profData.skillLevels)

                -- Check if any expansion is below decor threshold for this character's profession
                local anyBelowThreshold = false
                if hasSkillData then
                    for _, expName in ipairs(expansionOrder) do
                        local skillData = GetSkillDataByDisplayName(profData.skillLevels, expName)
                        if skillData then
                            local current = skillData.current or 0
                            local decorThreshold = HDG_GetDecorThreshold and HDG_GetDecorThreshold(profName, expName) or 0
                            if decorThreshold > 0 and current < decorThreshold then
                                anyBelowThreshold = true
                                break
                            end
                        end
                    end
                end

                -- Profession row (pooled)
                local profRow = profRowPool:Acquire(content)
                profRow:SetPoint("TOPLEFT", 0, yOffset)
                profRow:SetSize(CONFIG.MAIN_FRAME_WIDTH - 60, 18)
                local prScheme = GetScheme()
                if prScheme then
                    profRow:SetBackdropColor(prScheme.bg.r, prScheme.bg.g, prScheme.bg.b, 0.4)
                else
                    profRow:SetBackdropColor(0.05, 0.05, 0.05, 0.4)
                end

                profRow.profLabel:SetWidth(PROF_COL_WIDTH)
                ApplyFont(profRow.profLabel, prScheme, "small")
                local profColor
                if not hasSkillData then
                    profColor = HDG.Theme.C_Text()
                elseif anyBelowThreshold then
                    profColor = HDG.Theme.C_Warning()
                else
                    profColor = HDG.Theme.C_Accent()
                end
                profRow.profLabel:SetText(profColor .. localizedProfNameChar .. "|r")

                -- Expansion skill columns
                xOffset = PROF_COL_WIDTH + 8

                if viewMode == "recipes" then
                    for i, expName in ipairs(expansionOrder) do
                        local skillLabel = profRow.skillLabels[i]
                        local skillBtn = profRow.skillBtns[i]

                        skillBtn:ClearAllPoints()
                        skillBtn:SetPoint("LEFT", xOffset, 0)
                        skillBtn:SetSize(EXPANSION_COL_WIDTH, 18)
                        skillBtn:Show()

                        skillLabel:ClearAllPoints()
                        skillLabel:SetPoint("CENTER", 0, 0)
                        ApplyFont(skillLabel, prScheme, "small")
                        skillLabel:SetWidth(EXPANSION_COL_WIDTH)

                        local known, total = GetKnownRecipeCountForChar(charKey, profName, expName)
                        if total == 0 then
                            skillLabel:SetText(HDG.Theme.C_Dim() .. "-|r")
                        elseif known == nil then
                            skillLabel:SetText(HDG.Theme.C_Dim() .. string.format("?/%d|r", total))
                        else
                            local color
                            if known == 0 then
                                color = HDG.Theme.C_Dim()
                            elseif known >= total then
                                color = HDG.Theme.C_Accent()
                            else
                                color = HDG.Theme.C_Warning()
                            end
                            skillLabel:SetText(string.format("%s%d/%d|r", color, known, total))
                        end

                        xOffset = xOffset + EXPANSION_COL_WIDTH
                    end
                elseif hasSkillData then
                    for i, expName in ipairs(expansionOrder) do
                        local skillData = GetSkillDataByDisplayName(profData.skillLevels, expName)
                        local skillLabel = profRow.skillLabels[i]
                        local skillBtn = profRow.skillBtns[i]

                        skillBtn:ClearAllPoints()
                        skillBtn:SetPoint("LEFT", xOffset, 0)
                        skillBtn:SetSize(EXPANSION_COL_WIDTH, 18)
                        skillBtn:Show()

                        skillLabel:ClearAllPoints()
                        skillLabel:SetPoint("CENTER", 0, 0)
                        skillLabel:SetWidth(EXPANSION_COL_WIDTH)
                        ApplyFont(skillLabel, prScheme, "small")

                        if skillData then
                            local current = skillData.current or 0
                            local max = skillData.max or 0
                            local decorThreshold = HDG_GetDecorThreshold and HDG_GetDecorThreshold(profName, expName) or 0

                            local color
                            if decorThreshold == 0 then
                                color = HDG.Theme.C_Dim()
                            elseif current >= decorThreshold then
                                color = HDG.Theme.C_Accent()
                            else
                                color = HDG.Theme.C_Warning()
                            end
                            skillLabel:SetText(string.format("%s%d/%d|r", color, current, max))
                        else
                            skillLabel:SetText(HDG.Theme.C_Dim() .. "-|r")
                        end

                        xOffset = xOffset + EXPANSION_COL_WIDTH
                    end
                else
                    -- No skill data yet - hide all expansion buttons, show scan message
                    for i = 1, numExpansions do
                        profRow.skillBtns[i]:Hide()
                    end
                    profRow.scanLabel:ClearAllPoints()
                    profRow.scanLabel:SetPoint("LEFT", xOffset, 0)
                    profRow.scanLabel:SetText(HDG.Theme.C_Dim() .. "(Open profession window to scan skill levels)|r")
                    ApplyFont(profRow.scanLabel, prScheme, "small")
                    profRow.scanLabel:Show()
                end

                -- Hide unused expansion slots
                for i = numExpansions + 1, EXPANSION_SLOTS do
                    profRow.skillBtns[i]:Hide()
                end

                yOffset = yOffset - 18
            end
        end

        yOffset = yOffset - 16  -- Gap between characters
    end

    -- Collapsible "No Professions" section for characters without any professions
    if #charsWithoutProfs > 0 then
        local secScheme = GetScheme()
        local sectionHeader = charHeaderPool:Acquire(content)
        sectionHeader:SetPoint("TOPLEFT", 0, yOffset)
        sectionHeader:SetSize(CONFIG.MAIN_FRAME_WIDTH - 60, 22)
        if secScheme then
            sectionHeader:SetBackdropColor(secScheme.panel.r, secScheme.panel.g, secScheme.panel.b, 0.6)
        else
            sectionHeader:SetBackdropColor(0.1, 0.15, 0.15, 0.6)
        end

        local indicator = noProfCollapsed and "+ " or "- "
        ApplyFont(sectionHeader.label, secScheme, "body")
        sectionHeader.label:SetText(HDG.Theme.C_Dim() .. indicator .. "No Professions / Not Scanned (" .. #charsWithoutProfs .. ")|r")
        -- Hide lumber elements on this header
        sectionHeader.lumberIcon:SetAlpha(0)
        sectionHeader.lumberText:SetText("")

        sectionHeader:EnableMouse(true)
        sectionHeader:SetScript("OnMouseUp", function()
            noProfCollapsed = not noProfCollapsed
            HDG_AltsTab.Refresh()
        end)

        yOffset = yOffset - 24

        if not noProfCollapsed then
            for _, charKey in ipairs(charsWithoutProfs) do
                local isCurrentChar = (charKey == currentChar)
                local nameText = fontStringPool:Acquire(content)
                nameText:SetPoint("TOPLEFT", 16, yOffset)
                ApplyFont(nameText, secScheme, "small")
                local nameColor = isCurrentChar and HDG.Theme.C_Warning() or HDG.Theme.C_Dim()
                local indicator2 = isCurrentChar and " (current)" or ""
                nameText:SetText(nameColor .. charKey .. indicator2 .. "|r")
                yOffset = yOffset - 18
            end
        end

        yOffset = yOffset - 8
    end

    -- Info text at bottom
    yOffset = yOffset - 10
    local infoScheme = GetScheme()
    local infoText = fontStringPool:Acquire(content)
    infoText:SetPoint("TOPLEFT", 8, yOffset)
    ApplyFont(infoText, infoScheme, "small")
    infoText:SetText(HDG.Theme.C_Dim() .. "Open a profession window on each character to scan their skill levels.|r")

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

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

function HDG_AltsTab.Show()
    if mainFrame and mainFrame.charactersFrame then
        mainFrame.charactersFrame:Show()
    end
    HDG_AltsTab.Refresh()
end

function HDG_AltsTab.Hide()
    if mainFrame and mainFrame.charactersFrame then
        mainFrame.charactersFrame:Hide()
    end
end

-- ============================================================================
-- EVENTBUS SUBSCRIPTIONS
-- Decouple UI refresh from direct function calls
-- ============================================================================

local function RegisterEventBusHandlers()
    local HDG = _G.HousingDecorGuide
    if not HDG or not HDG.EventBus then return end

    HDG.EventBus:Register("HDG_STATE_CHANGED", function(payload)
        if not payload or not payload.action then return end

        local action = payload.action

        -- Character data changes
        if action == "UPDATE_CHARACTER" then
            HDG_AltsTab.Refresh()
        end
    end)
end

-- Register handlers after a short delay to ensure EventBus is ready
C_Timer.After(0.1, RegisterEventBusHandlers)

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

_G.HDG_AltsTab = HDG_AltsTab

return HDG_AltsTab
