local addonName, addonTable = ...
---@type MK
local MK = addonTable.MK
local core = addonTable
local TRANSLATE = core.TRANSLATE or function(msg) return msg end

core.CharacterTab = core.CharacterTab or {}
local CharacterTab = core.CharacterTab
local SetSecureButtonAttributes = core.SetSecureButtonAttributes or function(button, ...)
    if not (button and button.SetAttribute) then
        return false
    end

    local count = select("#", ...)
    for index = 1, count, 2 do
        local key = select(index, ...)
        if key ~= nil then
            local value = select(index + 1, ...)
            button:SetAttribute(key, value)
        end
    end
    return true
end

local CHARACTER_CARD_MIN_WIDTH = 372
local CHARACTER_CARD_GAP_Y = 14
local PLAYER_INFO_TITLE_OFFSET_Y = -20
local PLAYER_INFO_TITLE_INSET_RIGHT = 0
local PLAYER_INFO_TITLE_TO_SECTION_GAP = 16
local CURRENT_CHARACTER_CARD_HEIGHT = 100
local CURRENT_CHARACTER_CARD_GAP = 12
local CURRENT_CHARACTER_CARD_TOP_HEIGHT = 65
local CURRENT_CHARACTER_CARD_CONTENT_INSET_X = 18
local CURRENT_CHARACTER_CARD_CONTENT_INSET_TOP = 8
local CURRENT_CHARACTER_CARD_CONTROL_INSET_X = 14
local CURRENT_CHARACTER_CARD_MODEL_LEFT_INSET = 6
local CURRENT_CHARACTER_CARD_MODEL_BOTTOM_INSET = 4
local CURRENT_CHARACTER_CARD_MODEL_WIDTH = 122
local CURRENT_CHARACTER_CARD_MODEL_HEIGHT = 107
local CURRENT_CHARACTER_CARD_MODEL_TEXT_GAP = 12
local CURRENT_CHARACTER_CARD_MODEL_FRAME_LEVEL_OFFSET = 120
local CURRENT_CHARACTER_CARD_MODEL_TEXT_INSET_X = CURRENT_CHARACTER_CARD_MODEL_LEFT_INSET + CURRENT_CHARACTER_CARD_MODEL_WIDTH + CURRENT_CHARACTER_CARD_MODEL_TEXT_GAP
local CURRENT_CHARACTER_CARD_TOP_LABEL_Y = -9
local CURRENT_CHARACTER_CARD_INFO_ROW_Y = 10
local CURRENT_CHARACTER_CARD_BOTTOM_ROW_Y = 0
local CURRENT_CHARACTER_CARD_TOP_CONTENT_RIGHT_INSET = 20
local CURRENT_CHARACTER_CARD_NAME_FONT_SIZE = 13
local CURRENT_CHARACTER_CARD_RATING_FONT_SIZE = 13
local CURRENT_CHARACTER_CARD_ARMOR_FONT_SIZE = 11
local CURRENT_CHARACTER_CARD_KEY_FONT_SIZE = 11
local CURRENT_CHARACTER_CARD_ROLE_LABEL_FONT_SIZE = 12
local CURRENT_CHARACTER_CARD_ROLE_LABEL_WIDTH = 112
local CURRENT_CHARACTER_CARD_TELEPORT_WIDTH = 24
local CURRENT_CHARACTER_CARD_TELEPORT_ALIGNMENT_OFFSET_X = 2
local CURRENT_CHARACTER_CARD_TELEPORT_ICON_SIZE = 16
local CURRENT_CHARACTER_CARD_TELEPORT_ICON_OFFSET_X = 0.5
local CURRENT_CHARACTER_CARD_TELEPORT_ICON_OFFSET_Y = 0
local CURRENT_CHARACTER_CARD_TELEPORT_ICON_PRESSED_OFFSET_X = 1
local CURRENT_CHARACTER_CARD_TELEPORT_ICON_PRESSED_OFFSET_Y = -1
local CURRENT_CHARACTER_CARD_ROLE_TOGGLE_WIDTH = 36
local CURRENT_CHARACTER_CARD_ROLE_TOGGLE_HEIGHT = 20
local CURRENT_CHARACTER_CARD_ROLE_TOGGLE_GAP = 7
local CURRENT_CHARACTER_CARD_ROLE_CHECK_SIZE = 20
local CURRENT_CHARACTER_CARD_ROLE_ICON_LEFT_INSET = 21
local CURRENT_CHARACTER_CARD_ROLE_ICON_SIZE = 18
local CURRENT_CHARACTER_CARD_ROLE_ICON_OVERFLOW = 3
local CURRENT_CHARACTER_CARD_ROLE_RIGHT_INSET = 20
local CURRENT_CHARACTER_CARD_ROLE_SECTION_HEIGHT = 26
local CURRENT_CHARACTER_CARD_ROLE_SECTION_OFFSET_Y = -1
local CURRENT_CHARACTER_CARD_ROLE_ATLAS_INSET_X = 0
local CURRENT_CHARACTER_CARD_ROLE_ATLAS_SCALE = 1.10
local CURRENT_CHARACTER_CARD_ROLE_ATLAS_CONTENT_OFFSET_Y = (CURRENT_CHARACTER_CARD_ROLE_SECTION_HEIGHT * (CURRENT_CHARACTER_CARD_ROLE_ATLAS_SCALE - 1)) / 2
local CURRENT_CHARACTER_CARD_ROLE_MASK_SOURCE_HEIGHT = 80
local CURRENT_CHARACTER_CARD_ROLE_MASK_SKIP_TOP = 8
local CURRENT_CHARACTER_CARD_ROLE_BAR_ATLAS = "UI-QuestTracker-Secondary-Objective-Header"
local CURRENT_CHARACTER_CARD_CONTROL_BORDER_R = 1
local CURRENT_CHARACTER_CARD_CONTROL_BORDER_G = 0.82
local CURRENT_CHARACTER_CARD_CONTROL_BORDER_B = 0
local CURRENT_CHARACTER_CARD_CONTROL_BORDER_A = 0.82
local CHARACTER_SECTION_TITLE_CARD_GAP = 14
local WARBAND_CHARACTER_CARDS_BOTTOM_GAP = 20
local WEEKLY_REPORT_TITLE_FONT_SIZE = 13
local WEEKLY_REPORT_COUNT_FONT_SIZE = 11
local WEEKLY_REPORT_BODY_FONT_SIZE = 12
local WEEKLY_REPORT_CONTENT_INSET_X = 20
local WEEKLY_REPORT_COUNT_WIDTH = 170
local WEEKLY_REPORT_TITLE_OFFSET_Y = -13
local WEEKLY_REPORT_COUNT_OFFSET_Y = -15
local WEEKLY_REPORT_VAULT_OFFSET_Y = -43
local WEEKLY_REPORT_TOP_RUNS_OFFSET_Y = -66
local WEEKLY_REPORT_BODY_HEIGHT = 18
local WEEKLY_REPORT_RUN_LEVEL_COLORS = {
    timed = "ff00ff00",
    overtime = "ffff4040",
    muted = "ff8a8a8a",
    accent = "ffffb80d",
    champion = "ff1eff00",
    hero = "ffa335ee",
    myth = "ffffd100",
}
local WEEKLY_VAULT_MYTHIC_PLUS_EVENT_TYPE_ID = 1
local WEEKLY_VAULT_DEFAULT_THRESHOLDS = { 1, 4, 8 }
local WEEKLY_VAULT_REWARD_ICON_SIZE = 12
local WEEKLY_VAULT_REWARD_ICON_READY = string.format("|A:UI-LFG-ReadyMark:%d:%d|a", WEEKLY_VAULT_REWARD_ICON_SIZE, WEEKLY_VAULT_REWARD_ICON_SIZE)
local WEEKLY_VAULT_REWARD_ICON_INCOMPLETE = string.format("|A:UI-LFG-DeclineMark:%d:%d|a", WEEKLY_VAULT_REWARD_ICON_SIZE, WEEKLY_VAULT_REWARD_ICON_SIZE)
local NATIVE_ROLE_ATLAS_BY_KEY = {
    TANK = "UI-LFG-RoleIcon-Tank",
    HEAL = "UI-LFG-RoleIcon-Healer",
    DPS = "UI-LFG-RoleIcon-DPS",
}
local ROLE_TOOLTIP_TITLE_BY_KEY = {
    TANK = "坦克",
    HEAL = "治疗",
    DPS = "输出",
}
local ROLE_TOOLTIP_DESCRIPTION_BY_KEY = {
    TANK = "表示你愿意通过使敌人攻击自己，保护队友不受攻击。",
    HEAL = "表示你愿意在队友受到伤害时为他们提供治疗。",
    DPS = "表示你愿意担当对敌人输出伤害的职责。",
}

local DungeonTeleport = core.DungeonTeleport or {}
local AddDungeonTeleportTooltipLine = DungeonTeleport.AddTeleportTooltipLine or function() end
local AttachDungeonTeleportDialogTrigger = DungeonTeleport.AttachDialogTrigger or function() end
local GetDungeonTeleportMacroText = DungeonTeleport.GetMacroText or function() return nil end
local GetDungeonTeleportStatus = DungeonTeleport.GetStatus or function() return nil end
local ApplyDungeonTeleportButtonSecureState = DungeonTeleport.ApplyButtonSecureState or function(button, canUseTeleport, teleportMacroText, mapID)
    local canUse = canUseTeleport == true and type(teleportMacroText) == "string" and teleportMacroText ~= ""
    SetSecureButtonAttributes(
        button,
        "type", canUse and "macro" or nil,
        "type1", canUse and "macro" or nil,
        "macrotext", canUse and teleportMacroText or nil,
        "macrotext1", canUse and teleportMacroText or nil,
        "spell", nil,
        "spell1", nil
    )
    if button then
        button.mkTeleportReady = canUse
        button.mkTeleportEffectiveMapID = mapID
    end
    return canUse, false, mapID
end

local GetClassColorByToken = core.GetClassColorByToken or function() return 1, 0.82, 0 end
local HideKeystoneTooltip = core.HideKeystoneTooltip or function()
    if GameTooltip then
        GameTooltip:Hide()
    end
end
local ResetCardSubFrameSkin = core.ResetCardSubFrameSkin or function() end
local UpdateCharacterCardsScrollState = core.UpdateCharacterCardsScrollState or function() end

local function TrySetAtlas(texture, atlas, useAtlasSize)
    if not texture or not atlas or not texture.SetAtlas then
        return false
    end

    local ok = pcall(texture.SetAtlas, texture, atlas, useAtlasSize)
    return ok == true
end

local function CreateSolidTexture(parent, layer, subLevel)
    local texture = parent:CreateTexture(nil, layer or "ARTWORK", nil, subLevel or 0)
    texture:SetTexture("Interface\\Buttons\\WHITE8X8")
    return texture
end

local FRAME_TEXTURE_ROOT = "Interface\\AddOns\\" .. addonName .. "\\Art\\Ui\\Frame\\"
local FRAME_SOURCE_SCALE = 2
local FRAME_SOURCE_TEXTURE_WIDTH = 344
local FRAME_SOURCE_CAP_WIDTH = 30
local FRAME_DISPLAY_CAP_WIDTH = FRAME_SOURCE_CAP_WIDTH / FRAME_SOURCE_SCALE
local FRAME_VARIANT_BROWN = "brown"
local FRAME_VARIANT_CLASS = "class"
local FRAME_VARIANT_DARK = "dark"
local WEEKLY_REPORT_HEADER_DIVIDER_INSET_X = WEEKLY_REPORT_CONTENT_INSET_X
local WEEKLY_REPORT_HEADER_DIVIDER_CENTER_OFFSET_Y = -34
local WEEKLY_REPORT_HEADER_DIVIDER_HEIGHT = 1.5
local WEEKLY_REPORT_TITLE_R = 1
local WEEKLY_REPORT_TITLE_G = 0.82
local WEEKLY_REPORT_TITLE_B = 0
local WEEKLY_REPORT_TITLE_A = 1
local WEEKLY_REPORT_DIVIDER_R = 1
local WEEKLY_REPORT_DIVIDER_G = 0.82
local WEEKLY_REPORT_DIVIDER_B = 0
local WEEKLY_REPORT_DIVIDER_CENTER_A = 0.78
local WEEKLY_REPORT_DIVIDER_EDGE_A = 0
local FRAME_TEXTURES = {
    dark = FRAME_TEXTURE_ROOT .. "frame_bg_dark_2x.png",
    brown = FRAME_TEXTURE_ROOT .. "frame_bg_brown_2x.png",
    class = FRAME_TEXTURE_ROOT .. "frame_bg_class_2x.png",
    border = FRAME_TEXTURE_ROOT .. "frame_border_base_2x.png",
    hoverBorder = FRAME_TEXTURE_ROOT .. "frame_hover_border_glow_2x.png",
    roleMask = FRAME_TEXTURE_ROOT .. "frame_role_mask.png",
}

