local _, ns = ...
local L = ns.L or {}

local RenownView = {}
ns.UIRenownView = RenownView

local UIData = ns.UIData or {}
local PREMIUM = UIData.PREMIUM or {}
local THEME = UIData.THEME or {}

local GOLD = THEME.gold or { 0.92, 0.76, 0.24 }
local GOLD_DIM = THEME.goldDim or { 0.40, 0.31, 0.10 }
local PARCHMENT = THEME.parchment or { 0.93, 0.89, 0.77 }
local METAL = THEME.metalInset or { 0.12, 0.10, 0.085 }
local SHADOW = THEME.shadow or { 0.01, 0.01, 0.01 }

local HISTORY_ROWS = 12
local HISTORY_ROW_HEIGHT = 36
local RANK_ROWS = 28
local COSMETIC_MENU_ROWS = 25
local COSMETIC_MENU_ROW_HEIGHT = 25
local COSMETIC_MENU_BUTTON_WIDTH = 164
local COSMETIC_MENU_COLUMN_GAP = 10
local TOAST_STYLE_ORDER = { "normal", "clean", "simple" }
local TOAST_ANIMATION_ORDER = { "soft", "fade", "pop" }

local function callHelper(helpers, key)
    local value = helpers and helpers[key]
    if type(value) == "function" then
        return value
    end
    return function() end
end

local function tintTexture(texture, color, alpha)
    if not texture then
        return
    end
    texture:SetTexture("Interface\\Buttons\\WHITE8X8")
    texture:SetVertexColor(color[1], color[2], color[3], alpha == nil and 1 or alpha)
end

local function formatNumber(value)
    value = math.floor(tonumber(value) or 0)
    local formatted = tostring(value)
    while true do
        local nextText, replacements = formatted:gsub("^(-?%d+)(%d%d%d)", "%1,%2")
        formatted = nextText
        if replacements == 0 then
            break
        end
    end
    return formatted
end

local function cycleOptionValue(order, current)
    local nextIndex = 1
    for index, value in ipairs(order or {}) do
        if value == current then
            nextIndex = index + 1
            break
        end
    end
    if nextIndex > #(order or {}) then
        nextIndex = 1
    end
    return order[nextIndex] or current
end

local function getToastStyleLabel(style)
    if style == "clean" then
        return L.RENOWN_TOAST_STYLE_CLEAN or "Clean"
    end
    if style == "simple" then
        return L.RENOWN_TOAST_STYLE_SIMPLE or "Simple"
    end
    return L.RENOWN_TOAST_STYLE_NORMAL or "Normal"
end

local function getToastAnimationLabel(animation)
    if animation == "fade" then
        return L.RENOWN_TOAST_ANIMATION_FADE or "Fade"
    end
    if animation == "pop" then
        return L.RENOWN_TOAST_ANIMATION_POP or "Pop"
    end
    return L.RENOWN_TOAST_ANIMATION_SOFT or "Soft"
end

local function formatToastScaleLabel(scale)
    local percent = math.floor(((tonumber(scale) or 1) * 100) + 0.5)
    return string.format(L.RENOWN_TOAST_SCALE_FORMAT or "Size: %d%%", percent)
end

local function formatTimeAgo(timestamp)
    timestamp = tonumber(timestamp) or 0
    if timestamp <= 0 then
        return ""
    end

    local elapsed = math.max(0, (time and time() or timestamp) - timestamp)
    if elapsed < 60 then
        return L.TIME_JUST_NOW or "just now"
    end
    if elapsed < 3600 then
        return string.format(L.TIME_MIN or "%d m", math.floor(elapsed / 60))
    end
    if elapsed < 86400 then
        return string.format(L.TIME_HOUR_MIN or "%d h %d m", math.floor(elapsed / 3600), math.floor((elapsed % 3600) / 60))
    end
    return string.format(L.TIME_DAY_HOUR or "%d d %d h", math.floor(elapsed / 86400), math.floor((elapsed % 86400) / 3600))
end

local function getCharacterDisplay(character)
    if type(character) ~= "table" then
        return L.UNKNOWN or "Unknown"
    end

    local name = character.name or L.UNKNOWN or "Unknown"
    local realm = character.realm
    local display = realm and realm ~= "" and string.format("%s-%s", name, realm) or name
    if character.className and character.className ~= "" and tonumber(character.level) and tonumber(character.level) > 0 then
        return string.format("%s  |  %s %d", display, character.className, tonumber(character.level))
    end
    return display
end

local function getCharacterProgressMeta(character)
    if type(character) ~= "table" then
        return L.UNKNOWN or "Unknown"
    end

    local parts = {
        character.name or character.key or (L.UNKNOWN or "Unknown"),
        string.format(L.SIDEBAR_ROW_LEVEL or "Level %d", tonumber(character.level) or 0),
        character.className or (L.UNKNOWN or "Unknown"),
        character.realm or (L.UNKNOWN or "Unknown"),
    }
    local itemLevel = tonumber(character.itemLevel) or 0
    if itemLevel > 0 then
        parts[#parts + 1] = string.format(L.SIDEBAR_ROW_ILVL or "ilvl %d", itemLevel)
    end
    return table.concat(parts, "  |  ")
end

local function getShortCharacterName(character)
    if type(character) ~= "table" then
        return L.UNKNOWN or "Unknown"
    end
    return character.name or character.key or L.UNKNOWN or "Unknown"
end

local function getFrameCharacter(character, scope)
    if scope == "warband" and ns.GetCurrentCharacterInfo then
        local current = ns:GetCurrentCharacterInfo()
        if type(current) == "table" and type(current.key) == "string" and current.key ~= "" then
            return current
        end
    end
    return character
end

local function isRankFrameUnlocked(view, rank)
    if type(rank) ~= "table" or view.scope ~= "character" then
        return false
    end
    return (tonumber(view.level) or 1) >= (tonumber(rank.level) or 1)
end

local function getBadgeForRank(rank, scope)
    local index = tonumber(rank and rank.assetIndex)
    if scope == "character" and index and PREMIUM.renownBadges and PREMIUM.renownBadges[index] then
        return PREMIUM.renownBadges[index]
    end
    if scope == "warband" and index then
        if PREMIUM.renownWarbandBadges and PREMIUM.renownWarbandBadges[index] then
            return PREMIUM.renownWarbandBadges[index]
        end
        if PREMIUM.renownBadges and PREMIUM.renownBadges[index] then
            return PREMIUM.renownBadges[index]
        end
    end
    return PREMIUM.statusBadgeOpen or "Interface\\Buttons\\UI-CheckBox-Check"
end

local function getFrameForRank(rank)
    local index = tonumber(rank and rank.assetIndex)
    if index and PREMIUM.renownFrames and PREMIUM.renownFrames[index] then
        return PREMIUM.renownFrames[index]
    end
    return nil
end

local function createPanel(parent, title, helpers)
    local createLabel = callHelper(helpers, "createLabel")
    local createSectionFrame = callHelper(helpers, "createSectionFrame")

    local panel = createSectionFrame(parent, "cardInset")
    panel.title = createLabel(panel, "GameFontNormal", "LEFT")
    panel.title:SetPoint("TOPLEFT", 16, -14)
    panel.title:SetPoint("TOPRIGHT", -16, -14)
    panel.title:SetTextColor(GOLD[1], GOLD[2], GOLD[3], 1)
    panel.title:SetText(title or "")
    return panel
end

local function createOptionsSectionHeader(parent, text, helpers)
    local createLabel = callHelper(helpers, "createLabel")
    local label = createLabel(parent, "GameFontNormalSmall", "LEFT")
    label:SetHeight(16)
    label:SetTextColor(GOLD[1], GOLD[2], GOLD[3], 0.96)
    label:SetText(text or "")
    return label
end

local function createRosterButton(parent, helpers)
    local createLabel = callHelper(helpers, "createLabel")
    local createMiniActionButton = callHelper(helpers, "createMiniActionButton")
    local button = createMiniActionButton(parent, "", 222)
    button:SetHeight(30)
    button.meta = createLabel(button, "GameFontDisableSmall", "RIGHT")
    button.meta:SetPoint("RIGHT", -10, 0)
    button.meta:SetWidth(96)
    button.meta:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.72)
    if button.label then
        button.label:ClearAllPoints()
        button.label:SetPoint("LEFT", 12, 0)
        button.label:SetPoint("RIGHT", button.meta, "LEFT", -8, 0)
        button.label:SetJustifyH("LEFT")
    end
    return button
end

local function createRenownRosterRow(parent, helpers)
    local sidebarView = ns.UISidebarView or {}
    if sidebarView.CreateRosterRow then
        return sidebarView.CreateRosterRow(parent, helpers)
    end
    return createRosterButton(parent, helpers)
end

local function setRenownRosterActive(row, active)
    if not row then
        return
    end

    if row.selection then
        row.selection:SetShown(active == true)
    end
    if row.fill then
        local color = active and (THEME.selected or GOLD_DIM) or METAL
        row.fill:SetColorTexture(color[1], color[2], color[3], active and 0.08 or 0.03)
    end
    if row.SetBackdropBorderColor then
        if active then
            row:SetBackdropBorderColor(GOLD[1], GOLD[2], GOLD[3], 0.78)
        else
            row:SetBackdropBorderColor(GOLD_DIM[1], GOLD_DIM[2], GOLD_DIM[3], 0.36)
        end
    end
    if not row.selection and row.SetAlpha then
        row:SetAlpha(active and 1 or 0.82)
    end
end

local function setRosterRowText(row, title, meta)
    if row.name then
        row.name:SetText(title or "")
    elseif row.label then
        row.label:SetText(title or "")
    end
    if row.meta then
        row.meta:SetText(meta or "")
    end
end