local function SetFrameTexCoord(texture, left, right)
    if not texture then
        return
    end
    texture:SetTexCoord(left / FRAME_SOURCE_TEXTURE_WIDTH, right / FRAME_SOURCE_TEXTURE_WIDTH, 0, 1)
end

local function CreateFramePiece(parent, texturePath, layer, subLevel, left, right)
    local safeSubLevel = tonumber(subLevel) or 0
    safeSubLevel = math.max(-8, math.min(7, safeSubLevel))
    local texture = parent:CreateTexture(nil, layer or "ARTWORK", nil, safeSubLevel)
    texture:SetTexture(texturePath)
    SetFrameTexCoord(texture, left, right)
    return texture
end

local function CreateFrameStretchLayer(parent, texturePath, layer, subLevel)
    if not (parent and texturePath) then
        return nil
    end

    local sourceCap = FRAME_SOURCE_CAP_WIDTH
    local displayCap = FRAME_DISPLAY_CAP_WIDTH
    local sourceRightStart = FRAME_SOURCE_TEXTURE_WIDTH - sourceCap
    local pieces = {
        left = CreateFramePiece(parent, texturePath, layer, subLevel, 0, sourceCap),
        center = CreateFramePiece(parent, texturePath, layer, subLevel, sourceCap, sourceRightStart),
        right = CreateFramePiece(parent, texturePath, layer, subLevel, sourceRightStart, FRAME_SOURCE_TEXTURE_WIDTH),
    }

    pieces.left:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
    pieces.left:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 0, 0)
    pieces.left:SetWidth(displayCap)
    pieces.right:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, 0)
    pieces.right:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)
    pieces.right:SetWidth(displayCap)
    pieces.center:SetPoint("TOPLEFT", pieces.left, "TOPRIGHT", 0, 0)
    pieces.center:SetPoint("BOTTOMRIGHT", pieces.right, "BOTTOMLEFT", 0, 0)

    return pieces
end

local function SetFrameLayerAlpha(layer, alpha)
    if not layer then
        return
    end
    for _, texture in pairs(layer) do
        texture:SetAlpha(alpha or 1)
    end
end

local function SetFrameLayerBlendMode(layer, blendMode)
    if not layer then
        return
    end
    for _, texture in pairs(layer) do
        texture:SetBlendMode(blendMode or "BLEND")
    end
end

local function SetFrameLayerVertexColor(layer, r, g, b, a)
    if not layer then
        return
    end
    for _, texture in pairs(layer) do
        texture:SetVertexColor(r or 1, g or 1, b or 1, a or 1)
    end
end

local function WrapColor(text, color)
    return string.format("|c%s%s|r", color or "ffffffff", tostring(text or ""))
end

local function ApplyHorizontalColorGradient(texture, startR, startG, startB, startA, endR, endG, endB, endA)
    if not texture then
        return
    end

    texture:SetTexture("Interface\\Buttons\\WHITE8X8")
    if texture.SetGradient and CreateColor then
        local ok = pcall(
            texture.SetGradient,
            texture,
            "HORIZONTAL",
            CreateColor(startR, startG, startB, startA),
            CreateColor(endR, endG, endB, endA)
        )
        if ok then
            return
        end
    end

    if texture.SetGradientAlpha then
        local ok = pcall(texture.SetGradientAlpha, texture, "HORIZONTAL", startR, startG, startB, startA, endR, endG, endB, endA)
        if ok then
            return
        end
    end

    texture:SetVertexColor(endR, endG, endB, math.max(startA or 0, endA or 0))
end

local function CopyDefaultWeeklyVaultThresholds()
    local thresholds = {}
    for index, threshold in ipairs(WEEKLY_VAULT_DEFAULT_THRESHOLDS) do
        thresholds[index] = threshold
    end
    return thresholds
end