local function ensureRenownRosterRows(card, count, helpers)
    if not card or not card.rosterChild then
        return
    end

    card.rosterRows = card.rosterRows or {}
    while #card.rosterRows < count do
        local row = createRenownRosterRow(card.rosterChild, helpers)
        local index = #card.rosterRows + 1
        row:SetPoint("TOPLEFT", 0, -((index - 1) * 64))
        row:SetPoint("TOPRIGHT", 0, -((index - 1) * 64))
        card.rosterRows[index] = row
    end
end

local function createRankRow(parent, helpers)
    local createLabel = callHelper(helpers, "createLabel")
    local row = CreateFrame("Button", nil, parent)
    row:SetHeight(28)
    row:EnableMouse(true)
    if row.RegisterForClicks then
        row:RegisterForClicks("LeftButtonUp")
    end

    row.bg = row:CreateTexture(nil, "BACKGROUND")
    row.bg:SetPoint("TOPLEFT", 0, -1)
    row.bg:SetPoint("BOTTOMRIGHT", -2, 1)
    tintTexture(row.bg, SHADOW, 0)

    row.marker = row:CreateTexture(nil, "ARTWORK")
    row.marker:SetSize(18, 18)
    row.marker:SetPoint("LEFT", 0, 0)
    row.marker:SetTexture(PREMIUM.statusBadgeOpen or "Interface\\Buttons\\UI-CheckBox-Check")

    row.label = createLabel(row, "GameFontHighlightSmall", "LEFT")
    row.label:SetPoint("LEFT", row.marker, "RIGHT", 9, 0)
    row.label:SetPoint("RIGHT", -72, 0)
    row.label:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.95)

    row.value = createLabel(row, "GameFontNormalSmall", "RIGHT")
    row.value:SetPoint("RIGHT", 0, 0)
    row.value:SetWidth(68)
    row.value:SetTextColor(GOLD[1], GOLD[2], GOLD[3], 1)

    return row
end

local function createHistoryRow(parent, helpers)
    local createLabel = callHelper(helpers, "createLabel")
    local row = CreateFrame("Frame", nil, parent)
    row:SetHeight(32)

    row.bg = row:CreateTexture(nil, "BACKGROUND")
    row.bg:SetPoint("TOPLEFT", 0, -1)
    row.bg:SetPoint("BOTTOMRIGHT", -4, 1)
    tintTexture(row.bg, SHADOW, 0.16)

    row.icon = row:CreateTexture(nil, "ARTWORK")
    row.icon:SetSize(18, 18)
    row.icon:SetPoint("LEFT", 8, 0)
    row.icon:SetTexture(PREMIUM.statusBadgeComplete or "Interface\\Buttons\\UI-CheckBox-Check")

    row.label = createLabel(row, "GameFontHighlightSmall", "LEFT")
    row.label:SetPoint("LEFT", row.icon, "RIGHT", 10, 0)
    row.label:SetPoint("RIGHT", -126, 0)
    row.label:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.96)
    if row.label.SetWordWrap then
        row.label:SetWordWrap(false)
    end
    if row.label.SetNonSpaceWrap then
        row.label:SetNonSpaceWrap(false)
    end

    row.amount = createLabel(row, "GameFontNormalSmall", "RIGHT")
    row.amount:SetPoint("RIGHT", -62, 0)
    row.amount:SetWidth(58)
    row.amount:SetTextColor(GOLD[1], GOLD[2], GOLD[3], 1)

    row.time = createLabel(row, "GameFontDisableSmall", "RIGHT")
    row.time:SetPoint("RIGHT", -8, 0)
    row.time:SetWidth(48)
    row.time:SetTextColor(0.66, 0.66, 0.64, 0.92)

    return row
end

local function showResetConfirmation(scope, character)
    local targetName = scope == "warband"
        and (L.RENOWN_SCOPE_WARBAND or "Warband")
        or getCharacterDisplay(character)

    if StaticPopupDialogs and StaticPopup_Show then
        StaticPopupDialogs.VAULTLOOM_RENOWN_RESET = StaticPopupDialogs.VAULTLOOM_RENOWN_RESET or {
            text = L.RENOWN_RESET_CONFIRM or "Reset Chronik progress for %s?",
            button1 = YES or "Yes",
            button2 = NO or "No",
            timeout = 0,
            whileDead = true,
            hideOnEscape = true,
            OnAccept = function(_, data)
                if ns.ResetRenownProgress and type(data) == "table" then
                    ns:ResetRenownProgress(data.scope, data.characterKey)
                end
            end,
        }
        StaticPopupDialogs.VAULTLOOM_RENOWN_RESET.text = L.RENOWN_RESET_CONFIRM or "Reset Chronik progress for %s?"
        StaticPopup_Show("VAULTLOOM_RENOWN_RESET", targetName, nil, {
            scope = scope,
            characterKey = character and character.key,
        })
        return
    end

    if ns.ResetRenownProgress then
        ns:ResetRenownProgress(scope, character and character.key)
    end
end

local function createProgressSummaryPanel(parent, helpers)
    local createLabel = callHelper(helpers, "createLabel")
    local createSectionFrame = callHelper(helpers, "createSectionFrame")
    local createProgressBar = callHelper(helpers, "createProgressBar")

    local panel = createSectionFrame(parent, "cardInset")
    panel.tint = panel:CreateTexture(nil, "BORDER")
    panel.tint:SetAllPoints()
    tintTexture(panel.tint, METAL, 0.16)

    panel.badge = panel:CreateTexture(nil, "ARTWORK")
    panel.badge:SetSize(92, 92)
    panel.badge:SetPoint("LEFT", 28, 8)
    panel.badge:SetTexture(PREMIUM.statusBadgeComplete or PREMIUM.statusBadgeOpen or "Interface\\Buttons\\UI-CheckBox-Check")

    panel.title = createLabel(panel, "GameFontNormalHuge", "LEFT")
    panel.title:SetPoint("TOPLEFT", panel.badge, "TOPRIGHT", 20, -2)
    panel.title:SetPoint("TOPRIGHT", -18, -20)
    panel.title:SetTextColor(GOLD[1], GOLD[2], GOLD[3], 1)

    panel.subtitle = createLabel(panel, "GameFontHighlightSmall", "LEFT")
    panel.subtitle:SetPoint("TOPLEFT", panel.title, "BOTTOMLEFT", 0, -6)
    panel.subtitle:SetPoint("TOPRIGHT", -18, 0)
    panel.subtitle:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.92)

    panel.progressLabel = createLabel(panel, "GameFontHighlightSmall", "LEFT")
    panel.progressLabel:SetPoint("TOPLEFT", panel.subtitle, "BOTTOMLEFT", 0, -12)
    panel.progressLabel:SetPoint("RIGHT", -190, 0)

    panel.progressValue = createLabel(panel, "GameFontNormalSmall", "RIGHT")
    panel.progressValue:SetPoint("RIGHT", -18, 0)
    panel.progressValue:SetPoint("CENTER", panel.progressLabel, "CENTER", 0, 0)
    panel.progressValue:SetWidth(170)
    panel.progressValue:SetTextColor(GOLD[1], GOLD[2], GOLD[3], 1)

    panel.progressBar = createProgressBar(panel)
    panel.progressBar:SetPoint("TOPLEFT", panel.progressLabel, "BOTTOMLEFT", 0, -8)
    panel.progressBar:SetPoint("TOPRIGHT", -18, 0)
    panel.progressBar:SetHeight(18)
    panel.progressBar:EnableMouse(true)

    panel.statLine = createLabel(panel, "GameFontDisableSmall", "LEFT")
    panel.statLine:SetPoint("TOPLEFT", panel.progressBar, "BOTTOMLEFT", 0, -8)
    panel.statLine:SetPoint("TOPRIGHT", panel.progressBar, "BOTTOMRIGHT", 0, -8)
    panel.statLine:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.76)

    return panel
end

local function layoutCard(card)
    if not card or not card.selector or not card.progressPanel or not card.warbandProgressPanel then
        return
    end

    local selectorWidth = 286
    local rightWidth = 408
    local progressHeight = 172
    local optionsHeight = 108

    card.selector:ClearAllPoints()
    card.selector:SetPoint("TOPLEFT", 14, -14)
    card.selector:SetPoint("BOTTOMLEFT", 14, 14)
    card.selector:SetWidth(selectorWidth)
    if card.rosterChild then
        card.rosterChild:SetWidth(math.max(180, selectorWidth - 58))
    end

    card.rankPanel:ClearAllPoints()
    card.rankPanel:SetPoint("TOPRIGHT", -14, -14)
    card.rankPanel:SetPoint("BOTTOMRIGHT", card, "CENTER", -14, 4)
    card.rankPanel:SetWidth(rightWidth)
    if card.rankChild then
        card.rankChild:SetWidth(math.max(220, rightWidth - 42))
    end

    card.historyPanel:ClearAllPoints()
    card.historyPanel:SetPoint("TOPRIGHT", card.rankPanel, "BOTTOMRIGHT", 0, -14)
    card.historyPanel:SetPoint("BOTTOMRIGHT", -14, 14)
    card.historyPanel:SetWidth(rightWidth)

    card.warbandProgressPanel:ClearAllPoints()
    card.warbandProgressPanel:SetPoint("TOPLEFT", card.selector, "TOPRIGHT", 14, 0)
    card.warbandProgressPanel:SetPoint("TOPRIGHT", card.rankPanel, "TOPLEFT", -14, 0)
    card.warbandProgressPanel:SetHeight(progressHeight)

    card.progressPanel:ClearAllPoints()
    card.progressPanel:SetPoint("TOPLEFT", card.warbandProgressPanel, "BOTTOMLEFT", 0, -14)
    card.progressPanel:SetPoint("TOPRIGHT", card.warbandProgressPanel, "BOTTOMRIGHT", 0, -14)
    card.progressPanel:SetHeight(progressHeight)

    if card.optionsPanel then
        card.optionsPanel:ClearAllPoints()
        card.optionsPanel:SetPoint("TOPLEFT", card.progressPanel, "BOTTOMLEFT", 0, -14)
        card.optionsPanel:SetPoint("TOPRIGHT", card.progressPanel, "BOTTOMRIGHT", 0, -14)
        card.optionsPanel:SetHeight(optionsHeight)
    end
    if card.optionsDrawer then
        card.optionsDrawer:ClearAllPoints()
        card.optionsDrawer:SetPoint("BOTTOMLEFT", card.optionsPanel, "TOPLEFT", 0, 10)
        card.optionsDrawer:SetPoint("BOTTOMRIGHT", card.optionsPanel, "TOPRIGHT", 0, 10)
        card.optionsDrawer:SetHeight(360)
    end

    local historyWidth = math.max(300, math.floor((tonumber(card.historyPanel:GetWidth()) or 620) - 34))
    card.historyChild:SetWidth(historyWidth)
end

local function createCard(parent, helpers)
    local createLabel = callHelper(helpers, "createLabel")
    local createMiniActionButton = callHelper(helpers, "createMiniActionButton")
    local createSectionFrame = callHelper(helpers, "createSectionFrame")
    local createProgressBar = callHelper(helpers, "createProgressBar")
    local styleScrollFrame = callHelper(helpers, "styleScrollFrame")

    local card = createSectionFrame(parent, "card")
    card:Hide()

    card.selector = createPanel(card, L.SIDEBAR_TITLE or L.RENOWN_SCOPE_WARBAND or "Warband", helpers)
    card.warbandButton = createRenownRosterRow(card.selector, helpers)
    card.warbandButton:SetPoint("TOPLEFT", 16, -46)
    card.warbandButton:SetPoint("TOPRIGHT", -16, -46)

    card.resetButton = createMiniActionButton(card.selector, L.RENOWN_RESET_BUTTON or "Reset", 112)
    card.resetButton:SetPoint("BOTTOMLEFT", 16, 54)
    card.resetButton:SetPoint("BOTTOMRIGHT", -16, 54)

    card.rosterScroll = CreateFrame("ScrollFrame", nil, card.selector, "UIPanelScrollFrameTemplate")
    card.rosterScroll:SetPoint("TOPLEFT", card.warbandButton, "BOTTOMLEFT", 0, -10)
    card.rosterScroll:SetPoint("BOTTOMRIGHT", card.resetButton, "TOPRIGHT", -8, 12)
    card.rosterChild = CreateFrame("Frame", nil, card.rosterScroll)
    card.rosterChild:SetSize(214, 10)
    card.rosterScroll:SetScrollChild(card.rosterChild)
    styleScrollFrame(card.rosterScroll)
    card.rosterRows = {}

    card.backupHint = createLabel(card.selector, "GameFontDisableSmall", "LEFT")
    card.backupHint:SetPoint("TOPLEFT", card.resetButton, "BOTTOMLEFT", 0, -12)
    card.backupHint:SetPoint("RIGHT", -16, 0)
    card.backupHint:SetJustifyV("TOP")
    card.backupHint:SetWordWrap(true)
    card.backupHint:SetHeight(58)
    card.backupHint:SetTextColor(0.66, 0.66, 0.62, 0.92)
    card.backupHint:SetText(L.RENOWN_BACKUP_HINT or "Backup: WTF\\Account\\<Account>\\SavedVariables\\Vaultloom.lua")
    if card.backupHint.EnableMouse then
        card.backupHint:EnableMouse(true)
        card.backupHint:SetScript("OnEnter", function(selfText)
            GameTooltip:SetOwner(selfText, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(L.RENOWN_BACKUP_TOOLTIP_TITLE or "Backup file", GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:AddLine(L.RENOWN_BACKUP_HINT or "Backup: WTF\\Account\\<Account>\\SavedVariables\\Vaultloom.lua", 0.86, 0.86, 0.82, true)
            GameTooltip:Show()
        end)
        card.backupHint:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    card.warbandProgressPanel = createProgressSummaryPanel(card, helpers)

    card.progressPanel = createSectionFrame(card, "cardInset")
    card.progressPanel.tint = card.progressPanel:CreateTexture(nil, "BORDER")
    card.progressPanel.tint:SetAllPoints()
    tintTexture(card.progressPanel.tint, METAL, 0.16)

    card.badge = card.progressPanel:CreateTexture(nil, "ARTWORK")
    card.badge:SetSize(92, 92)
    card.badge:SetPoint("LEFT", 28, 8)
    card.badge:SetTexture(PREMIUM.statusBadgeComplete or PREMIUM.statusBadgeOpen or "Interface\\Buttons\\UI-CheckBox-Check")

    card.title = createLabel(card.progressPanel, "GameFontNormalHuge", "LEFT")
    card.title:SetPoint("TOPLEFT", card.badge, "TOPRIGHT", 20, -2)
    card.title:SetPoint("TOPRIGHT", -18, -20)
    card.title:SetTextColor(GOLD[1], GOLD[2], GOLD[3], 1)

    card.subtitle = createLabel(card.progressPanel, "GameFontHighlightSmall", "LEFT")
    card.subtitle:SetPoint("TOPLEFT", card.title, "BOTTOMLEFT", 0, -6)
    card.subtitle:SetPoint("TOPRIGHT", -18, 0)
    card.subtitle:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.92)

    card.optionsPanel = createPanel(card, L.RENOWN_OPTIONS_TITLE or "Options", helpers)
    card.optionsSummary = createLabel(card.optionsPanel, "GameFontDisableSmall", "LEFT")
    card.optionsSummary:SetPoint("TOPLEFT", card.optionsPanel.title, "BOTTOMLEFT", 0, -12)
    card.optionsSummary:SetPoint("RIGHT", -150, 0)
    card.optionsSummary:SetHeight(42)
    card.optionsSummary:SetWordWrap(true)
    card.optionsSummary:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.78)

    card.optionsOpenButton = createMiniActionButton(card.optionsPanel, L.RENOWN_OPTIONS_OPEN or "Open Options", 132)
    card.optionsOpenButton:SetPoint("TOPRIGHT", -16, -44)
    card.optionsOpenButton:SetHeight(26)

    card.optionsDrawer = createPanel(card, L.RENOWN_OPTIONS_DRAWER_TITLE or L.RENOWN_OPTIONS_TITLE or "Chronicle Options", helpers)
    card.optionsDrawer:SetFrameLevel(card:GetFrameLevel() + 24)
    card.optionsDrawer:Hide()
    card.optionsDrawerClose = createMiniActionButton(card.optionsDrawer, L.CLOSE or "Close", 92)
    card.optionsDrawerClose:SetPoint("TOPRIGHT", -14, -11)
    card.optionsDrawerClose:SetHeight(24)
    card.optionsDrawerHint = createLabel(card.optionsDrawer, "GameFontDisableSmall", "LEFT")
    card.optionsDrawerHint:SetPoint("TOPLEFT", card.optionsDrawer.title, "BOTTOMLEFT", 0, -8)
    card.optionsDrawerHint:SetPoint("TOPRIGHT", card.optionsDrawerClose, "BOTTOMLEFT", -12, 0)
    card.optionsDrawerHint:SetHeight(28)
    card.optionsDrawerHint:SetWordWrap(true)
    card.optionsDrawerHint:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.78)
    card.optionsDrawerHint:SetText(L.RENOWN_OPTIONS_DRAWER_HINT or "Chronicle display and popup settings. Hover entries for details.")

    card.appearanceHeader = createOptionsSectionHeader(card.optionsDrawer, L.RENOWN_OPTIONS_SECTION_APPEARANCE or "Appearance", helpers)
    card.appearanceHeader:SetPoint("TOPLEFT", card.optionsDrawerHint, "BOTTOMLEFT", 0, -10)
    card.appearanceHeader:SetPoint("RIGHT", -16, 0)

    card.frameDropdown = createMiniActionButton(card.optionsDrawer, L.RENOWN_FRAME_DEFAULT or "Default", 176)
    card.frameDropdown:SetPoint("TOPLEFT", card.appearanceHeader, "BOTTOMLEFT", 0, -7)
    card.frameDropdown:SetHeight(24)

    card.frameToggle = createMiniActionButton(card.optionsDrawer, L.RENOWN_FRAME_TOGGLE or "Frame", 112)
    card.frameToggle:SetPoint("LEFT", card.frameDropdown, "RIGHT", 10, 0)
    card.frameToggle:SetHeight(24)

    card.badgeDropdown = createMiniActionButton(card.optionsDrawer, L.RENOWN_BADGE_DEFAULT or "Current rank", 176)
    card.badgeDropdown:SetPoint("TOPLEFT", card.frameDropdown, "BOTTOMLEFT", 0, -8)
    card.badgeDropdown:SetHeight(24)

    card.badgeToggle = createMiniActionButton(card.optionsDrawer, L.RENOWN_BADGE_SHOWN or "Badge: On", 112)
    card.badgeToggle:SetPoint("LEFT", card.badgeDropdown, "RIGHT", 10, 0)
    card.badgeToggle:SetHeight(24)

    card.popupHeader = createOptionsSectionHeader(card.optionsDrawer, L.RENOWN_OPTIONS_SECTION_POPUP or "XP Popup", helpers)
    card.popupHeader:SetPoint("TOPLEFT", card.badgeDropdown, "BOTTOMLEFT", 0, -12)
    card.popupHeader:SetPoint("RIGHT", -16, 0)

    card.toastToggle = createMiniActionButton(card.optionsDrawer, L.RENOWN_TOAST_SHOWN or "Popups: On", 112)
    card.toastToggle:SetPoint("TOPLEFT", card.popupHeader, "BOTTOMLEFT", 0, -7)
    card.toastToggle:SetHeight(24)

    card.toastStyleButton = createMiniActionButton(card.optionsDrawer, "", 126)
    card.toastStyleButton:SetPoint("LEFT", card.toastToggle, "RIGHT", 10, 0)
    card.toastStyleButton:SetHeight(24)

    card.toastAnimationButton = createMiniActionButton(card.optionsDrawer, "", 132)
    card.toastAnimationButton:SetPoint("LEFT", card.toastStyleButton, "RIGHT", 10, 0)
    card.toastAnimationButton:SetHeight(24)

    card.toastSizeMinusButton = createMiniActionButton(card.optionsDrawer, "-", 42)
    card.toastSizeMinusButton:SetPoint("TOPLEFT", card.toastToggle, "BOTTOMLEFT", 0, -8)
    card.toastSizeMinusButton:SetHeight(24)

    card.toastSizeLabel = createLabel(card.optionsDrawer, "GameFontHighlightSmall", "CENTER")
    card.toastSizeLabel:SetPoint("LEFT", card.toastSizeMinusButton, "RIGHT", 10, 0)
    card.toastSizeLabel:SetSize(126, 24)
    card.toastSizeLabel:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.9)

    card.toastSizePlusButton = createMiniActionButton(card.optionsDrawer, "+", 42)
    card.toastSizePlusButton:SetPoint("LEFT", card.toastSizeLabel, "RIGHT", 10, 0)
    card.toastSizePlusButton:SetHeight(24)

    card.toastTestButton = createMiniActionButton(card.optionsDrawer, L.RENOWN_TOAST_TEST_BUTTON or "Test", 76)
    card.toastTestButton:SetPoint("TOPLEFT", card.toastSizeMinusButton, "BOTTOMLEFT", 0, -8)
    card.toastTestButton:SetHeight(24)

    card.toastPlaceButton = createMiniActionButton(card.optionsDrawer, L.RENOWN_TOAST_PLACE_BUTTON or "Place", 86)
    card.toastPlaceButton:SetPoint("LEFT", card.toastTestButton, "RIGHT", 10, 0)
    card.toastPlaceButton:SetHeight(24)

    card.toastHideButton = createMiniActionButton(card.optionsDrawer, L.RENOWN_TOAST_HIDE_TEST_BUTTON or "Hide", 76)
    card.toastHideButton:SetPoint("LEFT", card.toastPlaceButton, "RIGHT", 10, 0)
    card.toastHideButton:SetHeight(24)

    card.toastResetButton = createMiniActionButton(card.optionsDrawer, L.RENOWN_TOAST_RESET_BUTTON or "Reset", 86)
    card.toastResetButton:SetPoint("LEFT", card.toastHideButton, "RIGHT", 10, 0)
    card.toastResetButton:SetHeight(24)

    card.warbandHeader = createOptionsSectionHeader(card.optionsDrawer, L.RENOWN_OPTIONS_SECTION_WARBAND or "Warband", helpers)
    card.warbandHeader:SetPoint("TOPLEFT", card.toastTestButton, "BOTTOMLEFT", 0, -12)
    card.warbandHeader:SetPoint("RIGHT", -16, 0)

    card.warbandBadgeToggle = createMiniActionButton(card.optionsDrawer, L.RENOWN_WARBAND_BADGE_SHOWN or "Warband: On", 144)
    card.warbandBadgeToggle:SetPoint("TOPLEFT", card.warbandHeader, "BOTTOMLEFT", 0, -7)
    card.warbandBadgeToggle:SetHeight(24)

    card.frameMenu = CreateFrame("Frame", nil, card.optionsDrawer, BACKDROP_TEMPLATE)
    card.frameMenu:SetPoint("TOPRIGHT", card.frameDropdown, "BOTTOMRIGHT", 0, -4)
    card.frameMenu:SetSize(176, 156)
    card.frameMenu:SetFrameLevel(card.optionsDrawer:GetFrameLevel() + 18)
    card.frameMenu:Hide()
    if card.frameMenu.SetBackdrop then
        card.frameMenu:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false,
            edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 },
        })
        card.frameMenu:SetBackdropColor(SHADOW[1], SHADOW[2], SHADOW[3], 0.94)
        card.frameMenu:SetBackdropBorderColor(GOLD_DIM[1], GOLD_DIM[2], GOLD_DIM[3], 0.92)
    end
    card.frameMenuRows = {}
    for index = 1, COSMETIC_MENU_ROWS do
        local row = createMiniActionButton(card.frameMenu, "", 164)
        row:SetPoint("TOPLEFT", 6, -6 - ((index - 1) * COSMETIC_MENU_ROW_HEIGHT))
        row:SetWidth(COSMETIC_MENU_BUTTON_WIDTH)
        row:SetHeight(23)
        card.frameMenuRows[index] = row
    end

    card.badgeMenu = CreateFrame("Frame", nil, card.optionsDrawer, BACKDROP_TEMPLATE)
    card.badgeMenu:SetPoint("TOPRIGHT", card.badgeDropdown, "BOTTOMRIGHT", 0, -4)
    card.badgeMenu:SetSize(176, 156)
    card.badgeMenu:SetFrameLevel(card.optionsDrawer:GetFrameLevel() + 19)
    card.badgeMenu:Hide()
    if card.badgeMenu.SetBackdrop then
        card.badgeMenu:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false,
            edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 },
        })
        card.badgeMenu:SetBackdropColor(SHADOW[1], SHADOW[2], SHADOW[3], 0.94)
        card.badgeMenu:SetBackdropBorderColor(GOLD_DIM[1], GOLD_DIM[2], GOLD_DIM[3], 0.92)
    end
    card.badgeMenuRows = {}
    for index = 1, COSMETIC_MENU_ROWS do
        local row = createMiniActionButton(card.badgeMenu, "", 164)
        row:SetPoint("TOPLEFT", 6, -6 - ((index - 1) * COSMETIC_MENU_ROW_HEIGHT))
        row:SetWidth(COSMETIC_MENU_BUTTON_WIDTH)
        row:SetHeight(23)
        card.badgeMenuRows[index] = row
    end

    card.progressLabel = createLabel(card.progressPanel, "GameFontHighlightSmall", "LEFT")
    card.progressLabel:SetPoint("TOPLEFT", card.subtitle, "BOTTOMLEFT", 0, -12)
    card.progressLabel:SetPoint("RIGHT", -190, 0)

    card.progressValue = createLabel(card.progressPanel, "GameFontNormalSmall", "RIGHT")
    card.progressValue:SetPoint("RIGHT", -18, 0)
    card.progressValue:SetPoint("CENTER", card.progressLabel, "CENTER", 0, 0)
    card.progressValue:SetWidth(170)
    card.progressValue:SetTextColor(GOLD[1], GOLD[2], GOLD[3], 1)

    card.progressBar = createProgressBar(card.progressPanel)
    card.progressBar:SetPoint("TOPLEFT", card.progressLabel, "BOTTOMLEFT", 0, -8)
    card.progressBar:SetPoint("TOPRIGHT", -18, 0)
    card.progressBar:SetHeight(18)
    card.progressBar:EnableMouse(true)

    card.statLine = createLabel(card.progressPanel, "GameFontDisableSmall", "LEFT")
    card.statLine:SetPoint("TOPLEFT", card.progressBar, "BOTTOMLEFT", 0, -8)
    card.statLine:SetPoint("TOPRIGHT", card.progressBar, "BOTTOMRIGHT", 0, -8)
    card.statLine:SetTextColor(PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], 0.76)

    card.rankPanel = createPanel(card, L.RENOWN_PANEL_RANKS or "Rank Path", helpers)
    card.rankScroll = CreateFrame("ScrollFrame", nil, card.rankPanel, "UIPanelScrollFrameTemplate")
    card.rankScroll:SetPoint("TOPLEFT", card.rankPanel.title, "BOTTOMLEFT", 0, -12)
    card.rankScroll:SetPoint("BOTTOMRIGHT", -22, 12)
    card.rankChild = CreateFrame("Frame", nil, card.rankScroll)
    card.rankChild:SetSize(280, 10)
    card.rankScroll:SetScrollChild(card.rankChild)
    styleScrollFrame(card.rankScroll)

    card.rankRows = {}
    for index = 1, RANK_ROWS do
        local row = createRankRow(card.rankChild, helpers)
        row:SetPoint("TOPLEFT", 0, -((index - 1) * 31))
        row:SetPoint("TOPRIGHT", -4, -((index - 1) * 31))
        card.rankRows[index] = row
    end

    card.historyPanel = createPanel(card, L.RENOWN_HISTORY_TITLE or "Recent Glory", helpers)
    card.historyEmpty = createLabel(card.historyPanel, "GameFontDisableSmall", "LEFT")
    card.historyEmpty:SetPoint("TOPLEFT", card.historyPanel.title, "BOTTOMLEFT", 0, -18)
    card.historyEmpty:SetPoint("RIGHT", -16, 0)
    card.historyEmpty:SetWordWrap(true)

    card.historyScroll = CreateFrame("ScrollFrame", nil, card.historyPanel, "UIPanelScrollFrameTemplate")
    card.historyScroll:SetPoint("TOPLEFT", card.historyPanel.title, "BOTTOMLEFT", 0, -12)
    card.historyScroll:SetPoint("BOTTOMRIGHT", -22, 12)
    card.historyChild = CreateFrame("Frame", nil, card.historyScroll)
    card.historyChild:SetSize(400, 10)
    card.historyScroll:SetScrollChild(card.historyChild)
    styleScrollFrame(card.historyScroll)

    card.historyRows = {}
    for index = 1, HISTORY_ROWS do
        local row = createHistoryRow(card.historyChild, helpers)
        row:SetPoint("TOPLEFT", 0, -((index - 1) * HISTORY_ROW_HEIGHT))
        row:SetPoint("TOPRIGHT", -4, -((index - 1) * HISTORY_ROW_HEIGHT))
        card.historyRows[index] = row
    end

    card.warbandButton:SetScript("OnClick", function()
        if ns.SetRenownScope then
            ns:SetRenownScope("warband")
        end
    end)

    card.resetButton:SetScript("OnClick", function()
        local scope = ns.GetRenownScope and ns:GetRenownScope() or "character"
        local character = ns.GetSelectedCharacter and ns:GetSelectedCharacter() or nil
        showResetConfirmation(scope, character)
    end)

    local function setOptionsDrawerShown(shown)
        card.optionsDrawerOpen = shown == true
        if card.optionsDrawer then
            card.optionsDrawer:SetShown(card.optionsDrawerOpen)
        end
        if card.optionsOpenButton and card.optionsOpenButton.SetButtonText then
            card.optionsOpenButton:SetButtonText(card.optionsDrawerOpen and (L.RENOWN_OPTIONS_CLOSE or "Close") or (L.RENOWN_OPTIONS_OPEN or "Open Options"), 132, 148)
        elseif card.optionsOpenButton and card.optionsOpenButton.label then
            card.optionsOpenButton.label:SetText(card.optionsDrawerOpen and (L.RENOWN_OPTIONS_CLOSE or "Close") or (L.RENOWN_OPTIONS_OPEN or "Open Options"))
        end
        if card.frameMenu then
            card.frameMenu:Hide()
        end
        if card.badgeMenu then
            card.badgeMenu:Hide()
        end
    end

    card.optionsOpenButton:SetScript("OnClick", function()
        setOptionsDrawerShown(not card.optionsDrawerOpen)
    end)
    card.optionsOpenButton:SetScript("OnEnter", function(selfButton)
        GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
        GameTooltip:AddLine(L.RENOWN_OPTIONS_DRAWER_TITLE or L.RENOWN_OPTIONS_TITLE or "Chronicle Options", GOLD[1], GOLD[2], GOLD[3], true)
        GameTooltip:AddLine(L.RENOWN_OPTIONS_DRAWER_TOOLTIP or "Open Chronicle display, badge, frame, and popup settings.", 0.86, 0.86, 0.82, true)
        GameTooltip:Show()
    end)
    card.optionsOpenButton:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    card.optionsDrawerClose:SetScript("OnClick", function()
        setOptionsDrawerShown(false)
    end)

    card.toastTestButton:SetScript("OnClick", function()
        if ns.UI and ns.UI.ShowRenownTestToast then
            ns.UI:ShowRenownTestToast(false)
        end
    end)
    card.toastTestButton:SetScript("OnEnter", function(selfButton)
        GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
        GameTooltip:AddLine(L.RENOWN_TOAST_TEST_BUTTON or "Test", GOLD[1], GOLD[2], GOLD[3], true)
        GameTooltip:AddLine(L.RENOWN_TOAST_TEST_TOOLTIP or "Shows a short Chronicle popup preview without granting XP.", 0.86, 0.86, 0.82, true)
        GameTooltip:Show()
    end)
    card.toastTestButton:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    card.toastPlaceButton:SetScript("OnClick", function()
        if ns.UI and ns.UI.ShowRenownTestToast then
            ns.UI:ShowRenownTestToast(true)
        end
    end)
    card.toastPlaceButton:SetScript("OnEnter", function(selfButton)
        GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
        GameTooltip:AddLine(L.RENOWN_TOAST_PLACE_BUTTON or "Place", GOLD[1], GOLD[2], GOLD[3], true)
        GameTooltip:AddLine(L.RENOWN_TOAST_PLACE_TOOLTIP or "Keeps a test popup visible so you can drag it to the desired position.", 0.86, 0.86, 0.82, true)
        GameTooltip:Show()
    end)
    card.toastPlaceButton:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    card.toastHideButton:SetScript("OnClick", function()
        if ns.UI and ns.UI.HideRenownToast then
            ns.UI:HideRenownToast()
        end
    end)
    card.toastHideButton:SetScript("OnEnter", function(selfButton)
        GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
        GameTooltip:AddLine(L.RENOWN_TOAST_HIDE_TEST_BUTTON or "Hide", GOLD[1], GOLD[2], GOLD[3], true)
        GameTooltip:AddLine(L.RENOWN_TOAST_HIDE_TEST_TOOLTIP or "Hides the test popup and leaves the saved position intact.", 0.86, 0.86, 0.82, true)
        GameTooltip:Show()
    end)
    card.toastHideButton:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    card.toastResetButton:SetScript("OnClick", function()
        if ns.ResetRenownToastPosition then
            ns:ResetRenownToastPosition()
        end
        if ns.UI and ns.UI.ShowRenownTestToast then
            ns.UI:ShowRenownTestToast(true)
        end
    end)
    card.toastResetButton:SetScript("OnEnter", function(selfButton)
        GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
        GameTooltip:AddLine(L.RENOWN_TOAST_RESET_BUTTON or "Reset", GOLD[1], GOLD[2], GOLD[3], true)
        GameTooltip:AddLine(L.RENOWN_TOAST_RESET_TOOLTIP or "Resets the Chronicle popup to the default position and keeps the test popup visible.", 0.86, 0.86, 0.82, true)
        GameTooltip:Show()
    end)
    card.toastResetButton:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    card:SetScript("OnSizeChanged", layoutCard)
    return card