local function GetWeeklyVaultThresholds()
    if C_WeeklyRewards and C_WeeklyRewards.GetActivities then
        local ok, activities = pcall(C_WeeklyRewards.GetActivities, WEEKLY_VAULT_MYTHIC_PLUS_EVENT_TYPE_ID)
        if ok and type(activities) == "table" then
            local thresholds = {}
            local seen = {}
            for _, activityInfo in ipairs(activities) do
                local threshold = tonumber(activityInfo and activityInfo.threshold)
                if threshold and threshold > 0 and not seen[threshold] then
                    seen[threshold] = true
                    thresholds[#thresholds + 1] = threshold
                end
            end
            table.sort(thresholds)
            if #thresholds > 0 then
                return thresholds, false
            end
        end
    end

    return CopyDefaultWeeklyVaultThresholds(), true
end

local function GetWeeklyVaultRewardLabel(level)
    local rewardLevel = math.min(math.max(tonumber(level) or 0, 0), 10)
    if rewardLevel >= 10 then
        return TRANSLATE("神话")
    end
    if rewardLevel >= 3 then
        return TRANSLATE("英雄")
    end
    if rewardLevel >= 2 then
        return TRANSLATE("勇士")
    end
    return ""
end

local function GetWeeklyVaultRewardColor(level)
    local rewardLevel = math.min(math.max(tonumber(level) or 0, 0), 10)
    if rewardLevel >= 10 then
        return WEEKLY_REPORT_RUN_LEVEL_COLORS.myth
    end
    if rewardLevel >= 3 then
        return WEEKLY_REPORT_RUN_LEVEL_COLORS.hero
    end
    if rewardLevel >= 2 then
        return WEEKLY_REPORT_RUN_LEVEL_COLORS.champion
    end
    return WEEKLY_REPORT_RUN_LEVEL_COLORS.accent
end

local function GetWeeklyVaultThresholdLabel(threshold)
    threshold = tonumber(threshold) or 0
    if threshold == 1 then
        return TRANSLATE("初级奖励")
    end
    if threshold == 4 then
        return TRANSLATE("四本奖励")
    end
    if threshold == 8 then
        return TRANSLATE("八本奖励")
    end
    return string.format(TRANSLATE("%d 本奖励"), threshold)
end

local function FormatWeeklyRunLevel(run)
    if type(run) ~= "table" then
        return WrapColor("-", WEEKLY_REPORT_RUN_LEVEL_COLORS.muted)
    end

    local color = run.overtime and WEEKLY_REPORT_RUN_LEVEL_COLORS.overtime or WEEKLY_REPORT_RUN_LEVEL_COLORS.timed
    return WrapColor("+" .. tostring(tonumber(run.level) or 0), color)
end

local function FormatWeeklyVaultRewardPart(label, run)
    local labelText = WrapColor(label .. "：", WEEKLY_REPORT_RUN_LEVEL_COLORS.accent)
    if type(run) ~= "table" then
        return string.format("%s %s%s", WEEKLY_VAULT_REWARD_ICON_INCOMPLETE, labelText, WrapColor(TRANSLATE("未完成"), WEEKLY_REPORT_RUN_LEVEL_COLORS.muted))
    end

    local rewardLabel = GetWeeklyVaultRewardLabel(run.level)
    if rewardLabel == "" then
        return string.format("%s %s%s", WEEKLY_VAULT_REWARD_ICON_INCOMPLETE, labelText, WrapColor(TRANSLATE("未完成"), WEEKLY_REPORT_RUN_LEVEL_COLORS.muted))
    end

    return string.format("%s %s%s", WEEKLY_VAULT_REWARD_ICON_READY, labelText, WrapColor(rewardLabel, GetWeeklyVaultRewardColor(run.level)))
end

local function FormatWeeklyVaultRewardSummary(weeklyReport)
    local thresholds = weeklyReport and weeklyReport.vaultThresholds or WEEKLY_VAULT_DEFAULT_THRESHOLDS
    local vaultParts = {}
    for _, threshold in ipairs(thresholds) do
        vaultParts[#vaultParts + 1] = FormatWeeklyVaultRewardPart(GetWeeklyVaultThresholdLabel(threshold), weeklyReport.topRuns[threshold])
    end
    return string.format("%s  %s", TRANSLATE("宏伟宝库"), table.concat(vaultParts, "  "))
end

local function FormatWeeklyVaultTooltipReward(run)
    if type(run) ~= "table" then
        return WrapColor(TRANSLATE("未完成"), WEEKLY_REPORT_RUN_LEVEL_COLORS.muted)
    end

    local rewardLabel = GetWeeklyVaultRewardLabel(run.level)
    if rewardLabel == "" then
        return string.format("%s  %s", FormatWeeklyRunLevel(run), WrapColor(TRANSLATE("未完成"), WEEKLY_REPORT_RUN_LEVEL_COLORS.muted))
    end
    return string.format("%s  %s", FormatWeeklyRunLevel(run), WrapColor(rewardLabel, GetWeeklyVaultRewardColor(run.level)))
end

local function AddWeeklyVaultTooltipLines(report)
    local thresholds = report and report.vaultThresholds or WEEKLY_VAULT_DEFAULT_THRESHOLDS
    if type(thresholds) ~= "table" or #thresholds == 0 then
        thresholds = WEEKLY_VAULT_DEFAULT_THRESHOLDS
    end

    GameTooltip:AddLine(" ")
    GameTooltip:AddLine(TRANSLATE("宏伟宝库"), 1, 0.82, 0)
    for _, threshold in ipairs(thresholds) do
        local completedCount = math.min(tonumber(report.totalCount) or 0, threshold)
        GameTooltip:AddDoubleLine(
            string.format("%s (%d/%d)", GetWeeklyVaultThresholdLabel(threshold), completedCount, threshold),
            FormatWeeklyVaultTooltipReward(report.topRuns and report.topRuns[threshold]),
            0.82, 0.82, 0.82,
            1, 1, 1
        )
    end
end

local function BuildCurrentCharacterWeeklyReport(owner)
    local vaultThresholds, vaultThresholdsFallback = GetWeeklyVaultThresholds()
    local report = {
        topRuns = {},
        dungeonGroups = {},
        totalCount = 0,
        unavailable = false,
        vaultThresholds = vaultThresholds,
        vaultThresholdsFallback = vaultThresholdsFallback,
    }

    if not C_MythicPlus or not C_MythicPlus.GetRunHistory then
        report.unavailable = true
        return report
    end

    local ok, runHistory = pcall(C_MythicPlus.GetRunHistory, false, true)
    if not ok or type(runHistory) ~= "table" then
        report.unavailable = true
        return report
    end

    local sortedRuns = {}
    local groupsByMapID = {}
    for _, run in ipairs(runHistory) do
        local mapID = tonumber(run.mapChallengeModeID)
        local level = tonumber(run.level) or 0
        if mapID and level > 0 then
            local runData = {
                mapID = mapID,
                level = level,
                overtime = run.completed ~= true,
            }
            sortedRuns[#sortedRuns + 1] = runData
            report.totalCount = report.totalCount + 1

            local dungeonGroup = groupsByMapID[mapID]
            if not dungeonGroup then
                dungeonGroup = {
                    mapID = mapID,
                    dungeonName = owner and owner.GetAbbreviatedDungeonName and owner:GetAbbreviatedDungeonName(mapID) or tostring(mapID),
                    runs = {},
                }
                groupsByMapID[mapID] = dungeonGroup
                report.dungeonGroups[#report.dungeonGroups + 1] = dungeonGroup
            end
            dungeonGroup.runs[#dungeonGroup.runs + 1] = runData
        end
    end

    table.sort(sortedRuns, function(left, right)
        if left.level ~= right.level then
            return left.level > right.level
        end
        if left.overtime ~= right.overtime then
            return right.overtime
        end
        return left.mapID < right.mapID
    end)

    local maxVaultThreshold = 0
    for _, threshold in ipairs(vaultThresholds) do
        maxVaultThreshold = math.max(maxVaultThreshold, tonumber(threshold) or 0)
    end
    maxVaultThreshold = math.max(maxVaultThreshold, WEEKLY_VAULT_DEFAULT_THRESHOLDS[#WEEKLY_VAULT_DEFAULT_THRESHOLDS])

    for index = 1, math.min(maxVaultThreshold, #sortedRuns) do
        report.topRuns[index] = sortedRuns[index]
    end

    for _, dungeonGroup in ipairs(report.dungeonGroups) do
        table.sort(dungeonGroup.runs, function(left, right)
            if left.level ~= right.level then
                return left.level > right.level
            end
            return not left.overtime and right.overtime
        end)
    end
    table.sort(report.dungeonGroups, function(left, right)
        return (left.dungeonName or "") < (right.dungeonName or "")
    end)

    return report
end

local function CreateRoundedInsetBackdrop(parent)
    local frameParent = parent and parent:GetParent() or parent
    local frame = CreateFrame("Frame", nil, frameParent, "BackdropTemplate")
    frame:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
    frame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)
    frame:EnableMouse(false)
    if frame.SetFrameLevel and parent.GetFrameLevel then
        frame:SetFrameLevel(math.max((parent:GetFrameLevel() or 1) - 1, 0))
    end
    if frame.SetBackdrop then
        frame:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = false,
            edgeSize = 10,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        frame:SetBackdropColor(0, 0, 0, 0.78)
        frame:SetBackdropBorderColor(1, 1, 1, 0.16)
    end
    parent:HookScript("OnShow", function()
        frame:Show()
    end)
    parent:HookScript("OnHide", function()
        frame:Hide()
    end)
    return frame
end

local function ApplyCurrentCharacterControlBorder(backdrop)
    if backdrop and backdrop.SetBackdropBorderColor then
        backdrop:SetBackdropBorderColor(
            CURRENT_CHARACTER_CARD_CONTROL_BORDER_R,
            CURRENT_CHARACTER_CARD_CONTROL_BORDER_G,
            CURRENT_CHARACTER_CARD_CONTROL_BORDER_B,
            CURRENT_CHARACTER_CARD_CONTROL_BORDER_A
        )
    end
end

local function CreateFrameChrome(frame)
    if not frame or frame.mkFrameChrome then
        return frame and frame.mkFrameChrome
    end

    local chrome = {}
    chrome.dark = CreateFrameStretchLayer(frame, FRAME_TEXTURES.dark, "BACKGROUND", -6)
    chrome.brown = CreateFrameStretchLayer(frame, FRAME_TEXTURES.brown, "BACKGROUND", -5)
    chrome.class = CreateFrameStretchLayer(frame, FRAME_TEXTURES.class, "BACKGROUND", -4)
    chrome.border = CreateFrameStretchLayer(frame, FRAME_TEXTURES.border, "BORDER", 1)

    frame.mkFrameChrome = chrome
    return chrome
end

local function ApplyFrameChrome(frame, variant, classR, classG, classB)
    local chrome = frame and frame.mkFrameChrome
    if not chrome then
        return
    end

    variant = variant or frame.mkFrameVariant or FRAME_VARIANT_DARK
    frame.mkFrameVariant = variant

    local isBrown = variant == FRAME_VARIANT_BROWN
    local isClass = variant == FRAME_VARIANT_CLASS
    local isEmpty = variant == "empty"
    SetFrameLayerVertexColor(chrome.dark, 1, 1, 1, 1)
    SetFrameLayerVertexColor(chrome.brown, 1, 1, 1, 1)
    SetFrameLayerVertexColor(chrome.class, classR or 1, classG or 0.82, classB or 0, 1)
    SetFrameLayerAlpha(chrome.brown, isBrown and 1 or 0)
    SetFrameLayerAlpha(chrome.class, isClass and 1 or 0)
    SetFrameLayerAlpha(chrome.dark, ((isBrown or isClass) and not isEmpty) and 0 or (isEmpty and 0.70 or 1))
    SetFrameLayerAlpha(chrome.border, isEmpty and 0.45 or 1)
end

local function CreateCharacterFrameChrome(frame, variant)
    local chrome = CreateFrameChrome(frame)
    if chrome then
        ApplyFrameChrome(frame, variant or FRAME_VARIANT_DARK)
    end
    return chrome
end

local function ApplyCharacterFrameChrome(frame, r, g, b, variant)
    ApplyFrameChrome(frame, variant or FRAME_VARIANT_DARK, r, g, b)
end

local function CreateWeeklyReportChrome(frame)
    if not frame or frame.mkWeeklyReportChrome then
        return frame and frame.mkWeeklyReportChrome
    end

    local chrome = CreateFrameChrome(frame)

    local divider = CreateFrame("Frame", nil, frame)
    divider:SetPoint("LEFT", frame, "TOPLEFT", WEEKLY_REPORT_HEADER_DIVIDER_INSET_X, WEEKLY_REPORT_HEADER_DIVIDER_CENTER_OFFSET_Y)
    divider:SetPoint("RIGHT", frame, "TOPRIGHT", -WEEKLY_REPORT_HEADER_DIVIDER_INSET_X, WEEKLY_REPORT_HEADER_DIVIDER_CENTER_OFFSET_Y)
    divider:SetHeight(WEEKLY_REPORT_HEADER_DIVIDER_HEIGHT)
    divider:SetFrameLevel((frame:GetFrameLevel() or 0) + 1)

    chrome.headerDivider = divider
    chrome.headerDividerLeft = divider:CreateTexture(nil, "ARTWORK", nil, 1)
    chrome.headerDividerLeft:SetPoint("LEFT", divider, "LEFT", 0, 0)
    chrome.headerDividerLeft:SetPoint("RIGHT", divider, "CENTER", 0, 0)
    chrome.headerDividerLeft:SetHeight(WEEKLY_REPORT_HEADER_DIVIDER_HEIGHT)
    chrome.headerDividerLeft:SetBlendMode("BLEND")

    chrome.headerDividerRight = divider:CreateTexture(nil, "ARTWORK", nil, 1)
    chrome.headerDividerRight:SetPoint("LEFT", divider, "CENTER", 0, 0)
    chrome.headerDividerRight:SetPoint("RIGHT", divider, "RIGHT", 0, 0)
    chrome.headerDividerRight:SetHeight(WEEKLY_REPORT_HEADER_DIVIDER_HEIGHT)
    chrome.headerDividerRight:SetBlendMode("BLEND")

    frame.mkWeeklyReportChrome = chrome
    ApplyFrameChrome(frame, FRAME_VARIANT_BROWN)
    return chrome
end

local function ApplyWeeklyReportChrome(frame)
    local chrome = frame and frame.mkWeeklyReportChrome
    if not chrome then
        return
    end

    ApplyFrameChrome(frame, FRAME_VARIANT_BROWN)
    if chrome.headerDivider then
        chrome.headerDivider:SetAlpha(1)
    end
    ApplyHorizontalColorGradient(
        chrome.headerDividerLeft,
        WEEKLY_REPORT_DIVIDER_R, WEEKLY_REPORT_DIVIDER_G, WEEKLY_REPORT_DIVIDER_B, WEEKLY_REPORT_DIVIDER_EDGE_A,
        WEEKLY_REPORT_DIVIDER_R, WEEKLY_REPORT_DIVIDER_G, WEEKLY_REPORT_DIVIDER_B, WEEKLY_REPORT_DIVIDER_CENTER_A
    )
    ApplyHorizontalColorGradient(
        chrome.headerDividerRight,
        WEEKLY_REPORT_DIVIDER_R, WEEKLY_REPORT_DIVIDER_G, WEEKLY_REPORT_DIVIDER_B, WEEKLY_REPORT_DIVIDER_CENTER_A,
        WEEKLY_REPORT_DIVIDER_R, WEEKLY_REPORT_DIVIDER_G, WEEKLY_REPORT_DIVIDER_B, WEEKLY_REPORT_DIVIDER_EDGE_A
    )
end

local function ShowRaisedCardHoverHighlight(card)
    if card and card.hoverHighlightFrame then
        card.hoverHighlightFrame:Show()
    end
end

local function HideRaisedCardHoverHighlight(card)
    if not (card and card.hoverHighlightFrame) then
        return
    end

    if card.IsMouseOver and card:IsMouseOver() then
        return
    end
    card.hoverHighlightFrame:Hide()
end

local function RelayCharacterCardMouseWheel(_card, delta)
    local widgets = MK and MK.widgets
    local scrollFrame = widgets and widgets.cardsScrollFrame
    local onMouseWheel = scrollFrame and scrollFrame.GetScript and scrollFrame:GetScript("OnMouseWheel")
    if onMouseWheel then
        onMouseWheel(scrollFrame, delta)
    end
end

local function CreateCardHoverHighlight(card, raised)
    if not card then
        return nil
    end

    if card.hoverHighlightFrame then
        return card.hoverHighlightFrame
    end

    local hoverFrame = CreateFrame("Frame", nil, card)
    hoverFrame.owner = card
    hoverFrame:SetAllPoints(card)
    hoverFrame:EnableMouse(false)
    if hoverFrame.SetFrameLevel and card.GetFrameLevel then
        hoverFrame:SetFrameLevel((card:GetFrameLevel() or 0) + (raised and 80 or 30))
    end
    hoverFrame:SetScript("OnUpdate", function(frame)
        local owner = frame.owner
        if owner and owner.IsMouseOver and owner:IsMouseOver() then
            return
        end
        frame:Hide()
    end)
    hoverFrame:Hide()

    local hoverBaseBorder = CreateFrameStretchLayer(hoverFrame, FRAME_TEXTURES.border, "OVERLAY", 6)
    local hoverBorder = CreateFrameStretchLayer(hoverFrame, FRAME_TEXTURES.hoverBorder, "OVERLAY", 7)
    SetFrameLayerBlendMode(hoverBorder, "ADD")
    SetFrameLayerAlpha(hoverBaseBorder, 1)
    SetFrameLayerAlpha(hoverBorder, 0.74)

    card.hoverHighlightFrame = hoverFrame
    card.mkFrameHoverBaseBorder = hoverBaseBorder
    card.mkFrameHoverBorder = hoverBorder
    card.highlightTexture = nil
    card:HookScript("OnEnter", ShowRaisedCardHoverHighlight)
    card:HookScript("OnLeave", HideRaisedCardHoverHighlight)
    return hoverFrame
end

local function SetCardHoverHighlightColor(card, r, g, b, a)
    if card and card.mkFrameHoverBorder then
        SetFrameLayerVertexColor(card.mkFrameHoverBaseBorder, 1, 1, 1, 1)
        SetFrameLayerVertexColor(card.mkFrameHoverBorder, r or 1, g or 0.82, b or 0, 1)
        SetFrameLayerAlpha(card.mkFrameHoverBaseBorder, 1)
        SetFrameLayerAlpha(card.mkFrameHoverBorder, math.min(a or 0.74, 0.74))
    elseif card and card.highlightTexture then
        card.highlightTexture:SetVertexColor(r or 1, g or 0.82, b or 0, a or 0.95)
    end
end

local function GetCurrentCharacterKey()
    local name = UnitName("player") or "Unknown"
    local realm = GetRealmName() or "Unknown"
    return string.format("%s-%s", name, realm)
end

local function GetMaxCharacterLevel()
    if GetMaxPlayerLevel then
        local ok, maxLevel = pcall(GetMaxPlayerLevel)
        maxLevel = tonumber(maxLevel)
        if ok and maxLevel and maxLevel > 0 then
            return maxLevel
        end
    end

    local globalMaxLevel = tonumber(_G.MAX_PLAYER_LEVEL)
    if globalMaxLevel and globalMaxLevel > 0 then
        return globalMaxLevel
    end

    if type(_G.MAX_PLAYER_LEVEL_TABLE) == "table" then
        local maxLevel = 0
        for _, level in pairs(_G.MAX_PLAYER_LEVEL_TABLE) do
            level = tonumber(level)
            if level and level > maxLevel then
                maxLevel = level
            end
        end
        if maxLevel > 0 then
            return maxLevel
        end
    end

    return nil
end

local function CharacterHasAnyKeystoneData(character)
    return character and (
        (tonumber(character.keyLevel) and tonumber(character.keyLevel) > 0)
        or character.keystoneLink
        or character.hasKeystone
    )
end

local function IsWarbandMaxLevelCharacter(character)
    if not character then
        return false
    end

    local level = tonumber(character.level)
    local maxLevel = GetMaxCharacterLevel()
    if level and maxLevel then
        return level >= maxLevel
    end

    -- Older records did not store level; keep known keystone/rating records visible until they refresh with level.
    return level == nil and (CharacterHasAnyKeystoneData(character) or (tonumber(character.rating) or 0) > 0)
end

local function GetCharacterCardKeystoneText(owner, character)
    if character and character.keystoneLink then
        return character.keystoneLink
    end
    if CharacterHasAnyKeystoneData(character) then
        return owner:GetCharacterKeystoneText(character)
    end
    return TRANSLATE("暂无信息")
end

local function GetCurrentCharacterEntry(owner)
    local key = GetCurrentCharacterKey()
    local character = owner and owner.GetCharacterDataByKey and owner:GetCharacterDataByKey(key) or nil
    if character then
        return {
            key = key,
            data = character,
        }
    end

    return {
        key = key,
        data = {
            name = key,
            class = select(2, UnitClass("player")),
            realm = GetRealmName(),
            level = UnitLevel and UnitLevel("player") or nil,
        },
    }
end

local function GetPlainCurrentCharacterName(character, characterKey)
    local name = character and character.name or characterKey or (UnitName("player") or TRANSLATE("未知"))
    if type(name) ~= "string" or name == "" then
        name = UnitName("player") or TRANSLATE("未知")
    end

    if name:find("-", 1, true) then
        return name
    end

    local realm = character and character.realm or GetRealmName()
    if type(realm) == "string" and realm ~= "" then
        return string.format("%s-%s", name, realm)
    end

    return name
end

local function GetCurrentSpecializationRoleKey()
    if not GetSpecialization or not GetSpecializationRole then
        return "DPS"
    end

    local specializationIndex = GetSpecialization()
    local specializationRole = specializationIndex and GetSpecializationRole(specializationIndex)
    if specializationRole == "TANK" then
        return "TANK"
    end
    if specializationRole == "HEALER" then
        return "HEAL"
    end

    return "DPS"
end

local function HasAnyCurrentCharacterRole(roles, owner)
    if type(roles) ~= "table" then
        return false
    end
    local roleOrder = owner and owner.GetRoleOrder and owner:GetRoleOrder() or { "TANK", "HEAL", "DPS" }
    for _, roleKey in ipairs(roleOrder) do
        if roles[roleKey] then
            return true
        end
    end
    return false
end

local function AreCurrentCharacterRoleTablesEqual(left, right, owner)
    local roleOrder = owner and owner.GetRoleOrder and owner:GetRoleOrder() or { "TANK", "HEAL", "DPS" }
    for _, roleKey in ipairs(roleOrder) do
        if (left and left[roleKey] == true or false) ~= (right and right[roleKey] == true or false) then
            return false
        end
    end
    return true
end

local function CopyCurrentCharacterRoles(roles, owner)
    local copiedRoles = {}
    local roleOrder = owner and owner.GetRoleOrder and owner:GetRoleOrder() or { "TANK", "HEAL", "DPS" }
    for _, roleKey in ipairs(roleOrder) do
        if roles and roles[roleKey] then
            copiedRoles[roleKey] = true
        end
    end
    return copiedRoles
end

local function GetCurrentLFGRoleSelection()
    if not GetLFGRoles then
        return nil, false, nil
    end

    local ok, leader, tank, healer, damager = pcall(GetLFGRoles)
    if not ok then
        return nil, false, nil
    end

    return {
        TANK = tank == true or nil,
        HEAL = healer == true or nil,
        DPS = damager == true or nil,
    }, true, leader == true
end

local function GetCurrentLFGAvailableRoles()
    if not (C_LFGList and C_LFGList.GetAvailableRoles) then
        return nil, false
    end

    local ok, tank, healer, damager = pcall(C_LFGList.GetAvailableRoles)
    if not ok then
        return nil, false
    end

    return {
        TANK = tank == true,
        HEAL = healer == true,
        DPS = damager == true,
    }, true
end

local function ApplyCurrentCharacterLFGRoles(owner, character, roles)
    if not (owner and character and type(roles) == "table") then
        return false
    end

    local changed = not AreCurrentCharacterRoleTablesEqual(character.roles, roles, owner)
        or character.role ~= nil
        or character.rolesManuallyConfigured ~= true

    if changed then
        character.roles = CopyCurrentCharacterRoles(roles, owner)
        character.role = nil
        character.rolesManuallyConfigured = true
    end

    return changed
end

local function NormalizeCurrentCharacterRoles(owner, character)
    local lfgRoles, hasLFGRoles = GetCurrentLFGRoleSelection()
    if hasLFGRoles then
        ApplyCurrentCharacterLFGRoles(owner, character, lfgRoles)
        return lfgRoles
    end

    local roles = character and character.roles
    if character and character.rolesManuallyConfigured and type(roles) == "table" then
        if HasAnyCurrentCharacterRole(roles, owner) then
            return roles
        end
    end

    roles = {}
    if character and type(character.role) == "string" then
        for _, roleKey in ipairs(owner:GetRoleOrder()) do
            if character.role == roleKey then
                roles[roleKey] = true
                character.roles = roles
                character.role = nil
                character.rolesManuallyConfigured = true
                return roles
            end
        end
    end

    roles[GetCurrentSpecializationRoleKey()] = true
    if character then
        character.roles = roles
        character.role = nil
    end
    return roles
end

function MK:GetCurrentLFGRoleSelection()
    return GetCurrentLFGRoleSelection()
end

function MK:GetCurrentLFGAvailableRoles()
    return GetCurrentLFGAvailableRoles()
end

function MK:SyncCurrentCharacterLFGRoles(reason, suppressRefresh)
    local roles, hasLFGRoles = GetCurrentLFGRoleSelection()
    if not hasLFGRoles then
        return false, roles, hasLFGRoles
    end

    local character = self.GetCharacterDataByKey and self:GetCharacterDataByKey(GetCurrentCharacterKey()) or nil
    if not character then
        return false, roles, hasLFGRoles
    end

    local changed = ApplyCurrentCharacterLFGRoles(self, character, roles)
    if not changed then
        return false, roles, hasLFGRoles
    end

    if self.QueueCharacterPatchBroadcast then
        self:QueueCharacterPatchBroadcast(0.2)
    end
    if self.QueueBroadcastUpdate then
        self:QueueBroadcastUpdate(0.5)
    end

    if not suppressRefresh and self.IsMainWindowShown and self:IsMainWindowShown() then
        self:RefreshMainWindow(reason or "LFG_ROLE_UPDATE")
    end

    return true, roles, hasLFGRoles
end

function MK:UpdateCurrentCharacterLFGRole(roleKey, value, characterKey)
    local currentKey = GetCurrentCharacterKey()
    if characterKey and characterKey ~= currentKey then
        return false
    end
    if not SetLFGRoles then
        return false
    end

    local roles, hasLFGRoles, leader = GetCurrentLFGRoleSelection()
    if not hasLFGRoles then
        return false
    end

    local availableRoles = GetCurrentLFGAvailableRoles()
    if value and availableRoles and availableRoles[roleKey] == false then
        return false, roles
    end

    roles = CopyCurrentCharacterRoles(roles, self)
    roles[roleKey] = value and true or nil

    local ok = pcall(
        SetLFGRoles,
        leader == true,
        roles.TANK == true,
        roles.HEAL == true,
        roles.DPS == true
    )
    if not ok then
        return false, roles
    end

    self:SyncCurrentCharacterLFGRoles("UpdateCurrentCharacterLFGRole", true)
    return true, roles
end

local function CreateCurrentCharacterKeyButton(parent)
    local keyButton = CreateFrame("Button", nil, parent)
    keyButton:SetHeight(24)
    keyButton:EnableMouse(true)
    ResetCardSubFrameSkin(keyButton)

    local keyBackdrop = CreateRoundedInsetBackdrop(keyButton)
    ApplyCurrentCharacterControlBorder(keyBackdrop)

    local keyText = keyButton:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    keyText:SetPoint("LEFT", keyButton, "LEFT", 8, 0)
    keyText:SetPoint("RIGHT", keyButton, "RIGHT", -8, 0)
    keyText:SetJustifyH("CENTER")
    if core.ApplyFont then
        core.ApplyFont(keyText, CURRENT_CHARACTER_CARD_KEY_FONT_SIZE, "")
    end

    local keyHover = keyButton:CreateTexture(nil, "HIGHLIGHT")
    keyHover:SetPoint("TOPLEFT", keyButton, "TOPLEFT", 3, -3)
    keyHover:SetPoint("BOTTOMRIGHT", keyButton, "BOTTOMRIGHT", -3, 3)
    keyHover:SetTexture("Interface\\Buttons\\WHITE8X8")
    keyHover:SetVertexColor(1, 1, 1, 0.06)

    keyButton:SetScript("OnEnter", function(button)
        local link = button.keystoneLink
        if not link then
            return
        end

        GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
        GameTooltip:SetHyperlink(link)
        GameTooltip:Show()
    end)
    keyButton:SetScript("OnLeave", function()
        HideKeystoneTooltip()
    end)
    keyButton:SetScript("OnClick", function(button, mouseButton)
        local link = button.keystoneLink
        if not link then
            return
        end

        if HandleModifiedItemClick and HandleModifiedItemClick(link) then
            return
        end

        if mouseButton == "LeftButton" and SetItemRef then
            SetItemRef(link, link, mouseButton, button)
        end
    end)

    keyButton.text = keyText
    keyButton.roundedBackdrop = keyBackdrop
    return keyButton
end

local function AnchorCurrentCharacterTeleportIcon(button, pressed)
    local icon = button and button.icon
    if not icon then
        return
    end

    local offsetX = CURRENT_CHARACTER_CARD_TELEPORT_ICON_OFFSET_X
    local offsetY = CURRENT_CHARACTER_CARD_TELEPORT_ICON_OFFSET_Y
    if pressed then
        offsetX = offsetX + CURRENT_CHARACTER_CARD_TELEPORT_ICON_PRESSED_OFFSET_X
        offsetY = offsetY + CURRENT_CHARACTER_CARD_TELEPORT_ICON_PRESSED_OFFSET_Y
    end

    icon:ClearAllPoints()
    icon:SetPoint("CENTER", button.roundedBackdrop or button, "CENTER", offsetX, offsetY)
end

local function RefreshCurrentCharacterTeleportVisual(button)
    local icon = button and button.icon
    if not icon then
        return
    end

    local mapID = button.mapID
    local teleportStatus = button.mkTeleportStatus
    local canUseTeleport = button.mkTeleportReady == true

    if not mapID then
        if icon.SetDesaturated then
            icon:SetDesaturated(true)
        end
        icon:SetVertexColor(1, 1, 1, 0.55)
        icon:SetAlpha(0.45)
    elseif canUseTeleport then
        if icon.SetDesaturated then
            icon:SetDesaturated(false)
        end
        icon:SetVertexColor(1, 1, 1, 0.92)
        icon:SetAlpha(1)
    elseif teleportStatus == "cooldown" or teleportStatus == "not_learned" then
        if icon.SetDesaturated then
            icon:SetDesaturated(true)
        end
        icon:SetVertexColor(0.45, 0.45, 0.45, teleportStatus == "cooldown" and 0.85 or 0.72)
        icon:SetAlpha(teleportStatus == "cooldown" and 0.82 or 0.72)
    else
        if icon.SetDesaturated then
            icon:SetDesaturated(true)
        end
        icon:SetVertexColor(1, 1, 1, 0.55)
        icon:SetAlpha(0.7)
    end
end

local function CreateCurrentCharacterTeleportButton(parent)
    local button = CreateFrame("Button", nil, parent, "SecureActionButtonTemplate")
    button:RegisterForClicks("AnyUp", "AnyDown")
    SetSecureButtonAttributes(button, "type", nil, "type1", nil, "macrotext", nil, "macrotext1", nil, "spell", nil, "spell1", nil)
    ResetCardSubFrameSkin(button)

    local roundedBackdrop = CreateRoundedInsetBackdrop(button)
    ApplyCurrentCharacterControlBorder(roundedBackdrop)

    local pushedTexture = button:CreateTexture(nil, "HIGHLIGHT")
    pushedTexture:SetPoint("TOPLEFT", button, "TOPLEFT", 3, -3)
    pushedTexture:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -3, 3)
    pushedTexture:SetTexture("Interface\\Buttons\\WHITE8X8")
    pushedTexture:SetVertexColor(1, 1, 1, 0.06)
    if button.SetPushedTexture then
        button:SetPushedTexture(pushedTexture)
    end

    local icon = button:CreateTexture(nil, "OVERLAY")
    icon:SetSize(CURRENT_CHARACTER_CARD_TELEPORT_ICON_SIZE, CURRENT_CHARACTER_CARD_TELEPORT_ICON_SIZE)
    if icon.SetAtlas then
        icon:SetAtlas("MagePortalAlliance", false)
    end
    icon:SetVertexColor(1, 1, 1, 0.92)

    button:SetScript("OnEnter", function(teleportButton)
        local mapID = teleportButton.mapID
        if not mapID then
            return
        end

        GameTooltip:SetOwner(teleportButton, "ANCHOR_RIGHT")
        AddDungeonTeleportTooltipLine(GameTooltip, mapID)
        GameTooltip:Show()
    end)
    button:SetScript("OnLeave", function()
        HideKeystoneTooltip()
        button.isPressed = nil
        AnchorCurrentCharacterTeleportIcon(button, false)
    end)
    button:SetScript("OnMouseDown", function()
        if not button.mkTeleportReady then
            AnchorCurrentCharacterTeleportIcon(button, false)
            return
        end
        button.isPressed = true
        AnchorCurrentCharacterTeleportIcon(button, true)
    end)
    button:SetScript("OnMouseUp", function()
        button.isPressed = nil
        AnchorCurrentCharacterTeleportIcon(button, false)
    end)
    AttachDungeonTeleportDialogTrigger(MK, button)

    button.icon = icon
    button.roundedBackdrop = roundedBackdrop
    button.pushedTexture = pushedTexture
    button.mkSecureAttributesApplied = function(teleportButton)
        teleportButton.mkTeleportReady = teleportButton.mkTeleportDesiredReady == true
        teleportButton.mapID = teleportButton.mkTeleportEffectiveMapID
        teleportButton.mkTeleportStatus = GetDungeonTeleportStatus(teleportButton.mapID)
        teleportButton.mkTeleportSecurePending = nil
        RefreshCurrentCharacterTeleportVisual(teleportButton)
    end
    AnchorCurrentCharacterTeleportIcon(button, false)
    return button
end

local function RefreshCharacterCardTeleportButton(card, mapID)
    if not (card and card.teleportButton) then
        return
    end

    local teleportMacroText = GetDungeonTeleportMacroText(mapID)
    local teleportStatus = GetDungeonTeleportStatus(mapID)
    local canUseTeleport = mapID and teleportStatus == "ready" and teleportMacroText ~= nil
    card.teleportButton.mapID = mapID
    card.teleportButton.mkTeleportStatus = teleportStatus
    local _, _, effectiveMapID = ApplyDungeonTeleportButtonSecureState(card.teleportButton, canUseTeleport, teleportMacroText, mapID)
    card.teleportButton.mapID = effectiveMapID
    if effectiveMapID ~= mapID then
        card.teleportButton.mkTeleportStatus = GetDungeonTeleportStatus(effectiveMapID)
    end
    if card.teleportButton.mkTeleportReady ~= true then
        card.teleportButton.isPressed = nil
        AnchorCurrentCharacterTeleportIcon(card.teleportButton, false)
    end
    ApplyCurrentCharacterControlBorder(card.teleportButton.roundedBackdrop)
    RefreshCurrentCharacterTeleportVisual(card.teleportButton)
end

local function SkinNativeCheckButton(button, checkSize)
    if not button then
        return
    end

    checkSize = checkSize or 22
    local textures = {
        button:GetNormalTexture(),
        button:GetPushedTexture(),
        button:GetHighlightTexture(),
        button:GetCheckedTexture(),
        button:GetDisabledCheckedTexture(),
    }

    for _, texture in ipairs(textures) do
        if texture then
            texture:ClearAllPoints()
            texture:SetPoint("LEFT", button, "LEFT", 0, 0)
            texture:SetSize(checkSize, checkSize)
        end
    end
end

local function CreateNativeCurrentCharacterCheckButton(parent, labelText, width, onClick, buttonHeight, checkSize, labelInset)
    local button = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    button:SetSize(width, buttonHeight or 22)
    button:EnableMouse(true)
    SkinNativeCheckButton(button, checkSize or 22)

    local label = button:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    label:SetPoint("LEFT", button, "LEFT", labelInset or 25, 0)
    label:SetPoint("RIGHT", button, "RIGHT", 0, 0)
    label:SetJustifyH("LEFT")
    label:SetText(labelText or "")
    label:SetTextColor(1, 1, 1, 0.92)
    if core.ApplyFont then
        core.ApplyFont(label, 12, "")
    end

    button:SetScript("OnClick", function(self)
        if self.mkLocked then
            self:SetChecked(true)
            return
        end
        if self.mkUnavailable then
            self:SetChecked(false)
            return
        end

        if onClick then
            onClick(self:GetChecked() == true)
        end
    end)

    button.SetLocked = function(self, locked)
        self.mkLocked = locked and true or false
        self:SetChecked(self.mkLocked or self:GetChecked())
        self:EnableMouse(not self.mkLocked)
        if self.label then
            self.label:SetTextColor(1, 1, 1, self.mkLocked and 0.70 or 0.92)
        end
    end

    button.SetLabelText = function(self, text)
        if self.label then
            self.label:SetText(text or "")
        end
    end

    button.SetAvailable = function(self, available)
        local isAvailable = available ~= false
        self.mkUnavailable = not isAvailable
        if self.EnableMouse then
            self:EnableMouse(not self.mkLocked)
        end
        if not isAvailable then
            self:SetChecked(false)
        end
        if self.label then
            self.label:SetTextColor(1, 1, 1, isAvailable and 0.92 or 0.35)
        end
        if self.roleIcon then
            self.roleIcon:SetAlpha(isAvailable and 1 or 0.32)
        end
    end

    button.label = label
    return button
end

local function CreateNativeRoleCheckButton(parent, roleKey, onClick)
    local button = CreateNativeCurrentCharacterCheckButton(
        parent,
        "",
        CURRENT_CHARACTER_CARD_ROLE_TOGGLE_WIDTH,
        onClick,
        CURRENT_CHARACTER_CARD_ROLE_TOGGLE_HEIGHT,
        CURRENT_CHARACTER_CARD_ROLE_CHECK_SIZE
    )
    local icon = button:CreateTexture(nil, "OVERLAY")
    icon:SetPoint("LEFT", button, "LEFT", CURRENT_CHARACTER_CARD_ROLE_ICON_LEFT_INSET, 0)
    icon:SetSize(CURRENT_CHARACTER_CARD_ROLE_ICON_SIZE, CURRENT_CHARACTER_CARD_ROLE_ICON_SIZE)
    if not TrySetAtlas(icon, NATIVE_ROLE_ATLAS_BY_KEY[roleKey], false) then
        icon:SetTexture("Interface\\LFGFrame\\UI-LFG-ICON-ROLES")
    end
    icon:Show()

    button.roleIcon = icon
    button.roleKey = roleKey
    button:SetScript("OnEnter", function(roleButton)
        GameTooltip:SetOwner(roleButton, "ANCHOR_RIGHT")
        GameTooltip:ClearLines()
        GameTooltip:AddLine(TRANSLATE(ROLE_TOOLTIP_TITLE_BY_KEY[roleKey] or ""), 1, 0.82, 0)
        GameTooltip:AddLine(TRANSLATE(ROLE_TOOLTIP_DESCRIPTION_BY_KEY[roleKey] or ""), 1, 1, 1, true)
        if roleButton.mkUnavailable then
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine(TRANSLATE("你的职业无法担任该职责。"), 1, 0.1, 0.1, true)
        end
        GameTooltip:Show()
    end)
    button:SetScript("OnLeave", HideKeystoneTooltip)
    return button
end

local function GetCurrentCharacterCardLeftInset(card)
    if card and card.mkShowCurrentPlayerModel then
        return math.max(CURRENT_CHARACTER_CARD_CONTROL_INSET_X, CURRENT_CHARACTER_CARD_MODEL_TEXT_INSET_X - CURRENT_CHARACTER_CARD_CONTENT_INSET_X)
    end
    return CURRENT_CHARACTER_CARD_CONTROL_INSET_X
end

local function ConfigureCurrentPlayerModel(modelFrame)
    if not modelFrame then
        return
    end

    local parent = modelFrame.GetParent and modelFrame:GetParent()
    if modelFrame.SetFrameLevel then
        local parentLevel = parent and parent.GetFrameLevel and parent:GetFrameLevel() or 0
        modelFrame:SetFrameLevel(parentLevel + CURRENT_CHARACTER_CARD_MODEL_FRAME_LEVEL_OFFSET)
    end

    if modelFrame.SetUnit then
        modelFrame:SetUnit("player")
    end
    if modelFrame.SetCamera then
        modelFrame:SetCamera(0)
    end
    if modelFrame.SetPortraitZoom then
        modelFrame:SetPortraitZoom(0.46)
    end
    if modelFrame.SetPosition then
        modelFrame:SetPosition(0, 0, -0.20)
    end
    if modelFrame.SetFacing then
        modelFrame:SetFacing(0.40)
    end
    if modelFrame.RefreshCamera then
        modelFrame:RefreshCamera()
    end
end

local function CreateCurrentPlayerModel(card)
    if not card then
        return nil
    end

    local modelFrame = CreateFrame("PlayerModel", nil, card)
    modelFrame:EnableMouse(false)
    modelFrame:SetPoint("BOTTOMLEFT", card, "BOTTOMLEFT", CURRENT_CHARACTER_CARD_MODEL_LEFT_INSET, CURRENT_CHARACTER_CARD_MODEL_BOTTOM_INSET)
    modelFrame:SetSize(CURRENT_CHARACTER_CARD_MODEL_WIDTH, CURRENT_CHARACTER_CARD_MODEL_HEIGHT)
    modelFrame:SetScript("OnShow", ConfigureCurrentPlayerModel)
    ConfigureCurrentPlayerModel(modelFrame)
    modelFrame:Show()
    return modelFrame
end

local function CreateCurrentCharacterTopControls(card, topSection)
    local leftInsetX = GetCurrentCharacterCardLeftInset(card)
    local rightOffset = CURRENT_CHARACTER_CARD_CONTENT_INSET_X - CURRENT_CHARACTER_CARD_TOP_CONTENT_RIGHT_INSET

    local teleportButton = CreateCurrentCharacterTeleportButton(topSection)
    teleportButton:SetPoint("BOTTOMRIGHT", topSection, "BOTTOMRIGHT", rightOffset + CURRENT_CHARACTER_CARD_TELEPORT_ALIGNMENT_OFFSET_X, CURRENT_CHARACTER_CARD_INFO_ROW_Y)
    teleportButton:SetSize(CURRENT_CHARACTER_CARD_TELEPORT_WIDTH, 24)

    local nameLabel = topSection:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    nameLabel:SetPoint("TOPLEFT", topSection, "TOPLEFT", leftInsetX, CURRENT_CHARACTER_CARD_TOP_LABEL_Y + 1)
    nameLabel:SetJustifyH("LEFT")
    if core.ApplyFont then
        core.ApplyFont(nameLabel, CURRENT_CHARACTER_CARD_NAME_FONT_SIZE, "OUTLINE")
    end

    local armorLabel = topSection:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    armorLabel:SetPoint("TOPRIGHT", topSection, "TOPRIGHT", rightOffset, CURRENT_CHARACTER_CARD_TOP_LABEL_Y + 1)
    armorLabel:SetWidth(120)
    armorLabel:SetJustifyH("RIGHT")
    armorLabel:SetTextColor(0.86, 0.86, 0.86, 0.92)
    if core.ApplyFont then
        core.ApplyFont(armorLabel, CURRENT_CHARACTER_CARD_ARMOR_FONT_SIZE, "")
    end

    local ratingLabel = topSection:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    ratingLabel:SetPoint("LEFT", nameLabel, "RIGHT", 6, 0)
    ratingLabel:SetPoint("RIGHT", armorLabel, "LEFT", -8, 0)
    ratingLabel:SetJustifyH("LEFT")
    if core.ApplyFont then
        core.ApplyFont(ratingLabel, CURRENT_CHARACTER_CARD_RATING_FONT_SIZE, "OUTLINE")
    end

    local keyButton = CreateCurrentCharacterKeyButton(topSection)
    keyButton:SetPoint("BOTTOMLEFT", topSection, "BOTTOMLEFT", leftInsetX, CURRENT_CHARACTER_CARD_INFO_ROW_Y)
    keyButton:SetPoint("RIGHT", teleportButton, "LEFT", -10, 0)

    card.nameLabel = nameLabel
    card.ratingLabel = ratingLabel
    card.armorLabel = armorLabel
    card.keyButton = keyButton
    card.keyText = keyButton.text
    card.teleportButton = teleportButton
    card.teleportIcon = teleportButton.icon
end

local function CreateCurrentCharacterBottomControls(card, bottomSection)
    local rowOffsetY = CURRENT_CHARACTER_CARD_BOTTOM_ROW_Y + CURRENT_CHARACTER_CARD_ROLE_ATLAS_CONTENT_OFFSET_Y
    local showInGroup = CreateNativeCurrentCharacterCheckButton(bottomSection, TRANSLATE("加入车队列表"), 142, function(value)
        if card.characterKey then
            MK:UpdateCharacterSetting(card.characterKey, "showInGroup", value)
        end
    end)
    local leftInsetX = CURRENT_CHARACTER_CARD_CONTENT_INSET_X + GetCurrentCharacterCardLeftInset(card)
    showInGroup:SetPoint("LEFT", bottomSection, "LEFT", leftInsetX, rowOffsetY)

    local rolePrompt = bottomSection:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    rolePrompt:SetPoint("LEFT", bottomSection, "LEFT", leftInsetX, rowOffsetY)
    rolePrompt:SetWidth(CURRENT_CHARACTER_CARD_ROLE_LABEL_WIDTH)
    rolePrompt:SetHeight(20)
    rolePrompt:SetJustifyH("LEFT")
    rolePrompt:SetText(TRANSLATE("职责选择"))
    rolePrompt:SetTextColor(1, 1, 1, 0.96)
    if core.ApplyFont then
        core.ApplyFont(rolePrompt, CURRENT_CHARACTER_CARD_ROLE_LABEL_FONT_SIZE, "")
    end
    rolePrompt:Hide()

    local rolesAnchor = CreateFrame("Frame", nil, bottomSection)
    rolesAnchor:SetPoint("RIGHT", bottomSection, "RIGHT", -CURRENT_CHARACTER_CARD_ROLE_RIGHT_INSET, rowOffsetY)
    rolesAnchor:SetSize(
        (CURRENT_CHARACTER_CARD_ROLE_TOGGLE_WIDTH * 3) + (CURRENT_CHARACTER_CARD_ROLE_TOGGLE_GAP * 2) + CURRENT_CHARACTER_CARD_ROLE_ICON_OVERFLOW,
        CURRENT_CHARACTER_CARD_ROLE_TOGGLE_HEIGHT
    )

    local roleToggles = {}
    local previousToggle = nil
    for _, roleKey in ipairs(MK:GetRoleOrder()) do
        local toggle = CreateNativeRoleCheckButton(bottomSection, roleKey, function(value)
            if card.characterKey then
                local updatedRoles
                if card.mkUseLFGRoles and MK.UpdateCurrentCharacterLFGRole then
                    local _, lfgRoles = MK:UpdateCurrentCharacterLFGRole(roleKey, value, card.characterKey)
                    updatedRoles = lfgRoles or select(1, GetCurrentLFGRoleSelection())
                else
                    local _, savedRoles = MK:UpdateCharacterRole(card.characterKey, roleKey, value)
                    updatedRoles = savedRoles
                    if not updatedRoles then
                        local updatedCharacter = MK.GetCharacterDataByKey and MK:GetCharacterDataByKey(card.characterKey) or nil
                        updatedRoles = MK:NormalizeRoles(updatedCharacter)
                    end
                end
                updatedRoles = updatedRoles or {}
                for _, syncRoleKey in ipairs(MK:GetRoleOrder()) do
                    card.roles[syncRoleKey]:SetChecked(updatedRoles[syncRoleKey] or false)
                end
            end
        end)
        if previousToggle then
            toggle:SetPoint("LEFT", previousToggle, "RIGHT", CURRENT_CHARACTER_CARD_ROLE_TOGGLE_GAP, 0)
        else
            toggle:SetPoint("LEFT", rolesAnchor, "LEFT", 0, 0)
        end
        previousToggle = toggle
        roleToggles[roleKey] = toggle
    end

    card.showInGroupToggle = showInGroup
    card.rolePromptLabel = rolePrompt
    card.roles = roleToggles
end

local function LayoutCurrentCharacterRoleAtlas(card)
    local bottomBg = card and card.bottomBg
    local bottomSection = card and card.bottomSection
    if not (bottomBg and bottomSection) then
        return
    end

    local cardWidth = card.GetWidth and card:GetWidth() or 0
    if cardWidth <= 0 then
        cardWidth = CHARACTER_CARD_MIN_WIDTH
    end

    local extraWidth = math.floor((cardWidth * (CURRENT_CHARACTER_CARD_ROLE_ATLAS_SCALE - 1)) / 2 + 0.5)
    local height = CURRENT_CHARACTER_CARD_ROLE_SECTION_HEIGHT * CURRENT_CHARACTER_CARD_ROLE_ATLAS_SCALE

    bottomBg:ClearAllPoints()
    bottomBg:SetPoint("BOTTOMLEFT", bottomSection, "BOTTOMLEFT", CURRENT_CHARACTER_CARD_ROLE_ATLAS_INSET_X - extraWidth, 0)
    bottomBg:SetPoint("BOTTOMRIGHT", bottomSection, "BOTTOMRIGHT", -CURRENT_CHARACTER_CARD_ROLE_ATLAS_INSET_X + extraWidth, 0)
    bottomBg:SetHeight(height)
end

local function CreateCurrentCharacterCardSections(card)
    local insetX = CURRENT_CHARACTER_CARD_CONTENT_INSET_X
    local insetTop = CURRENT_CHARACTER_CARD_CONTENT_INSET_TOP

    local topSection = CreateFrame("Frame", nil, card)
    topSection:SetPoint("TOPLEFT", card, "TOPLEFT", insetX, -insetTop)
    topSection:SetPoint("TOPRIGHT", card, "TOPRIGHT", -insetX, -insetTop)
    topSection:SetHeight(CURRENT_CHARACTER_CARD_TOP_HEIGHT)

    local topBg = CreateSolidTexture(topSection, "BACKGROUND", -1)
    topBg:SetAllPoints(topSection)
    topBg:SetVertexColor(0, 0, 0, 0)

    local bottomSection = CreateFrame("Frame", nil, card)
    bottomSection:SetPoint("LEFT", card, "LEFT", 0, 0)
    bottomSection:SetPoint("RIGHT", card, "RIGHT", 0, 0)
    bottomSection:SetPoint("BOTTOM", card, "BOTTOM", 0, CURRENT_CHARACTER_CARD_ROLE_SECTION_OFFSET_Y)
    bottomSection:SetHeight(CURRENT_CHARACTER_CARD_ROLE_SECTION_HEIGHT)

    local bottomBg = bottomSection:CreateTexture(nil, "BACKGROUND", nil, -1)
    if not TrySetAtlas(bottomBg, CURRENT_CHARACTER_CARD_ROLE_BAR_ATLAS, false) then
        bottomBg:SetTexture(FRAME_TEXTURES.roleMask)
        bottomBg:SetTexCoord(0, 1, CURRENT_CHARACTER_CARD_ROLE_MASK_SKIP_TOP / CURRENT_CHARACTER_CARD_ROLE_MASK_SOURCE_HEIGHT, 1)
    end
    bottomBg:SetVertexColor(1, 1, 1, 1)

    CreateCurrentCharacterTopControls(card, topSection)
    CreateCurrentCharacterBottomControls(card, bottomSection)

    card.topSection = topSection
    card.topBg = topBg
    card.bottomSection = bottomSection
    card.bottomBg = bottomBg
    LayoutCurrentCharacterRoleAtlas(card)
    card:HookScript("OnSizeChanged", LayoutCurrentCharacterRoleAtlas)
end

local function ShowMythicStatsTooltip(card)
    if not card or type(card.weeklyReport) ~= "table" then
        return
    end

    local report = card.weeklyReport
    GameTooltip:SetOwner(card, "ANCHOR_RIGHT")
    GameTooltip:ClearLines()
    GameTooltip:AddLine(TRANSLATE("大秘境周报"), 1, 0.82, 0)

    if report.unavailable then
        GameTooltip:AddLine(TRANSLATE("无法读取本周大秘境记录。"), 0.70, 0.70, 0.70, true)
        GameTooltip:Show()
        return
    end

    if report.totalCount <= 0 then
        GameTooltip:AddLine(TRANSLATE("本周暂无大秘境记录。"), 0.70, 0.70, 0.70, true)
        GameTooltip:Show()
        return
    end

    GameTooltip:AddDoubleLine(TRANSLATE("本周大秘境次数"), tostring(report.totalCount), 0.82, 0.82, 0.82, 1, 1, 1)
    AddWeeklyVaultTooltipLines(report)
    GameTooltip:AddLine(" ")
    GameTooltip:AddLine(TRANSLATE("通关地下城"), 1, 0.82, 0)
    for _, dungeonGroup in ipairs(report.dungeonGroups or {}) do
        local runParts = {}
        for _, run in ipairs(dungeonGroup.runs or {}) do
            runParts[#runParts + 1] = FormatWeeklyRunLevel(run)
        end
        GameTooltip:AddDoubleLine(
            string.format("%s (%d)", dungeonGroup.dungeonName or TRANSLATE("未知地下城"), #(dungeonGroup.runs or {})),
            table.concat(runParts, " "),
            0.92, 0.92, 0.92,
            1, 1, 1
        )
    end
    GameTooltip:Show()
end

local function CreateCurrentCharacterCard(parentFrame, frameName, cardType, options)
    local isInteractiveCurrentCard = cardType == "currentCharacter" and options and options.showCurrentPlayerModel == true
    local isWarbandCharacterCard = cardType == "currentCharacter" and not isInteractiveCurrentCard
    local frameType = (cardType == "mythicStats" or isInteractiveCurrentCard) and "Button" or "Frame"
    local card = CreateFrame(frameType, frameName, parentFrame, "BackdropTemplate")
    card.cardType = cardType
    card.mkShowCurrentPlayerModel = isInteractiveCurrentCard
    card:EnableMouse(cardType == "mythicStats" or isInteractiveCurrentCard or isWarbandCharacterCard)
    if isWarbandCharacterCard and card.EnableMouseWheel then
        card:EnableMouseWheel(true)
        card:SetScript("OnMouseWheel", RelayCharacterCardMouseWheel)
    end
    if card.SetBackdrop then
        card:SetBackdrop(nil)
    end
    if cardType == "mythicStats" then
        CreateWeeklyReportChrome(card)
        ApplyWeeklyReportChrome(card)
    else
        CreateCharacterFrameChrome(card, isInteractiveCurrentCard and FRAME_VARIANT_BROWN or FRAME_VARIANT_DARK)
    end

    if cardType == "currentCharacter" then
        CreateCurrentCharacterCardSections(card)
        if card.mkShowCurrentPlayerModel then
            card.playerModelFrame = CreateCurrentPlayerModel(card)
        end
    end

    if isInteractiveCurrentCard then
        CreateCardHoverHighlight(card, true)
        if card.RegisterForClicks then
            card:RegisterForClicks("AnyUp")
        end
        card:SetScript("OnClick", function(frame, mouseButton)
            if mouseButton and mouseButton ~= "LeftButton" then
                return
            end
            if MK.ShowCurrentCharacterBestRunsPanel then
                MK:ShowCurrentCharacterBestRunsPanel(frame.characterData, frame.characterKey)
            end
        end)
    elseif isWarbandCharacterCard then
        CreateCardHoverHighlight(card)
    end

    if cardType ~= "currentCharacter" then
        local nameLabel = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        local nameInsetX = cardType == "mythicStats" and WEEKLY_REPORT_CONTENT_INSET_X or 18
        local nameOffsetY = cardType == "mythicStats" and WEEKLY_REPORT_TITLE_OFFSET_Y or -12
        local nameRightInsetX = cardType == "mythicStats" and (WEEKLY_REPORT_CONTENT_INSET_X + WEEKLY_REPORT_COUNT_WIDTH + 12) or nameInsetX
        nameLabel:SetPoint("TOPLEFT", card, "TOPLEFT", nameInsetX, nameOffsetY)
        nameLabel:SetPoint("TOPRIGHT", card, "TOPRIGHT", -nameRightInsetX, nameOffsetY)
        nameLabel:SetJustifyH("LEFT")
        nameLabel:SetText(TRANSLATE("当前角色"))
        if core.ApplyFont then
            core.ApplyFont(nameLabel, cardType == "mythicStats" and WEEKLY_REPORT_TITLE_FONT_SIZE or 14, "OUTLINE")
        end
        card.nameLabel = nameLabel

        if cardType ~= "mythicStats" then
            local headerDivider = CreateSolidTexture(card, "ARTWORK", 1)
            headerDivider:SetPoint("TOPLEFT", card, "TOPLEFT", 18, -34)
            headerDivider:SetPoint("TOPRIGHT", card, "TOPRIGHT", -18, -34)
            headerDivider:SetHeight(1)
            headerDivider:SetVertexColor(1, 0.82, 0, 0.42)
            card.headerDivider = headerDivider
        end
    end

    if cardType == "mythicStats" then
        local totalLabel = card:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        totalLabel:SetPoint("TOPRIGHT", card, "TOPRIGHT", -WEEKLY_REPORT_CONTENT_INSET_X, WEEKLY_REPORT_COUNT_OFFSET_Y)
        totalLabel:SetWidth(WEEKLY_REPORT_COUNT_WIDTH)
        totalLabel:SetHeight(18)
        totalLabel:SetJustifyH("RIGHT")
        totalLabel:SetJustifyV("TOP")
        if core.ApplyFont then
            core.ApplyFont(totalLabel, WEEKLY_REPORT_COUNT_FONT_SIZE, "")
        end
        totalLabel:SetTextColor(1, 1, 1, 0.92)

        local vaultLabel = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        vaultLabel:SetPoint("TOPLEFT", card, "TOPLEFT", WEEKLY_REPORT_CONTENT_INSET_X, WEEKLY_REPORT_VAULT_OFFSET_Y)
        vaultLabel:SetPoint("TOPRIGHT", card, "TOPRIGHT", -WEEKLY_REPORT_CONTENT_INSET_X, WEEKLY_REPORT_VAULT_OFFSET_Y)
        vaultLabel:SetHeight(WEEKLY_REPORT_BODY_HEIGHT)
        vaultLabel:SetJustifyH("LEFT")
        vaultLabel:SetJustifyV("MIDDLE")
        if vaultLabel.SetWordWrap then
            vaultLabel:SetWordWrap(false)
        end
        if vaultLabel.SetNonSpaceWrap then
            vaultLabel:SetNonSpaceWrap(false)
        end
        if core.ApplyFont then
            core.ApplyFont(vaultLabel, WEEKLY_REPORT_BODY_FONT_SIZE, "")
        end
        vaultLabel:SetTextColor(1, 1, 1, 0.92)

        local topRunsLabel = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        topRunsLabel:SetPoint("TOPLEFT", card, "TOPLEFT", WEEKLY_REPORT_CONTENT_INSET_X, WEEKLY_REPORT_TOP_RUNS_OFFSET_Y)
        topRunsLabel:SetPoint("TOPRIGHT", card, "TOPRIGHT", -WEEKLY_REPORT_CONTENT_INSET_X, WEEKLY_REPORT_TOP_RUNS_OFFSET_Y)
        topRunsLabel:SetHeight(WEEKLY_REPORT_BODY_HEIGHT)
        topRunsLabel:SetJustifyH("LEFT")
        topRunsLabel:SetJustifyV("MIDDLE")
        if topRunsLabel.SetWordWrap then
            topRunsLabel:SetWordWrap(false)
        end
        if topRunsLabel.SetNonSpaceWrap then
            topRunsLabel:SetNonSpaceWrap(false)
        end
        if core.ApplyFont then
            core.ApplyFont(topRunsLabel, WEEKLY_REPORT_BODY_FONT_SIZE, "")
        end
        topRunsLabel:SetTextColor(1, 1, 1, 0.92)

        card.weeklyTotalLabel = totalLabel
        card.weeklyVaultLabel = vaultLabel
        card.weeklyTopRunsLabel = topRunsLabel
        card:SetScript("OnEnter", ShowMythicStatsTooltip)
        card:SetScript("OnLeave", function()
            HideKeystoneTooltip()
        end)
        CreateCardHoverHighlight(card)
    end
    return card
end

function MK:CreateCurrentCharacterCardFrame(parentFrame, titleFrame)
    local container = CreateFrame("Frame", "PlayerCurrentCharacterCardArea", parentFrame)
    container:SetPoint("TOP", titleFrame, "BOTTOM", 0, -CHARACTER_SECTION_TITLE_CARD_GAP)
    container:SetPoint("LEFT", parentFrame, "LEFT", 0, 0)
    container:SetPoint("RIGHT", parentFrame, "RIGHT", 0, 0)
    container:SetHeight(CURRENT_CHARACTER_CARD_HEIGHT)
    container:SetFrameLevel((titleFrame:GetFrameLevel() or parentFrame:GetFrameLevel() or 0) + 2)

    local leftCard = CreateCurrentCharacterCard(container, "PlayerCurrentCharacterCard", "currentCharacter", { showCurrentPlayerModel = true })
    local rightCard = CreateCurrentCharacterCard(container, "PlayerCurrentMythicStatsCard", "mythicStats")
    leftCard:SetPoint("TOPLEFT", container, "TOPLEFT", 0, 0)
    rightCard:SetPoint("TOPRIGHT", container, "TOPRIGHT", 0, 0)

    container.currentCharacterCard = leftCard
    container.mythicStatsCard = rightCard
    container.leftCard = leftCard
    container.rightCard = rightCard
    self:LayoutCurrentCharacterCardFrame(container)
    return container
end

function MK:LayoutCurrentCharacterCardFrame(container)
    if not container or not container.leftCard or not container.rightCard then
        return
    end

    local availableWidth = container:GetWidth() or 0
    if availableWidth <= 0 and container:GetParent() then
        availableWidth = container:GetParent():GetWidth() or 0
    end
    if availableWidth <= 0 then
        availableWidth = 900
    end

    local cardWidth = math.max(1, math.floor((availableWidth - CURRENT_CHARACTER_CARD_GAP) / 2))
    container:SetHeight(CURRENT_CHARACTER_CARD_HEIGHT)
    container.leftCard:SetSize(cardWidth, CURRENT_CHARACTER_CARD_HEIGHT)
    container.rightCard:SetSize(cardWidth, CURRENT_CHARACTER_CARD_HEIGHT)
end

function MK:RefreshCurrentCharacterCard(container)
    if not container then
        return
    end

    self:LayoutCurrentCharacterCardFrame(container)

    local currentEntry = GetCurrentCharacterEntry(self)
    local character = currentEntry.data or {}
    character.showInGroup = true
    character.hide = false
    local roles = NormalizeCurrentCharacterRoles(self, character)
    local availableRoles = GetCurrentLFGAvailableRoles()
    local classToken = character.class or select(2, UnitClass("player"))
    local classR, classG, classB = GetClassColorByToken(classToken)

    local currentCharacterCard = container.currentCharacterCard or container.leftCard
    if currentCharacterCard then
        currentCharacterCard.characterKey = currentEntry.key
        currentCharacterCard.characterData = character
        currentCharacterCard.mkUseLFGRoles = true

        if currentCharacterCard.SetBackdropColor then
            currentCharacterCard:SetBackdropColor(0, 0, 0, 0)
        end
        if currentCharacterCard.SetBackdropBorderColor then
            currentCharacterCard:SetBackdropBorderColor(classR, classG, classB, 1)
        end
        ApplyCharacterFrameChrome(currentCharacterCard, 1, 1, 1, FRAME_VARIANT_BROWN)
        SetCardHoverHighlightColor(currentCharacterCard, 1, 1, 1, 0.95)
        if currentCharacterCard.topBg then
            currentCharacterCard.topBg:SetVertexColor(0, 0, 0, 0)
        end
        if currentCharacterCard.bottomBg then
            currentCharacterCard.bottomBg:SetVertexColor(1, 1, 1, 1)
        end
        if currentCharacterCard.nameLabel then
            currentCharacterCard.nameLabel:SetText(GetPlainCurrentCharacterName(character, currentEntry.key))
            currentCharacterCard.nameLabel:SetTextColor(classR, classG, classB, 1)
        end
        if currentCharacterCard.playerModelFrame then
            ConfigureCurrentPlayerModel(currentCharacterCard.playerModelFrame)
            currentCharacterCard.playerModelFrame:Show()
        end
        if currentCharacterCard.ratingLabel then
            local score = tonumber(character.rating)
            if score and score > 0 then
                score = math.floor(score + 0.5)
                currentCharacterCard.ratingLabel:SetText(string.format("（%d）", score))
                if self.GetDungeonScoreColor then
                    local scoreR, scoreG, scoreB = self:GetDungeonScoreColor(score)
                    currentCharacterCard.ratingLabel:SetTextColor(scoreR, scoreG, scoreB, 1)
                else
                    currentCharacterCard.ratingLabel:SetTextColor(1, 1, 1, 0.95)
                end
                currentCharacterCard.ratingLabel:Show()
            else
                currentCharacterCard.ratingLabel:SetText("")
                currentCharacterCard.ratingLabel:Hide()
            end
        end
        if currentCharacterCard.armorLabel then
            currentCharacterCard.armorLabel:SetText(self:GetArmorType(character))
        end

        if currentCharacterCard.keyButton then
            currentCharacterCard.keyButton.keystoneLink = character.keystoneLink
            ApplyCurrentCharacterControlBorder(currentCharacterCard.keyButton.roundedBackdrop)
        end
        if currentCharacterCard.keyText then
            currentCharacterCard.keyText:SetText(GetCharacterCardKeystoneText(self, character))
            currentCharacterCard.keyText:SetTextColor(classR, classG, classB, 1)
        end

        RefreshCharacterCardTeleportButton(currentCharacterCard, character.mapID)

        if currentCharacterCard.showInGroupToggle then
            currentCharacterCard.showInGroupToggle:SetChecked(true)
            if currentCharacterCard.showInGroupToggle.SetLocked then
                currentCharacterCard.showInGroupToggle:SetLocked(true)
            end
            currentCharacterCard.showInGroupToggle:Hide()
        end
        if currentCharacterCard.rolePromptLabel then
            currentCharacterCard.rolePromptLabel:SetText(TRANSLATE("职责选择"))
            currentCharacterCard.rolePromptLabel:SetTextColor(1, 1, 1, 0.96)
            currentCharacterCard.rolePromptLabel:Show()
        end
        if currentCharacterCard.roles then
            for _, roleKey in ipairs(self:GetRoleOrder()) do
                if currentCharacterCard.roles[roleKey] then
                    currentCharacterCard.roles[roleKey]:SetChecked(roles[roleKey] or false)
                    if currentCharacterCard.roles[roleKey].SetAvailable then
                        currentCharacterCard.roles[roleKey]:SetAvailable(availableRoles == nil or availableRoles[roleKey] ~= false)
                    end
                end
            end
        end
    end

    local mythicStatsCard = container.mythicStatsCard or container.rightCard
    if mythicStatsCard then
        if mythicStatsCard.SetBackdropColor then
            mythicStatsCard:SetBackdropColor(0, 0, 0, 0)
        end
        if mythicStatsCard.SetBackdropBorderColor then
            mythicStatsCard:SetBackdropBorderColor(0, 0, 0, 0)
        end
        ApplyWeeklyReportChrome(mythicStatsCard)
        if mythicStatsCard.nameLabel then
            mythicStatsCard.nameLabel:SetText(TRANSLATE("大秘境周报"))
            mythicStatsCard.nameLabel:SetTextColor(WEEKLY_REPORT_TITLE_R, WEEKLY_REPORT_TITLE_G, WEEKLY_REPORT_TITLE_B, WEEKLY_REPORT_TITLE_A)
        end

        local weeklyReport = BuildCurrentCharacterWeeklyReport(self)
        mythicStatsCard.weeklyReport = weeklyReport

        if mythicStatsCard.weeklyTotalLabel then
            if weeklyReport.unavailable then
                mythicStatsCard.weeklyTotalLabel:SetText(WrapColor("-", WEEKLY_REPORT_RUN_LEVEL_COLORS.muted))
            else
                mythicStatsCard.weeklyTotalLabel:SetText(string.format(TRANSLATE("本周通关 %d 次"), weeklyReport.totalCount))
            end
        end

        if mythicStatsCard.weeklyVaultLabel then
            if weeklyReport.unavailable then
                mythicStatsCard.weeklyVaultLabel:SetText(TRANSLATE("无法读取本周大秘境记录。"))
                mythicStatsCard.weeklyVaultLabel:SetTextColor(0.58, 0.58, 0.58, 0.95)
            else
                mythicStatsCard.weeklyVaultLabel:SetText(FormatWeeklyVaultRewardSummary(weeklyReport))
                mythicStatsCard.weeklyVaultLabel:SetTextColor(1, 1, 1, 0.95)
            end
        end

        if mythicStatsCard.weeklyTopRunsLabel then
            if weeklyReport.unavailable then
                mythicStatsCard.weeklyTopRunsLabel:SetText("")
            elseif weeklyReport.totalCount <= 0 then
                mythicStatsCard.weeklyTopRunsLabel:SetText(string.format("%s  %s", TRANSLATE("通关记录"), WrapColor(TRANSLATE("本周暂无大秘境记录。"), WEEKLY_REPORT_RUN_LEVEL_COLORS.muted)))
            else
                local topRunParts = {}
                for _, run in ipairs(weeklyReport.topRuns or {}) do
                    topRunParts[#topRunParts + 1] = FormatWeeklyRunLevel(run)
                end
                mythicStatsCard.weeklyTopRunsLabel:SetText(string.format("%s  %s", TRANSLATE("通关记录"), table.concat(topRunParts, "  ")))
            end
        end
    end
end

function MK:CreateCharacterCardFrame(parentFrame)
    local card = CreateCurrentCharacterCard(parentFrame, nil, "currentCharacter")
    card:SetSize(CHARACTER_CARD_MIN_WIDTH, CURRENT_CHARACTER_CARD_HEIGHT)
    card:Hide()
    return card
end

function MK:EnsureCharacterCards(requiredCount)
    local characterCards = self.widgets.characterCards
    local parentFrame = self.widgets.cardsContent
    if not characterCards or not parentFrame then
        return
    end

    while #characterCards < requiredCount do
        characterCards[#characterCards + 1] = self:CreateCharacterCardFrame(parentFrame)
    end

    for _, card in ipairs(characterCards) do
        if card:GetParent() ~= parentFrame then
            card:SetParent(parentFrame)
        end
    end
end

function MK:LayoutCharacterCards(cardCount)
    local widgets = self.widgets
    local rowsContainer = widgets and widgets.cardsContent
    local characterCards = widgets and widgets.characterCards or {}
    if not rowsContainer then
        return
    end

    local availableWidth = rowsContainer:GetWidth() or 0
    if availableWidth <= 0 then
        availableWidth = (self.mainWindow and self.mainWindow:GetWidth() or 1000) - 70
    end

    local cardWidth = math.max(320, math.floor((availableWidth - CURRENT_CHARACTER_CARD_GAP) / 2))
    local rowCount = math.ceil(cardCount / 2)
    local cardsHeight = rowCount > 0 and (rowCount * CURRENT_CHARACTER_CARD_HEIGHT + math.max(0, rowCount - 1) * CHARACTER_CARD_GAP_Y) or 0
    local titleBottom = math.max(0, (tonumber(widgets.warbandCharacterCardsTopOffset) or CHARACTER_SECTION_TITLE_CARD_GAP) - CHARACTER_SECTION_TITLE_CARD_GAP)
    if widgets.warbandCharactersTitle and widgets.cardsContent then
        local playerTop = widgets.cardsContent:GetTop()
        local titleBottomScreen = widgets.warbandCharactersTitle:GetBottom()
        if playerTop and titleBottomScreen then
            titleBottom = math.max(0, playerTop - titleBottomScreen)
        end
    end
    local topOffset = titleBottom + CHARACTER_SECTION_TITLE_CARD_GAP
    local emptyHeight = widgets.emptyContainer and (widgets.emptyContainer:GetHeight() or 0) or 0
    if cardCount == 0 then
        local viewHeight = widgets.cardsScrollFrame and (widgets.cardsScrollFrame:GetHeight() or 0) or 0
        emptyHeight = math.max(40, viewHeight - topOffset)
    end
    local bottomGap = cardCount > 0 and WARBAND_CHARACTER_CARDS_BOTTOM_GAP or 0
    local totalHeight = topOffset + math.max(cardsHeight + bottomGap, emptyHeight)
    rowsContainer:SetHeight(math.max(totalHeight, 1))

    if widgets.emptyContainer then
        widgets.emptyContainer:ClearAllPoints()
        widgets.emptyContainer:SetPoint("TOPLEFT", rowsContainer, "TOPLEFT", 0, -topOffset)
        widgets.emptyContainer:SetPoint("TOPRIGHT", rowsContainer, "TOPRIGHT", -8, -topOffset)
        widgets.emptyContainer:SetHeight(cardCount == 0 and emptyHeight or 40)
    end

    for index = 1, cardCount do
        local card = characterCards[index]
        local column = (index - 1) % 2
        local row = math.floor((index - 1) / 2)
        local xOffset = column * (cardWidth + CURRENT_CHARACTER_CARD_GAP)
        local yOffset = -(topOffset + (row * (CURRENT_CHARACTER_CARD_HEIGHT + CHARACTER_CARD_GAP_Y)))

        card:ClearAllPoints()
        card:SetPoint("TOPLEFT", rowsContainer, "TOPLEFT", xOffset, yOffset)
        card:SetWidth(cardWidth)
        card:SetHeight(CURRENT_CHARACTER_CARD_HEIGHT)
    end

    UpdateCharacterCardsScrollState(widgets)
end

function MK:SetCharacterCardVisible(card, visible)
    if not card then
        return
    end

    if visible then
        card:Show()
        return
    end

    card.characterKey = nil
    card.characterData = nil
    card.mkUseLFGRoles = nil
    if card.nameLabel then
        card.nameLabel:SetText("")
    end
    if card.ratingLabel then
        card.ratingLabel:SetText("")
        card.ratingLabel:Hide()
    end
    if card.armorLabel then
        card.armorLabel:SetText("")
    end
    if card.keyButton then
        card.keyButton.keystoneLink = nil
    end
    if card.keyText then
        card.keyText:SetText("")
    end
    RefreshCharacterCardTeleportButton(card, nil)
    if card.showInGroupToggle then
        card.showInGroupToggle:Show()
        if card.showInGroupToggle.SetLabelText then
            card.showInGroupToggle:SetLabelText(TRANSLATE("加入车队列表"))
        end
        card.showInGroupToggle:SetChecked(true)
        if card.showInGroupToggle.SetLocked then
            card.showInGroupToggle:SetLocked(false)
        end
    end
    if card.rolePromptLabel then
        card.rolePromptLabel:Hide()
    end
    if card.roles then
        for _, roleKey in ipairs(self:GetRoleOrder()) do
            card.roles[roleKey]:SetChecked(false)
            if card.roles[roleKey].SetAvailable then
                card.roles[roleKey]:SetAvailable(true)
            end
        end
    end
    card:Hide()
end

function MK:RefreshCharacterCard(card, characterEntry)
    local character = characterEntry.data
    local roles = self:NormalizeRoles(character)
    local roleAvailability = self.GetCharacterRoleAvailability and self:GetCharacterRoleAvailability(character) or nil
    local classR, classG, classB = GetClassColorByToken(character.class)

    card.characterKey = characterEntry.key
    card.characterData = character
    card.mkUseLFGRoles = false
    if card.SetBackdropColor then
        card:SetBackdropColor(0, 0, 0, 0)
    end
    if card.SetBackdropBorderColor then
        card:SetBackdropBorderColor(classR, classG, classB, 1)
    end
    ApplyCharacterFrameChrome(card, classR, classG, classB, FRAME_VARIANT_CLASS)
    SetCardHoverHighlightColor(card, classR, classG, classB, 0.95)
    if card.topBg then
        card.topBg:SetVertexColor(0, 0, 0, 0)
    end
    if card.bottomBg then
        card.bottomBg:SetVertexColor(1, 1, 1, 1)
    end
    if card.nameLabel then
        card.nameLabel:SetText(GetPlainCurrentCharacterName(character, characterEntry.key))
        card.nameLabel:SetTextColor(classR, classG, classB, 1)
    end
    if card.ratingLabel then
        local score = tonumber(character.rating)
        if score and score > 0 then
            score = math.floor(score + 0.5)
            card.ratingLabel:SetText(string.format("（%d）", score))
            if self.GetDungeonScoreColor then
                local scoreR, scoreG, scoreB = self:GetDungeonScoreColor(score)
                card.ratingLabel:SetTextColor(scoreR, scoreG, scoreB, 1)
            else
                card.ratingLabel:SetTextColor(1, 1, 1, 0.95)
            end
            card.ratingLabel:Show()
        else
            card.ratingLabel:SetText("")
            card.ratingLabel:Hide()
        end
    end
    if card.armorLabel then
        card.armorLabel:SetText(self:GetArmorType(character))
    end
    if card.keyButton then
        card.keyButton.keystoneLink = character.keystoneLink
        ApplyCurrentCharacterControlBorder(card.keyButton.roundedBackdrop)
    end
    if card.keyText then
        card.keyText:SetText(GetCharacterCardKeystoneText(self, character))
        card.keyText:SetTextColor(classR, classG, classB, 1)
    end
    RefreshCharacterCardTeleportButton(card, character.mapID)
    if card.showInGroupToggle then
        card.showInGroupToggle:Show()
        if card.showInGroupToggle.SetLabelText then
            card.showInGroupToggle:SetLabelText(TRANSLATE("加入车队列表"))
        end
        card.showInGroupToggle:SetChecked(self:IsCharacterShownInGroup(character))
        if card.showInGroupToggle.SetLocked then
            card.showInGroupToggle:SetLocked(false)
        end
    end
    if card.rolePromptLabel then
        card.rolePromptLabel:Hide()
    end

    if card.roles then
        for _, roleKey in ipairs(self:GetRoleOrder()) do
            if card.roles[roleKey] then
                if card.roles[roleKey].SetAvailable then
                    card.roles[roleKey]:SetAvailable(roleAvailability == nil or roleAvailability[roleKey] ~= false)
                end
                card.roles[roleKey]:SetChecked(roles[roleKey] or false)
            end
        end
    end

    self:SetCharacterCardVisible(card, true)
end

function MK:GetWarbandCharacterEntries()
    if core.CharacterStore and core.CharacterStore.GetWarbandCharacterEntries then
        return core.CharacterStore:GetWarbandCharacterEntries(self)
    end

    return {}
end

function CharacterTab.Build(owner, tabGroup)
    local createScrollContainer = core.CreateNativeScrollContainer
    local createSectionTitle = core.CreatePlayerInfoSectionTitle
    if not (createScrollContainer and createSectionTitle) then
        return
    end

    local playerInfo = CreateFrame("Frame", "PlayerInfo", UIParent)
    playerInfo.mkFillDecorFrame = true
    playerInfo.mkDecorFrameInsetLeft = 24
    playerInfo.mkDecorFrameInsetTop = 4
    playerInfo.mkDecorFrameInsetRight = 24
    playerInfo.mkDecorFrameInsetBottom = 10
    tabGroup:AddChild(playerInfo)

    local scrollFrame, content, scrollBar = createScrollContainer(playerInfo, function()
        UpdateCharacterCardsScrollState(owner.widgets)
    end, 0)
    if scrollBar and tabGroup and tabGroup.frame then
        scrollBar:ClearAllPoints()
        scrollBar:SetPoint("TOPRIGHT", tabGroup.frame, "TOPRIGHT", 17, -4)
        scrollBar:SetPoint("BOTTOMRIGHT", tabGroup.frame, "BOTTOMRIGHT", 17, 4)
        scrollBar:SetWidth(17)
    end

    local playerInfoTitle, playerInfoTitleBg, playerInfoTitleText = createSectionTitle(content, "PlayerInfoTitle", TRANSLATE("当前角色"))
    playerInfoTitle:SetPoint("TOPLEFT", content, "TOPLEFT", 0, PLAYER_INFO_TITLE_OFFSET_Y)
    playerInfoTitle:SetPoint("TOPRIGHT", content, "TOPRIGHT", -PLAYER_INFO_TITLE_INSET_RIGHT, PLAYER_INFO_TITLE_OFFSET_Y)

    local currentCharacterCard = owner:CreateCurrentCharacterCardFrame(content, playerInfoTitle)

    local warbandCharactersTitle, warbandCharactersTitleBg, warbandCharactersTitleText = createSectionTitle(content, "PlayerWarbandCharactersTitle", TRANSLATE("战团角色"))
    warbandCharactersTitle:SetPoint("TOPLEFT", currentCharacterCard, "BOTTOMLEFT", 0, -PLAYER_INFO_TITLE_TO_SECTION_GAP)
    warbandCharactersTitle:SetPoint("TOPRIGHT", currentCharacterCard, "BOTTOMRIGHT", -PLAYER_INFO_TITLE_INSET_RIGHT, -PLAYER_INFO_TITLE_TO_SECTION_GAP)

    local emptyContainer = CreateFrame("Frame", nil, content)
    emptyContainer:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -218)
    emptyContainer:SetPoint("TOPRIGHT", content, "TOPRIGHT", -8, 0)
    emptyContainer:SetHeight(40)

    local empty = emptyContainer:CreateFontString(nil, "OVERLAY", "GameFontDisable")
    empty:SetPoint("TOPLEFT", emptyContainer, "TOPLEFT", 0, 0)
    empty:SetPoint("BOTTOMRIGHT", emptyContainer, "BOTTOMRIGHT", 0, 0)
    empty:SetJustifyH("CENTER")
    empty:SetJustifyV("MIDDLE")
    empty:SetText(TRANSLATE("暂无可同步的满级战团角色，切换该战团内其他角色可自动补充数据"))
    if core.ApplyPanelEmptyPromptStyle then
        core.ApplyPanelEmptyPromptStyle(empty)
    end
    emptyContainer:Hide()

    owner.widgets = {
        tabGroup = owner.widgets and owner.widgets.tabGroup or tabGroup,
        root = playerInfo,
        empty = empty,
        emptyContainer = emptyContainer,
        characterCardsContainer = content,
        playerInfo = playerInfo,
        playerInfoTitle = playerInfoTitle,
        playerInfoTitleBg = playerInfoTitleBg,
        playerInfoTitleText = playerInfoTitleText,
        warbandCharactersTitle = warbandCharactersTitle,
        warbandCharactersTitleBg = warbandCharactersTitleBg,
        warbandCharactersTitleText = warbandCharactersTitleText,
        warbandCharacterCardsTopOffset = 218,
        currentCharacterCard = currentCharacterCard,
        cardsScrollFrame = scrollFrame,
        cardsScrollBar = scrollBar,
        cardsContent = content,
        characterCards = {},
    }
    owner.tabViews = owner.tabViews or {}
    owner.tabViews.characters = owner.widgets

    if core.ApplyThemeToTabGroup then
        core.ApplyThemeToTabGroup(tabGroup)
    end

    if core.ApplyFontToFrame then
        core.ApplyFontToFrame(playerInfo)
    end

    C_Timer.After(0, function()
        if core.FinalizeFlatTabLayout then
            core.FinalizeFlatTabLayout(tabGroup)
        end
        if owner.widgets and owner.widgets.currentCharacterCard and owner.LayoutCurrentCharacterCardFrame then
            owner:LayoutCurrentCharacterCardFrame(owner.widgets.currentCharacterCard)
        end
    end)

    owner:RefreshMainWindow("BuildSelectedTab", "characters")
end