end

local function refreshRoster(card, character, scope, helpers)
    local characters = ns.GetLiveVaultloomCharacters and ns:GetLiveVaultloomCharacters() or {}
    local getClassColor = helpers and helpers.getClassColor or function()
        return GOLD[1], GOLD[2], GOLD[3]
    end
    local setClassIconTexture = helpers and helpers.setClassIconTexture or function() end
    local selectedKey = character and character.key
    local warbandView = ns.GetRenownView and ns:GetRenownView(character, "warband") or nil
    local warbandRank = type(warbandView) == "table" and warbandView.rank or nil

    setRenownRosterActive(card.warbandButton, scope == "warband")
    setRosterRowText(
        card.warbandButton,
        L.RENOWN_WARBAND_ENTRY or "Warband",
        string.format(L.RENOWN_WARBAND_PROGRESS_META or "%d tracked characters", #characters)
    )
    if card.warbandButton.name then
        card.warbandButton.name:SetTextColor(GOLD[1], GOLD[2], GOLD[3])
    end
    if card.warbandButton.classIcon then
        card.warbandButton.classIcon:SetTexture(getBadgeForRank(warbandRank, "warband"))
        card.warbandButton.classIcon:SetTexCoord(0, 1, 0, 1)
    end
    if card.warbandButton.iconRing then
        card.warbandButton.iconRing:SetTexture(PREMIUM.classRing)
        card.warbandButton.iconRing:Show()
    end
    if card.warbandButton.classLine then
        card.warbandButton.classLine:SetColorTexture(GOLD[1], GOLD[2], GOLD[3], 1)
    end
    if card.warbandButton.mainTag then
        card.warbandButton.mainTag:Hide()
    end
    ensureRenownRosterRows(card, #characters, helpers)
    for index, row in ipairs(card.rosterRows or {}) do
        local entry = characters[index]
        if entry then
            local isSelected = scope ~= "warband" and selectedKey and entry.key == selectedKey
            local isMain = ns.IsLiveMainCharacter and ns:IsLiveMainCharacter(entry.key)
            local r, g, b = getClassColor(entry.classFile or entry.classToken or entry.classFilename)
            local levelText = tonumber(entry.level) and tonumber(entry.level) > 0 and tostring(entry.level) or "--"
            local itemLevel = tonumber(entry.itemLevel) or 0
            local metaText = itemLevel > 0 and string.format("Lv %s  |  ilvl %d", levelText, itemLevel) or string.format("Lv %s", levelText)

            row.characterKey = entry.key
            setRosterRowText(row, getShortCharacterName(entry), metaText)
            if row.name then
                row.name:SetTextColor(r, g, b)
            end
            if row.classIcon then
                setClassIconTexture(row.classIcon, entry.classFile or entry.classToken or entry.classFilename)
            end
            if row.iconRing then
                row.iconRing:SetTexture(PREMIUM.classRing)
                row.iconRing:Show()
            end
            if row.classLine then
                row.classLine:SetColorTexture(r, g, b, 1)
                row.classLine:SetWidth(isSelected and 3 or 2)
            end
            if row.mainTag then
                row.mainTag:SetShown(isMain == true)
            end
            row:SetScript("OnClick", function(selfButton)
                if ns.SetSelectedCharacterKey and selfButton.characterKey then
                    ns:SetSelectedCharacterKey(selfButton.characterKey)
                end
                if ns.SetRenownScope then
                    ns:SetRenownScope("character")
                end
            end)
            setRenownRosterActive(row, isSelected)
            row:Show()
        else
            row.characterKey = nil
            row:Hide()
        end
    end

    if card.rosterChild then
        card.rosterChild:SetHeight(math.max(10, (#characters * 64) - 6))
    end
    if card.rosterScroll and card.rosterScroll.UpdateScrollChildRect then
        card.rosterScroll:UpdateScrollChildRect()
    end
end

local function refreshRankRows(card, view, character)
    local ranks = ns.Renown and ns.Renown.GetRanks and ns.Renown.GetRanks(view.scope) or {}
    local currentIndex = 1
    local frameState = ns.GetRenownSelectedPortraitFrame and ns:GetRenownSelectedPortraitFrame(character) or { key = "default" }
    local selectedFrameKey = frameState and frameState.key or "default"
    local characterKey = character and character.key
    for index, rank in ipairs(ranks) do
        if (tonumber(view.level) or 1) >= (tonumber(rank.level) or 1) then
            currentIndex = index
        end
    end

    for rowIndex, row in ipairs(card.rankRows or {}) do
        local rank = ranks[rowIndex]
        if rank then
            local naturalReached = (tonumber(view.level) or 1) >= (tonumber(rank.level) or 1)
            local frameUnlocked = isRankFrameUnlocked(view, rank)
            local reached = naturalReached or frameUnlocked
            local current = rowIndex == currentIndex
            local selectedFrame = view.scope == "character" and selectedFrameKey == rank.key
            row.marker:SetTexture(reached and getBadgeForRank(rank, view.scope) or (PREMIUM.statusBadgeOpen or "Interface\\Buttons\\UI-CheckBox-Check"))
            row.marker:SetAlpha(reached and 1 or 0.45)
            if row.bg then
                if selectedFrame then
                    tintTexture(row.bg, GOLD_DIM, 0.42)
                elseif current then
                    tintTexture(row.bg, GOLD_DIM, 0.30)
                else
                    tintTexture(row.bg, SHADOW, rowIndex % 2 == 0 and 0.08 or 0)
                end
            end
            local prefix = view.scope == "warband" and "RENOWN_WARBAND_RANK_" or "RENOWN_RANK_"
            row.label:SetText(rank.label or L[prefix .. string.upper(rank.key or "")] or "")
            row.label:SetTextColor(current and GOLD[1] or PARCHMENT[1], current and GOLD[2] or PARCHMENT[2], current and GOLD[3] or PARCHMENT[3], current and 1 or 0.92)
            if selectedFrame then
                row.value:SetText(L.RENOWN_FRAME_SELECTED or "Selected")
            else
                row.value:SetText(current and (L.RENOWN_CURRENT_RANK or "Now") or string.format(L.RENOWN_RANK_LEVEL_FORMAT or "Lv %d", rank.level or 1))
            end
            local clickedRank = rank
            local clickedReached = reached
            local clickedCurrent = current
            local clickedSelectedFrame = selectedFrame
            local clickedPrefix = prefix
            local clickedRowIndex = rowIndex
            row:SetScript("OnClick", nil)
            row:SetScript("OnEnter", function(selfRow)
                if selfRow.bg then
                    tintTexture(selfRow.bg, clickedSelectedFrame and GOLD_DIM or GOLD, clickedSelectedFrame and 0.48 or 0.18)
                end
                GameTooltip:SetOwner(selfRow, "ANCHOR_CURSOR_RIGHT")
                GameTooltip:AddLine(clickedRank.label or L[clickedPrefix .. string.upper(clickedRank.key or "")] or "", GOLD[1], GOLD[2], GOLD[3], true)
                GameTooltip:AddLine(string.format(L.RENOWN_RANK_LEVEL_FORMAT or "Lv %d", clickedRank.level or 1), PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], true)
                if view.scope == "character" then
                    if clickedReached and getFrameForRank(clickedRank) then
                        GameTooltip:AddLine(L.RENOWN_FRAME_DROPDOWN_TOOLTIP or "Select unlocked portrait frames from the dropdown.", 0.86, 0.86, 0.82, true)
                    else
                        GameTooltip:AddLine(L.RENOWN_FRAME_LOCKED_TOOLTIP or "Reach this rank to unlock its portrait frame.", 0.66, 0.66, 0.62, true)
                    end
                end
                GameTooltip:Show()
            end)
            row:SetScript("OnLeave", function(selfRow)
                if selfRow.bg then
                    if clickedSelectedFrame then
                        tintTexture(selfRow.bg, GOLD_DIM, 0.42)
                    elseif clickedCurrent then
                        tintTexture(selfRow.bg, GOLD_DIM, 0.30)
                    else
                        tintTexture(selfRow.bg, SHADOW, clickedRowIndex % 2 == 0 and 0.08 or 0)
                    end
                end
                GameTooltip:Hide()
            end)
            row:Show()
        else
            row:SetScript("OnClick", nil)
            row:SetScript("OnEnter", nil)
            row:SetScript("OnLeave", nil)
            row:Hide()
        end
    end

    if card.rankChild then
        card.rankChild:SetHeight(math.max(10, (#ranks * 31) + 4))
    end
    if card.rankScroll and card.rankScroll.UpdateScrollChildRect then
        card.rankScroll:UpdateScrollChildRect()
    end
end

local function refreshHistory(card, history)
    history = type(history) == "table" and history or {}
    card.historyEmpty:SetText(L.RENOWN_HISTORY_EMPTY or "No new XP events yet. Existing completed activities are baselined first so old snapshots do not grant duplicate XP.")
    card.historyEmpty:SetShown(#history == 0)
    card.historyScroll:SetShown(#history > 0)

    for index, row in ipairs(card.historyRows or {}) do
        local entry = history[index]
        if entry then
            tintTexture(row.bg, SHADOW, index % 2 == 0 and 0.10 or 0.18)
            row.icon:SetTexture(PREMIUM.statusBadgeComplete or "Interface\\Buttons\\UI-CheckBox-Check")
            row.label:SetText(entry.label or "")
            row.amount:SetText(string.format("+%d XP", tonumber(entry.amount) or 0))
            row.time:SetText(formatTimeAgo(entry.at))
            row:Show()
        else
            row:Hide()
        end
    end

    card.historyChild:SetHeight(math.max(10, math.min(#history, HISTORY_ROWS) * HISTORY_ROW_HEIGHT + 4))
    if card.historyScroll.UpdateScrollChildRect then
        card.historyScroll:UpdateScrollChildRect()
    end
end

local function getLocalizedRankLabel(rank, scope)
    if type(rank) ~= "table" then
        return ""
    end
    local prefix = scope == "warband" and "RENOWN_WARBAND_RANK_" or "RENOWN_RANK_"
    return rank.label or L[prefix .. string.upper(rank.key or "")] or ""
end

local function setDropdownText(button, text)
    if not button then
        return
    end
    if button.SetButtonText then
        button:SetButtonText(text or "", 150, 176)
    elseif button.label then
        button.label:SetText(text or "")
    end
end

local function hideFrameMenu(card)
    if card and card.frameMenu then
        card.frameMenu:Hide()
    end
    if card and card.badgeMenu then
        card.badgeMenu:Hide()
    end
end

local function layoutCosmeticMenu(menu, rows, optionCount)
    if not menu then
        return
    end

    optionCount = math.max(1, tonumber(optionCount) or 1)
    local columns = optionCount > 10 and 2 or 1
    local rowCount = math.ceil(optionCount / columns)
    local width = 12 + (columns * COSMETIC_MENU_BUTTON_WIDTH) + ((columns - 1) * COSMETIC_MENU_COLUMN_GAP)
    menu:SetSize(width, (rowCount * COSMETIC_MENU_ROW_HEIGHT) + 12)

    for index, row in ipairs(rows or {}) do
        local column = (index - 1) % columns
        local rowIndex = math.floor((index - 1) / columns)
        row:ClearAllPoints()
        row:SetPoint("TOPLEFT", menu, "TOPLEFT", 6 + (column * (COSMETIC_MENU_BUTTON_WIDTH + COSMETIC_MENU_COLUMN_GAP)), -6 - (rowIndex * COSMETIC_MENU_ROW_HEIGHT))
        row:SetWidth(COSMETIC_MENU_BUTTON_WIDTH)
    end
end

local function refreshFrameDropdown(card, scope, character)
    if not card or not card.frameDropdown or not card.frameMenu then
        return
    end

    local frameCharacter = getFrameCharacter(character, scope)
    local frameCharacterKey = frameCharacter and frameCharacter.key
    local frameView = ns.GetRenownView and ns:GetRenownView(frameCharacter, "character") or { scope = "character", level = 1 }
    frameView.scope = "character"
    local frameState = ns.GetRenownSelectedPortraitFrame and ns:GetRenownSelectedPortraitFrame(frameCharacter) or { key = "default" }
    local selectedKey = frameState and frameState.key or "default"
    local ranks = ns.Renown and ns.Renown.GetRanks and ns.Renown.GetRanks("character") or {}

    local options = {
        { key = "default", label = L.RENOWN_FRAME_DEFAULT or "Default", texture = PREMIUM.classRing },
    }
    for _, rank in ipairs(ranks) do
        if getFrameForRank(rank) and isRankFrameUnlocked(frameView, rank) then
            options[#options + 1] = {
                key = rank.key,
                label = getLocalizedRankLabel(rank, "character"),
                texture = getFrameForRank(rank),
            }
        end
    end

    local selectedLabel = L.RENOWN_FRAME_DEFAULT or "Default"
    for _, option in ipairs(options) do
        if option.key == selectedKey then
            selectedLabel = option.label
            break
        end
    end
    setDropdownText(card.frameDropdown, string.format(L.RENOWN_FRAME_DROPDOWN_FORMAT or "Frame: %s", selectedLabel))

    card.frameDropdown:SetScript("OnClick", function()
        if card.frameMenu:IsShown() then
            card.frameMenu:Hide()
        else
            if card.badgeMenu then
                card.badgeMenu:Hide()
            end
            card.frameMenu:Show()
        end
    end)
    card.frameDropdown:SetScript("OnEnter", function(selfButton)
        GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
        GameTooltip:AddLine(L.RENOWN_FRAME_DROPDOWN_TITLE or "Portrait frame", GOLD[1], GOLD[2], GOLD[3], true)
        GameTooltip:AddLine(L.RENOWN_FRAME_DROPDOWN_TOOLTIP or "Select unlocked portrait frames from the dropdown.", 0.86, 0.86, 0.82, true)
        GameTooltip:Show()
    end)
    card.frameDropdown:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    layoutCosmeticMenu(card.frameMenu, card.frameMenuRows, #options)
    for index, row in ipairs(card.frameMenuRows or {}) do
        local option = options[index]
        if option then
            setDropdownText(row, option.label)
            row:SetAlpha(option.key == selectedKey and 1 or 0.88)
            row:SetScript("OnClick", function()
                if frameCharacterKey and ns.SetRenownSelectedPortraitFrame then
                    ns:SetRenownSelectedPortraitFrame(frameCharacterKey, option.key)
                end
                hideFrameMenu(card)
            end)
            row:SetScript("OnEnter", function(selfButton)
                GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
                GameTooltip:AddLine(option.label, GOLD[1], GOLD[2], GOLD[3], true)
                GameTooltip:AddLine(L.RENOWN_FRAME_MENU_OPTION_TOOLTIP or "Use this frame around the character portrait.", 0.86, 0.86, 0.82, true)
                GameTooltip:Show()
            end)
            row:SetScript("OnLeave", function()
                GameTooltip:Hide()
            end)
            row:Show()
        else
            row:SetScript("OnClick", nil)
            row:SetScript("OnEnter", nil)
            row:SetScript("OnLeave", nil)
            row:Hide()
        end
    end
end

local function refreshBadgeDropdown(card, scope, character)
    if not card or not card.badgeDropdown or not card.badgeMenu then
        return
    end

    local badgeCharacter = getFrameCharacter(character, scope)
    local badgeCharacterKey = badgeCharacter and badgeCharacter.key
    local badgeView = ns.GetRenownView and ns:GetRenownView(badgeCharacter, "character") or { scope = "character", level = 1 }
    badgeView.scope = "character"
    local badgeState = ns.GetRenownSelectedBadge and ns:GetRenownSelectedBadge(badgeCharacter) or { key = "default" }
    local selectedKey = badgeState and badgeState.key or "default"
    local ranks = ns.Renown and ns.Renown.GetRanks and ns.Renown.GetRanks("character") or {}

    local options = {
        { key = "default", label = L.RENOWN_BADGE_DEFAULT or "Current rank", texture = getBadgeForRank(badgeView.rank, "character") },
    }
    for _, rank in ipairs(ranks) do
        if getBadgeForRank(rank, "character") and isRankFrameUnlocked(badgeView, rank) then
            options[#options + 1] = {
                key = rank.key,
                label = getLocalizedRankLabel(rank, "character"),
                texture = getBadgeForRank(rank, "character"),
            }
        end
    end

    local selectedLabel = L.RENOWN_BADGE_DEFAULT or "Current rank"
    for _, option in ipairs(options) do
        if option.key == selectedKey then
            selectedLabel = option.label
            break
        end
    end
    setDropdownText(card.badgeDropdown, string.format(L.RENOWN_BADGE_DROPDOWN_FORMAT or "Badge: %s", selectedLabel))

    card.badgeDropdown:SetScript("OnClick", function()
        if card.badgeMenu:IsShown() then
            card.badgeMenu:Hide()
        else
            if card.frameMenu then
                card.frameMenu:Hide()
            end
            card.badgeMenu:Show()
        end
    end)
    card.badgeDropdown:SetScript("OnEnter", function(selfButton)
        GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
        GameTooltip:AddLine(L.RENOWN_BADGE_DROPDOWN_TITLE or "Header badge", GOLD[1], GOLD[2], GOLD[3], true)
        GameTooltip:AddLine(L.RENOWN_BADGE_DROPDOWN_TOOLTIP or "Select the unlocked badge shown in the header and Chronicle panel.", 0.86, 0.86, 0.82, true)
        GameTooltip:Show()
    end)
    card.badgeDropdown:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    layoutCosmeticMenu(card.badgeMenu, card.badgeMenuRows, #options)
    for index, row in ipairs(card.badgeMenuRows or {}) do
        local option = options[index]
        if option then
            setDropdownText(row, option.label)
            row:SetAlpha(option.key == selectedKey and 1 or 0.88)
            row:SetScript("OnClick", function()
                if badgeCharacterKey and ns.SetRenownSelectedBadge then
                    ns:SetRenownSelectedBadge(badgeCharacterKey, option.key)
                end
                hideFrameMenu(card)
            end)
            row:SetScript("OnEnter", function(selfButton)
                GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
                GameTooltip:AddLine(option.label, GOLD[1], GOLD[2], GOLD[3], true)
                GameTooltip:AddLine(L.RENOWN_BADGE_MENU_OPTION_TOOLTIP or "Use this badge in the character header.", 0.86, 0.86, 0.82, true)
                GameTooltip:Show()
            end)
            row:SetScript("OnLeave", function()
                GameTooltip:Hide()
            end)
            row:Show()
        else
            row:SetScript("OnClick", nil)
            row:SetScript("OnEnter", nil)
            row:SetScript("OnLeave", nil)
            row:Hide()
        end
    end
end

local function getRenownViewOrFallback(character, scope)
    local view = ns.GetRenownView and ns:GetRenownView(character, scope) or nil
    view = type(view) == "table" and view or {
        history = {},
        rank = { label = "Peon", assetIndex = 1 },
        ratio = 0,
        totalXP = 0,
        level = 1,
        currentXP = 0,
        neededXP = 1,
        scope = scope,
    }
    view.scope = scope
    view.rank = type(view.rank) == "table" and view.rank or { label = "Peon", assetIndex = 1 }
    return view
end

local function setProgressTooltip(progressBar, view)
    if not progressBar then
        return
    end
    progressBar:SetScript("OnEnter", function(selfBar)
        GameTooltip:SetOwner(selfBar, "ANCHOR_CURSOR_RIGHT")
        GameTooltip:AddLine(L.RENOWN_XP_TOOLTIP_TITLE or "Chronik XP", GOLD[1], GOLD[2], GOLD[3], true)
        GameTooltip:AddLine(L.RENOWN_XP_TOOLTIP_BODY or "XP comes from completed Vaultloom tasks: vault rows, weekly and daily objectives, dungeons, raids, delves, PvP, professions, and completed neighborhood tasks.", 0.86, 0.86, 0.82, true)
        GameTooltip:AddLine(string.format("%s / %s XP", formatNumber(view.currentXP or 0), formatNumber(view.neededXP or 0)), PARCHMENT[1], PARCHMENT[2], PARCHMENT[3], true)
        GameTooltip:Show()
    end)
    progressBar:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)
end

local function refreshProgressSummary(panel, view, titleText, subtitleText, badgeTexture, setProgressBar)
    if not panel then
        return
    end

    local rankLabel = view.rank and view.rank.label or ""
    panel.badge:SetTexture(badgeTexture or getBadgeForRank(view.rank, view.scope))
    panel.badge:Show()
    panel.title:SetText(titleText or "")
    panel.subtitle:SetText(subtitleText or "")
    panel.progressLabel:SetText(string.format(L.RENOWN_LEVEL_FORMAT or "Level %d - %s", view.level or 1, rankLabel))
    panel.progressValue:SetText(string.format("%s / %s XP", formatNumber(view.currentXP or 0), formatNumber(view.neededXP or 0)))
    panel.statLine:SetText(string.format(
        L.RENOWN_STAT_LINE_FORMAT or "Total %s XP  |  Next rank: %s  |  Backup: SavedVariables",
        formatNumber(view.totalXP or 0),
        view.nextRank and view.nextRank.label or (L.RENOWN_MAX_RANK or "Paragon")
    ))
    setProgressBar(panel.progressBar, view.ratio or 0, GOLD[1], GOLD[2], GOLD[3])
    setProgressTooltip(panel.progressBar, view)
end

local function refreshCard(ui, character, helpers)
    if not ui or not ui.frame or not ui.frame.renownCard then
        return
    end

    local card = ui.frame.renownCard
    local setProgressBar = callHelper(helpers, "setProgressBar")
    local detailScope = ns.GetRenownScope and ns:GetRenownScope() or "character"
    local characterView = getRenownViewOrFallback(character, "character")
    local warbandView = getRenownViewOrFallback(character, "warband")
    local view = detailScope == "warband" and warbandView or characterView

    layoutCard(card)
    refreshRoster(card, character, detailScope, helpers)

    local characterScopeLabel = L.RENOWN_SCOPE_CHARACTER or "Character"
    local warbandScopeLabel = L.RENOWN_SCOPE_WARBAND or "Warband"
    local activeLabel = getCharacterProgressMeta(character)
    local trackedCharacters = ns.GetLiveVaultloomCharacters and ns:GetLiveVaultloomCharacters() or {}
    local warbandMeta = string.format(L.RENOWN_WARBAND_PROGRESS_META or "%d tracked characters", #trackedCharacters)
    local frameCharacter = getFrameCharacter(character, "character")
    local frameState = ns.GetRenownSelectedPortraitFrame and ns:GetRenownSelectedPortraitFrame(frameCharacter) or { visible = true, key = "default" }
    local badgeState = ns.GetRenownSelectedBadge and ns:GetRenownSelectedBadge(frameCharacter) or { visible = true, key = "default" }
    local frameVisible = frameState.visible ~= false
    local badgeVisible = badgeState.visible ~= false
    local toastVisible = not ns.GetRenownToastVisible or ns:GetRenownToastVisible()
    local toastStyle = ns.GetRenownToastStyle and ns:GetRenownToastStyle() or "normal"
    local toastAnimation = ns.GetRenownToastAnimation and ns:GetRenownToastAnimation() or "soft"
    local toastScale = ns.GetRenownToastScale and ns:GetRenownToastScale() or 1
    local warbandBadgeVisible = not ns.GetRenownWarbandBadgeVisible or ns:GetRenownWarbandBadgeVisible()

    if card.optionsDrawer then
        card.optionsDrawer:SetShown(card.optionsDrawerOpen == true)
    end
    if card.optionsOpenButton then
        local buttonText = card.optionsDrawerOpen and (L.RENOWN_OPTIONS_CLOSE or "Close") or (L.RENOWN_OPTIONS_OPEN or "Open Options")
        if card.optionsOpenButton.SetButtonText then
            card.optionsOpenButton:SetButtonText(buttonText, 132, 148)
        elseif card.optionsOpenButton.label then
            card.optionsOpenButton.label:SetText(buttonText)
        end
    end
    if card.optionsSummary then
        local onText = L.RENOWN_OPTIONS_STATE_ON or "On"
        local offText = L.RENOWN_OPTIONS_STATE_OFF or "Off"
        card.optionsSummary:SetText(string.format(
            L.RENOWN_OPTIONS_SUMMARY_FORMAT or "Frame %s | Badge %s | Popups %s | Warband %s",
            frameVisible and onText or offText,
            badgeVisible and onText or offText,
            toastVisible and onText or offText,
            warbandBadgeVisible and onText or offText
        ))
    end

    refreshProgressSummary(
        card.warbandProgressPanel,
        warbandView,
        string.format(L.RENOWN_TITLE_FORMAT or "%s Glory", warbandScopeLabel),
        warbandMeta,
        getBadgeForRank(warbandView.rank, "warband"),
        setProgressBar
    )

    do
        local badgeTexture = nil
        if badgeState.index and PREMIUM.renownBadges then
            badgeTexture = PREMIUM.renownBadges[tonumber(badgeState.index)]
        end
        card.badge:SetTexture(badgeTexture or getBadgeForRank(characterView.rank, "character"))
        card.badge:Show()
    end
    card.title:SetText(string.format(L.RENOWN_TITLE_FORMAT or "%s Glory", characterScopeLabel))
    card.subtitle:SetText(activeLabel)
    card.progressLabel:SetText(string.format(L.RENOWN_LEVEL_FORMAT or "Level %d - %s", characterView.level or 1, characterView.rank and characterView.rank.label or ""))
    card.progressValue:SetText(string.format("%s / %s XP", formatNumber(characterView.currentXP or 0), formatNumber(characterView.neededXP or 0)))
    card.statLine:SetText(string.format(
        L.RENOWN_STAT_LINE_FORMAT or "Total %s XP  |  Next rank: %s  |  Backup: SavedVariables",
        formatNumber(characterView.totalXP or 0),
        characterView.nextRank and characterView.nextRank.label or (L.RENOWN_MAX_RANK or "Paragon")
    ))
    setProgressBar(card.progressBar, characterView.ratio or 0, GOLD[1], GOLD[2], GOLD[3])
    setProgressTooltip(card.progressBar, characterView)

    if card.frameToggle then
        local enabled = frameVisible
        if card.frameToggle.SetButtonText then
            card.frameToggle:SetButtonText(enabled and (L.RENOWN_FRAME_SHOWN or "Frame: On") or (L.RENOWN_FRAME_HIDDEN or "Frame: Off"), 112, 132)
        elseif card.frameToggle.label then
            card.frameToggle.label:SetText(enabled and (L.RENOWN_FRAME_SHOWN or "Frame: On") or (L.RENOWN_FRAME_HIDDEN or "Frame: Off"))
        end
        card.frameToggle:SetScript("OnClick", function()
            if ns.SetRenownPortraitFrameVisible then
                ns:SetRenownPortraitFrameVisible(not enabled)
            end
        end)
        card.frameToggle:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(L.RENOWN_FRAME_TOGGLE_TOOLTIP or "Show or hide the selected portrait frame.", GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:Show()
        end)
        card.frameToggle:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    refreshFrameDropdown(card, "character", character)

    if card.badgeToggle then
        local enabled = badgeVisible
        if card.badgeToggle.SetButtonText then
            card.badgeToggle:SetButtonText(enabled and (L.RENOWN_BADGE_SHOWN or "Badge: On") or (L.RENOWN_BADGE_HIDDEN or "Badge: Off"), 112, 132)
        elseif card.badgeToggle.label then
            card.badgeToggle.label:SetText(enabled and (L.RENOWN_BADGE_SHOWN or "Badge: On") or (L.RENOWN_BADGE_HIDDEN or "Badge: Off"))
        end
        card.badgeToggle:SetScript("OnClick", function()
            if ns.SetRenownBadgeVisible then
                ns:SetRenownBadgeVisible(not enabled)
            end
        end)
        card.badgeToggle:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(L.RENOWN_BADGE_TOGGLE_TOOLTIP or "Show or hide the selected header badge.", GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:Show()
        end)
        card.badgeToggle:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    refreshBadgeDropdown(card, "character", character)

    if card.toastToggle then
        local enabled = toastVisible
        if card.toastToggle.SetButtonText then
            card.toastToggle:SetButtonText(enabled and (L.RENOWN_TOAST_SHOWN or "Popups: On") or (L.RENOWN_TOAST_HIDDEN or "Popups: Off"), 126, 144)
        elseif card.toastToggle.label then
            card.toastToggle.label:SetText(enabled and (L.RENOWN_TOAST_SHOWN or "Popups: On") or (L.RENOWN_TOAST_HIDDEN or "Popups: Off"))
        end
        card.toastToggle:SetScript("OnClick", function()
            if ns.SetRenownToastVisible then
                ns:SetRenownToastVisible(not enabled)
            end
        end)
        card.toastToggle:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(enabled and (L.RENOWN_TOAST_SHOWN or "Popups: On") or (L.RENOWN_TOAST_HIDDEN or "Popups: Off"), GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:AddLine(L.RENOWN_TOAST_TOGGLE_TOOLTIP or "Shows or hides Chronicle XP popups. XP tracking continues.", 0.86, 0.86, 0.82, true)
            GameTooltip:Show()
        end)
        card.toastToggle:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    if card.toastStyleButton then
        local styleLabel = getToastStyleLabel(toastStyle)
        local buttonText = string.format(L.RENOWN_TOAST_STYLE_FORMAT or "Style: %s", styleLabel)
        if card.toastStyleButton.SetButtonText then
            card.toastStyleButton:SetButtonText(buttonText, 126, 146)
        elseif card.toastStyleButton.label then
            card.toastStyleButton.label:SetText(buttonText)
        end
        card.toastStyleButton:SetScript("OnClick", function()
            if ns.SetRenownToastStyle then
                ns:SetRenownToastStyle(cycleOptionValue(TOAST_STYLE_ORDER, toastStyle))
            end
        end)
        card.toastStyleButton:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(buttonText, GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:AddLine(L.RENOWN_TOAST_STYLE_TOOLTIP or "Normal shows the full popup, Clean removes the background, Simple shows only icon and XP.", 0.86, 0.86, 0.82, true)
            GameTooltip:Show()
        end)
        card.toastStyleButton:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    if card.toastAnimationButton then
        local animationLabel = getToastAnimationLabel(toastAnimation)
        local buttonText = string.format(L.RENOWN_TOAST_ANIMATION_FORMAT or "Intro: %s", animationLabel)
        if card.toastAnimationButton.SetButtonText then
            card.toastAnimationButton:SetButtonText(buttonText, 132, 152)
        elseif card.toastAnimationButton.label then
            card.toastAnimationButton.label:SetText(buttonText)
        end
        card.toastAnimationButton:SetScript("OnClick", function()
            if ns.SetRenownToastAnimation then
                ns:SetRenownToastAnimation(cycleOptionValue(TOAST_ANIMATION_ORDER, toastAnimation))
            end
        end)
        card.toastAnimationButton:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(buttonText, GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:AddLine(L.RENOWN_TOAST_ANIMATION_TOOLTIP or "Chooses a lightweight reveal animation for Chronicle XP popups.", 0.86, 0.86, 0.82, true)
            GameTooltip:Show()
        end)
        card.toastAnimationButton:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    if card.toastSizeLabel then
        card.toastSizeLabel:SetText(formatToastScaleLabel(toastScale))
    end
    if card.toastSizeMinusButton then
        card.toastSizeMinusButton:SetScript("OnClick", function()
            if ns.AdjustRenownToastScale then
                ns:AdjustRenownToastScale(-1)
            end
        end)
        card.toastSizeMinusButton:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(L.RENOWN_TOAST_SCALE_DECREASE or "Smaller", GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:AddLine(L.RENOWN_TOAST_SCALE_TOOLTIP or "Changes the Chronicle popup size.", 0.86, 0.86, 0.82, true)
            GameTooltip:Show()
        end)
        card.toastSizeMinusButton:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end
    if card.toastSizePlusButton then
        card.toastSizePlusButton:SetScript("OnClick", function()
            if ns.AdjustRenownToastScale then
                ns:AdjustRenownToastScale(1)
            end
        end)
        card.toastSizePlusButton:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(L.RENOWN_TOAST_SCALE_INCREASE or "Bigger", GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:AddLine(L.RENOWN_TOAST_SCALE_TOOLTIP or "Changes the Chronicle popup size.", 0.86, 0.86, 0.82, true)
            GameTooltip:Show()
        end)
        card.toastSizePlusButton:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    if card.warbandBadgeToggle then
        local enabled = warbandBadgeVisible
        if card.warbandBadgeToggle.SetButtonText then
            card.warbandBadgeToggle:SetButtonText(enabled and (L.RENOWN_WARBAND_BADGE_SHOWN or "Warband: On") or (L.RENOWN_WARBAND_BADGE_HIDDEN or "Warband: Off"), 126, 144)
        elseif card.warbandBadgeToggle.label then
            card.warbandBadgeToggle.label:SetText(enabled and (L.RENOWN_WARBAND_BADGE_SHOWN or "Warband: On") or (L.RENOWN_WARBAND_BADGE_HIDDEN or "Warband: Off"))
        end
        card.warbandBadgeToggle:SetScript("OnClick", function()
            if ns.SetRenownWarbandBadgeVisible then
                ns:SetRenownWarbandBadgeVisible(not enabled)
            end
        end)
        card.warbandBadgeToggle:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_CURSOR_RIGHT")
            GameTooltip:AddLine(enabled and (L.RENOWN_WARBAND_BADGE_SHOWN or "Warband: On") or (L.RENOWN_WARBAND_BADGE_HIDDEN or "Warband: Off"), GOLD[1], GOLD[2], GOLD[3], true)
            GameTooltip:AddLine(L.RENOWN_WARBAND_BADGE_TOGGLE_TOOLTIP or "Shows or hides the Warband badge in the left character panel.", 0.86, 0.86, 0.82, true)
            GameTooltip:Show()
        end)
        card.warbandBadgeToggle:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    if card.rankPanel and card.rankPanel.title then
        local detailLabel = detailScope == "warband" and warbandScopeLabel or characterScopeLabel
        card.rankPanel.title:SetText(string.format("%s - %s", L.RENOWN_PANEL_RANKS or "Rank Path", detailLabel))
    end
    if card.historyPanel and card.historyPanel.title then
        local detailLabel = detailScope == "warband" and warbandScopeLabel or characterScopeLabel
        card.historyPanel.title:SetText(string.format("%s - %s", L.RENOWN_HISTORY_TITLE or "Recent Glory", detailLabel))
    end

    refreshRankRows(card, view, frameCharacter or character)
    refreshHistory(card, view.history)
end

RenownView.CreateCard = createCard
RenownView.RefreshCard = refreshCard
