local addonName, addonTable = ...
---@type MK
local MK = addonTable.MK
local core = addonTable
local TRANSLATE = core.TRANSLATE or function(msg) return msg end
local ADDON_PANEL_PORTRAIT_PATH = core.ADDON_PANEL_PORTRAIT_PATH or ("Interface\\AddOns\\" .. addonName .. "\\Art\\Logo\\MyKeyStone.png")
local MEETINGSTONE_REFRESH_BUTTON_TEXTURE = "Interface\\AddOns\\" .. addonName .. "\\Art\\Ui\\Refresh.png"
core.MK_ICON_PATH = core.MK_ICON_PATH or ("Interface\\AddOns\\" .. addonName .. "\\Art\\Ui\\Icon\\")
local PLAYER_INFO_TITLE_HEIGHT = 40
local PLAYER_INFO_TITLE_TEXT_INSET_X = 14
local PLAYER_INFO_TITLE_TEXT_OFFSET_Y = 0
local PLAYER_INFO_TITLE_TEXT_SIZE = 14
local PLAYER_INFO_TITLE_LEFT_FADE_WIDTH = 74
local CHARACTER_CARDS_SCROLL_EPSILON = 1

local ROLE_ATLAS_BY_KEY = {
    TANK = "UI-LFG-RoleIcon-Tank",
    HEAL = "UI-LFG-RoleIcon-Healer",
    DPS = "UI-LFG-RoleIcon-DPS",
    EMPTY = "groupfinder-icon-emptyslot",
}
core.GROUP_LIST_ROLE_ATLAS_BY_KEY = ROLE_ATLAS_BY_KEY
core.GROUP_LIST_ROLE_ICON_SIZE = 18
core.GROUP_LIST_TOOLTIP_ICON_SIZE = core.GROUP_LIST_ROLE_ICON_SIZE
core.GROUP_LIST_TOOLTIP_ICON_TEXT_GAP = "  "
core.GROUP_LIST_LEADER_ATLAS = "UI-LFG-RoleIcon-Leader"
local STATUS_ATLAS_BY_KEY = {
    READY = "UI-LFG-ReadyMark",
    DECLINE = "UI-LFG-DeclineMark",
}
local GROUP_LIST_PANEL_PADDING_X = 8
local GROUP_LIST_PANEL_PADDING_TOP = 8
local GROUP_LIST_PANEL_PADDING_BOTTOM = 10
local GROUP_LIST_HEADER_HEIGHT = 36
local GROUP_LIST_HEADER_ROW_GAP = 6
local GROUP_LIST_ROWS_PADDING_X = 4
local GROUP_LIST_ROWS_PADDING_TOP = 4
local GROUP_LIST_ROWS_PADDING_BOTTOM = 4
local GROUP_LIST_ROW_HEIGHT = 36
core.GROUP_LIST_HEADER_HEIGHT = GROUP_LIST_HEADER_HEIGHT
core.GROUP_LIST_ROWS_PADDING_X = GROUP_LIST_ROWS_PADDING_X
core.GROUP_LIST_ROW_HEIGHT = GROUP_LIST_ROW_HEIGHT
local GROUP_LIST_MEETINGSTONE_BUTTON_WIDTH = 72
local GROUP_LIST_MEETINGSTONE_BUTTON_HEIGHT = 26
local GROUP_LIST_MEETINGSTONE_BUTTON_TEXT_OFFSET_Y = 0
core.GROUP_LIST_ROW_BACKGROUND_HEIGHT = 33
core.GROUP_LIST_ROW_BACKGROUND_ATLAS = "ui-damagemeters-header-bar"
core.GROUP_LIST_ROW_HOVER_HEIGHT = 28
core.GROUP_LIST_ROW_HOVER_INSET_X = 2
core.GROUP_LIST_ROW_HOVER_OFFSET_Y = 0
core.GROUP_LIST_ROW_HOVER_FADE_WIDTH = 96
core.GROUP_LIST_TELEPORT_BUTTON_SIZE = 18
core.GROUP_LIST_STATUS_ICON_SIZE = core.GROUP_LIST_ROLE_ICON_SIZE
core.GROUP_LIST_STATUS_ICON_GAP = 4
core.GROUP_LIST_ACCOUNT_TYPE_VETERAN_ICON_TEXTURE = core.MK_ICON_PATH .. "Veteran.png"
core.GROUP_LIST_ACCOUNT_TYPE_VETERAN_ICON_SIZE = 18
core.GROUP_LIST_ACCOUNT_TYPE_VETERAN_GAP = 3
core.GROUP_LIST_ACCOUNT_TYPE_VETERAN_TEXT = "老兵"
core.GROUP_LIST_ACCOUNT_TYPE_VETERAN_TEXT_COLOR = { 1, 0.82, 0, 1 }
core.GROUP_LIST_ACCOUNT_TYPE_QUERYING_TEXT = "查询中"
core.GROUP_LIST_ACCOUNT_TYPE_QUERYING_TEXT_COLOR = { 0.58, 0.58, 0.58, 1 }
core.GROUP_LIST_ACCOUNT_TYPE_OFFLINE_TEXT = "离线"
core.GROUP_LIST_ACCOUNT_TYPE_OFFLINE_TEXT_COLOR = { 0.52, 0.52, 0.52, 1 }
core.GROUP_LIST_FORCE_SCROLL_BAR_VISIBLE = false

core.GROUP_LIST_UNAVAILABLE_TEXT = "-"
core.GROUP_LIST_UNAVAILABLE_TEXT_COLOR = { 0.52, 0.52, 0.52, 1 }
core.GROUP_LIST_ROW_BACKGROUND_CROP_TOP = 0
core.GROUP_LIST_ROW_BACKGROUND_CROP_BOTTOM = 0.05
core.DUNGEON_TILE_GRID_OFFSET_Y = 8
local GROUP_LIST_EMPTY_MIN_HEIGHT = 96
local DEFAULT_WINDOW_WIDTH = 1024
local DEFAULT_WINDOW_HEIGHT = 520
local MAIN_WINDOW_SCALE_MIN = 1
local MAIN_WINDOW_SCALE_MAX = 1.5
local MAIN_WINDOW_SCALE_DEFAULT = MAIN_WINDOW_SCALE_MIN
local MAIN_PANEL_INSET_LEFT = 26
local MAIN_PANEL_INSET_RIGHT = 26
local MAIN_PANEL_INSET_TOP = 64
local MAIN_PANEL_INSET_BOTTOM = 24
local MAIN_WINDOW_TITLE_OFFSET_Y = -6
local GROUP_LIST_SCROLLBAR_SLOT_WIDTH = 17
local GROUP_LIST_SCROLLBAR_SLOT_RIGHT_OFFSET = 17
local GROUP_LIST_SCROLLBAR_SLOT_VERTICAL_INSET = 4
local MAIN_PANEL_CONTENT_PADDING_X = 18
local MAIN_PANEL_CONTENT_PADDING_TOP = 24
local MAIN_PANEL_CONTENT_PADDING_BOTTOM = 16
local MAIN_PANEL_TAB_MIN_WIDTH = 95
local MAIN_PANEL_TAB_MAX_WIDTH = 170
local MAIN_PANEL_TAB_HEIGHT = 32
local MAIN_PANEL_TAB_GAP = 1
local MAIN_WINDOW_FRAME_STRATA = "MEDIUM"
local MAIN_WINDOW_FRAME_LEVEL = 10
local MAIN_WINDOW_DRAG_HANDLE_FRAME_LEVEL_OFFSET = 80
local MAIN_WINDOW_DRAG_HANDLE_TOP_OFFSET = 0
local MAIN_WINDOW_DRAG_HANDLE_BOTTOM_OFFSET = -40
local MAIN_WINDOW_DRAG_HANDLE_LEFT_INSET = 76
local MAIN_WINDOW_DRAG_HANDLE_RIGHT_INSET = 78
local GREAT_VAULT_BUTTON_SIZE = 35
local GREAT_VAULT_READY_BORDER_TEXTURE = "Interface\\AddOns\\" .. addonName .. "\\Art\\Ui\\ApertureBorder.png"
local GREAT_VAULT_READY_BORDER_SIZE = 37
local GREAT_VAULT_READY_BORDER_ALPHA = 1
local GREAT_VAULT_READY_BORDER_COLUMNS = 5
local GREAT_VAULT_READY_BORDER_ROWS = 6
local GREAT_VAULT_READY_BORDER_FRAME_COUNT = GREAT_VAULT_READY_BORDER_COLUMNS * GREAT_VAULT_READY_BORDER_ROWS
local GREAT_VAULT_READY_BORDER_FPS = 24
local TRANSMOG_PANEL_MASK_TOP_ALPHA = 0
local TRANSMOG_PANEL_MASK_BOTTOM_ALPHA = 0.4

local MAIN_TAB_DEFINITIONS = {
    { key = "characters", text = "角色选项卡" },
    { key = "group", text = "队伍" },
    { key = "carpool", text = "车队" },
    { key = "findgroup", text = "寻找队伍" },
    { key = "meetingstone", text = "集合石" },
    { key = "blacklist", text = "黑名单" },
    { key = "dungeons", text = "地下城" },
    { key = "options", text = "设置" },
    { key = "debug", text = "调试", debugOnly = true },
}

local function IsDebugMainTabEnabled()
    local owner = core.MK
    return owner and owner.IsDebugModeEnabled and owner:IsDebugModeEnabled()
end

local function ShouldIncludeMainTabDefinition(definition, includeHiddenDebug)
    return not definition.debugOnly or includeHiddenDebug == true or IsDebugMainTabEnabled()
end

function core.GetMainTabDefinitions(includeHiddenDebug)
    local definitions = {}
    for _, definition in ipairs(MAIN_TAB_DEFINITIONS) do
        if ShouldIncludeMainTabDefinition(definition, includeHiddenDebug) then
            definitions[#definitions + 1] = {
                key = definition.key,
                value = definition.key,
                text = definition.text,
                debugOnly = definition.debugOnly,
            }
        end
    end
    return definitions
end

local function IsMainTabAvailable(owner, group)
    for _, definition in ipairs(MAIN_TAB_DEFINITIONS) do
        if definition.key == group then
            if definition.debugOnly then
                return owner and owner.IsDebugModeEnabled and owner:IsDebugModeEnabled()
            end
            return true
        end
    end
    return false
end

local function NormalizeMainTabSelection(owner, group)
    group = tostring(group or "")
    if IsMainTabAvailable(owner, group) then
        return group
    end
    if group == "debug" then
        return "options"
    end
    return "characters"
end

local function ApplyElvUISkinToButton(button)
    if not button or not C_AddOns or not C_AddOns.IsAddOnLoaded or not C_AddOns.IsAddOnLoaded("ElvUI") then
        return
    end

    if type(_G.ElvUI) ~= "table" then
        return
    end

    local E = unpack(_G.ElvUI)
    if not E or not E.GetModule then
        return
    end

    local ok, skins = pcall(E.GetModule, E, "Skins")
    if ok and skins and skins.HandleButton then
        skins:HandleButton(button)
    end
end
core.ApplyElvUISkinToButton = ApplyElvUISkinToButton

local function ApplyTeleportMiniButtonStyle(button)
    if not button or button.bkTeleportStyled then
        return
    end

    if core.SkinButton then
        core.SkinButton(button)
    else
        ApplyElvUISkinToButton(button)
    end

    button.bkTeleportStyled = true
end
core.ApplyTeleportMiniButtonStyle = ApplyTeleportMiniButtonStyle


local ApplyThemeToTabGroup

local function EnsureFlatTabStyle(tab)
    if not tab or tab.bkFlatTabStyled or (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded("ElvUI") and type(_G.ElvUI) == "table") then
        return
    end

    for _, region in ipairs({
        tab.Left, tab.Middle, tab.Right,
        tab.LeftDisabled, tab.MiddleDisabled, tab.RightDisabled,
        tab.HighlightTexture,
    }) do
        if region then
            region:SetTexture(nil)
            region:Hide()
        end
    end

    local bg = tab:CreateTexture(nil, "BACKGROUND", nil, -1)
    bg:SetPoint("TOPLEFT", tab, "TOPLEFT", 1, -1)
    bg:SetPoint("BOTTOMRIGHT", tab, "BOTTOMRIGHT", -1, 1)
    bg:SetTexture("Interface\\Buttons\\WHITE8X8")
    tab.bkFlatTabBackground = bg

    local borderTop = tab:CreateTexture(nil, "BORDER", nil, 1)
    borderTop:SetPoint("TOPLEFT", bg, "TOPLEFT", -1, 1)
    borderTop:SetPoint("TOPRIGHT", bg, "TOPRIGHT", 1, 1)
    borderTop:SetHeight(1)
    borderTop:SetTexture("Interface\\Buttons\\WHITE8X8")

    local borderBottom = tab:CreateTexture(nil, "BORDER", nil, 1)
    borderBottom:SetPoint("BOTTOMLEFT", bg, "BOTTOMLEFT", -1, -1)
    borderBottom:SetPoint("BOTTOMRIGHT", bg, "BOTTOMRIGHT", 1, -1)
    borderBottom:SetHeight(1)
    borderBottom:SetTexture("Interface\\Buttons\\WHITE8X8")

    local borderLeft = tab:CreateTexture(nil, "BORDER", nil, 1)
    borderLeft:SetPoint("TOPLEFT", bg, "TOPLEFT", -1, 1)
    borderLeft:SetPoint("BOTTOMLEFT", bg, "BOTTOMLEFT", -1, -1)
    borderLeft:SetWidth(1)
    borderLeft:SetTexture("Interface\\Buttons\\WHITE8X8")

    local borderRight = tab:CreateTexture(nil, "BORDER", nil, 1)
    borderRight:SetPoint("TOPRIGHT", bg, "TOPRIGHT", 1, 1)
    borderRight:SetPoint("BOTTOMRIGHT", bg, "BOTTOMRIGHT", 1, -1)
    borderRight:SetWidth(1)
    borderRight:SetTexture("Interface\\Buttons\\WHITE8X8")

    tab.bkFlatTabBorder = {
        top = borderTop,
        bottom = borderBottom,
        left = borderLeft,
        right = borderRight,
    }

    tab.bkFlatTabStyled = true
end

local function UpdateFlatTabStyle(tab)
    if not tab or (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded("ElvUI") and type(_G.ElvUI) == "table") then
        return
    end

    EnsureFlatTabStyle(tab)

    local isActive = tab.selected or not tab:IsEnabled()
    local isHover = tab:IsMouseOver() and not isActive

    if tab.bkFlatTabBackground then
        if isActive then
            tab.bkFlatTabBackground:SetVertexColor(1, 0.82, 0, 0.10)
        elseif isHover then
            tab.bkFlatTabBackground:SetVertexColor(0.12, 0.12, 0.12, 0.96)
        else
            tab.bkFlatTabBackground:SetVertexColor(0.07, 0.07, 0.07, 0.92)
        end
        tab.bkFlatTabBackground:Show()
    end

    if tab.bkFlatTabBorder then
        local r, g, b, a
        if isActive then
            r, g, b, a = 1, 0.82, 0, 0.95
        else
            r, g, b, a = 0, 0, 0, isHover and 0.95 or 0.8
        end

        for _, edge in pairs(tab.bkFlatTabBorder) do
            edge:SetVertexColor(r, g, b, a)
            edge:Show()
        end
    end

    if tab.Text then
        if core.ApplyFont then
            core.ApplyFont(tab.Text, 12, "OUTLINE")
        end

        if isActive then
            tab.Text:SetTextColor(1, 0.82, 0, 1)
        else
            tab.Text:SetTextColor(1, 1, 1, 0.92)
        end
    end
end

local function AdjustFlatTabSpacing(tabGroup, spacing)
    if not tabGroup or not tabGroup.tabs then
        return
    end

    local previousTab = nil
    for index, tab in ipairs(tabGroup.tabs) do
        if tab and tab:IsShown() then
            if index > 1 and previousTab then
                tab:ClearAllPoints()
                tab:SetPoint("LEFT", previousTab, "RIGHT", spacing or 8, 0)
            end

            previousTab = tab
        end
    end
end

local function FinalizeFlatTabLayout(tabGroup)
    if not tabGroup then
        return
    end

    local hasElvUI = C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded("ElvUI") and type(_G.ElvUI) == "table"
    if hasElvUI then
        return
    end

    ApplyThemeToTabGroup(tabGroup)
end
core.FinalizeFlatTabLayout = FinalizeFlatTabLayout

ApplyThemeToTabGroup = function(tabGroup)
    if not tabGroup then
        return
    end

    if core.SkinFrame and tabGroup.border then
        core.SkinFrame(tabGroup.border, { light = true })
    end

    if core.ApplyFont and tabGroup.titletext then
        core.ApplyFont(tabGroup.titletext, 12, "OUTLINE")
    end

    local hasElvUI = C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded("ElvUI") and type(_G.ElvUI) == "table"

    for _, tab in pairs(tabGroup.tabs or {}) do
        if tab then
            if not hasElvUI then
                EnsureFlatTabStyle(tab)
                UpdateFlatTabStyle(tab)

                if not tab.bkFlatTabHooked then
                    tab:HookScript("OnEnter", function(self)
                        UpdateFlatTabStyle(self)
                    end)
                    tab:HookScript("OnLeave", function(self)
                        UpdateFlatTabStyle(self)
                    end)
                    tab:HookScript("OnShow", function(self)
                        UpdateFlatTabStyle(self)
                    end)
                    tab.bkFlatTabHooked = true
                end

                if tab.Text then
                    tab.Text:ClearAllPoints()
                    tab.Text:SetPoint("CENTER", tab, "CENTER", 0, 0)
                end
            elseif tab.Text and core.ApplyFont then
                core.ApplyFont(tab.Text, 12, "OUTLINE")
            end

        end
    end

    if not hasElvUI then
        AdjustFlatTabSpacing(tabGroup, 8)
    end
end
core.ApplyThemeToTabGroup = ApplyThemeToTabGroup

local CreateCardEdge

local function GetPlayerClassColor()
    local _, classToken = UnitClass("player")
    local classColors = CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS
    local color = classToken and classColors and classColors[classToken]
    if color then
        return color.r or 1, color.g or 0.82, color.b or 0
    end

    return 1, 0.82, 0
end

local function GetClassColorByToken(classToken)
    local classColors = CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS
    local color = classToken and classColors and classColors[classToken]
    if color then
        return color.r or 1, color.g or 0.82, color.b or 0
    end

    return 1, 0.82, 0
end
core.GetClassColorByToken = GetClassColorByToken

local function ApplySystemPanelTitleStyle(fontString)
    if not fontString then
        return
    end

    if fontString.SetFontObject and _G.GameFontNormal then
        fontString:SetFontObject(_G.GameFontNormal)
    end

    if fontString.SetTextColor and _G.NORMAL_FONT_COLOR and _G.NORMAL_FONT_COLOR.GetRGB then
        fontString:SetTextColor(_G.NORMAL_FONT_COLOR:GetRGB())
    elseif fontString.SetTextColor then
        fontString:SetTextColor(1, 0.82, 0, 1)
    end
end

local function CenterSystemPanelTitle(frame, fontString, offsetY)
    if not frame or not fontString then
        return
    end

    fontString:ClearAllPoints()
    fontString:SetPoint("TOP", frame, "TOP", 0, offsetY or MAIN_WINDOW_TITLE_OFFSET_Y)
    fontString:SetJustifyH("CENTER")
end

local function UpdateMainWindowTitle(frame)
    if not frame then
        return
    end

    local title = TRANSLATE("大秘境钥石助手")
    if frame.SetTitle then
        frame:SetTitle(title)
        ApplySystemPanelTitleStyle(frame.systemTitleText)
        CenterSystemPanelTitle(frame, frame.systemTitleText, MAIN_WINDOW_TITLE_OFFSET_Y)
        return
    end

    if frame.systemTitleText then
        frame.systemTitleText:SetText(title)
        ApplySystemPanelTitleStyle(frame.systemTitleText)
        CenterSystemPanelTitle(frame, frame.systemTitleText, MAIN_WINDOW_TITLE_OFFSET_Y)
        return
    end

    if not frame.titlePrefix or not frame.titleSuffix then
        return
    end

    local classR, classG, classB = GetPlayerClassColor()
    frame.titlePrefix:SetText(TRANSLATE("大秘境"))
    frame.titlePrefix:SetTextColor(1, 1, 1, 1)
    frame.titleSuffix:SetText(TRANSLATE("钥匙助手"))
    frame.titleSuffix:SetTextColor(classR, classG, classB, 1)

    local prefixWidth = frame.titlePrefix:GetStringWidth() or 0
    local suffixWidth = frame.titleSuffix:GetStringWidth() or 0
    local spacing = -1
    local totalWidth = prefixWidth + suffixWidth + spacing
    local startX = -(totalWidth / 2)

    frame.titlePrefix:ClearAllPoints()
    frame.titlePrefix:SetPoint("TOPLEFT", frame, "TOP", startX, MAIN_WINDOW_TITLE_OFFSET_Y)

    frame.titleSuffix:ClearAllPoints()
    frame.titleSuffix:SetPoint("LEFT", frame.titlePrefix, "RIGHT", spacing, 0)
end

local function ResetCardSubFrameSkin(frame)
    if not frame then
        return
    end

    if frame._rhhBG then
        frame._rhhBG:SetColorTexture(0, 0, 0, 0)
        frame._rhhBG:Hide()
    end
    if frame._rhhBorder then
        frame._rhhBorder:SetColorTexture(0, 0, 0, 0)
        frame._rhhBorder:Hide()
    end
    if frame._rhhInner then
        frame._rhhInner:SetColorTexture(0, 0, 0, 0)
        frame._rhhInner:Hide()
    end

    if frame.SetBackdrop then
        frame:SetBackdrop(nil)
    end
    if frame.SetBackdropColor then
        frame:SetBackdropColor(0, 0, 0, 0)
    end
    if frame.SetBackdropBorderColor then
        frame:SetBackdropBorderColor(0, 0, 0, 0)
    end
end

local function CreateFrameWithTemplate(frameType, name, parent, template)
    local ok, frame = pcall(CreateFrame, frameType, name, parent, template)
    if ok and frame then
        return frame
    end

    return CreateFrame(frameType, name, parent)
end

local function CreateFrameWithTemplateOptions(frameType, name, parent, templates)
    for _, template in ipairs(templates or {}) do
        local ok, frame = pcall(CreateFrame, frameType, name, parent, template)
        if ok and frame then
            return frame, template
        end
    end

    return CreateFrame(frameType, name, parent), nil
end
core.CreateFrameWithTemplateOptions = CreateFrameWithTemplateOptions

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

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

local function ApplyVerticalBlackMask(texture, topAlpha, bottomAlpha)
    if not texture then
        return
    end

    texture:SetTexture("Interface\\Buttons\\WHITE8X8")
    if texture.SetGradient and CreateColor then
        local ok = pcall(texture.SetGradient, texture, "VERTICAL", CreateColor(0, 0, 0, topAlpha), CreateColor(0, 0, 0, bottomAlpha))
        if ok then
            return
        end
    end

    if texture.SetGradientAlpha then
        local ok = pcall(texture.SetGradientAlpha, texture, "VERTICAL", 0, 0, 0, topAlpha, 0, 0, 0, bottomAlpha)
        if ok then
            return
        end
    end

    texture:SetVertexColor(0, 0, 0, bottomAlpha)
end

local function ApplyHorizontalBlackMask(texture, leftAlpha, rightAlpha)
    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(0, 0, 0, leftAlpha), CreateColor(0, 0, 0, rightAlpha))
        if ok then
            return
        end
    end

    if texture.SetGradientAlpha then
        local ok = pcall(texture.SetGradientAlpha, texture, "HORIZONTAL", 0, 0, 0, leftAlpha, 0, 0, 0, rightAlpha)
        if ok then
            return
        end
    end

    texture:SetVertexColor(0, 0, 0, math.max(leftAlpha or 0, rightAlpha or 0))
end

local function CreatePlayerInfoSectionTitle(parent, frameName, titleText)
    local titleFrame = CreateFrame("Frame", frameName, parent)
    titleFrame:SetHeight(PLAYER_INFO_TITLE_HEIGHT)

    local background = titleFrame:CreateTexture(nil, "BACKGROUND", nil, 1)
    background:SetAllPoints(titleFrame)
    if not TrySetAtlas(background, "housefinder_header-bg-gradient", false) then
        background:SetTexture("Interface\\Buttons\\WHITE8X8")
        background:SetVertexColor(0, 0, 0, 0.45)
    else
        background:SetVertexColor(1, 1, 1, 1)
    end

    local leftFade = titleFrame:CreateTexture(nil, "ARTWORK", nil, 1)
    leftFade:SetPoint("TOPLEFT", titleFrame, "TOPLEFT", 0, 0)
    leftFade:SetPoint("BOTTOMLEFT", titleFrame, "BOTTOMLEFT", 0, 0)
    leftFade:SetWidth(PLAYER_INFO_TITLE_LEFT_FADE_WIDTH)
    ApplyHorizontalBlackMask(leftFade, 0.96, 0)

    local text = titleFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    text:SetPoint("TOPLEFT", background, "TOPLEFT", PLAYER_INFO_TITLE_TEXT_INSET_X, PLAYER_INFO_TITLE_TEXT_OFFSET_Y)
    text:SetPoint("BOTTOMRIGHT", background, "BOTTOMRIGHT", -PLAYER_INFO_TITLE_TEXT_INSET_X, PLAYER_INFO_TITLE_TEXT_OFFSET_Y)
    text:SetJustifyH("LEFT")
    text:SetJustifyV("MIDDLE")
    text:SetText(titleText)
    text:SetTextColor(1, 0.82, 0, 1)
    if core.ApplyFont then
        core.ApplyFont(text, PLAYER_INFO_TITLE_TEXT_SIZE, "OUTLINE")
    end

    titleFrame.Background = background
    titleFrame.LeftFade = leftFade
    titleFrame.Text = text
    return titleFrame, background, text
end
core.CreatePlayerInfoSectionTitle = CreatePlayerInfoSectionTitle

local function ApplySystemOptionsBackplateStyle(frame)
    if not frame then
        return
    end

    if frame.mkOptionsInnerFrame then
        frame.mkOptionsInnerFrame:Hide()
    end

    if frame.SetBackdrop then
        frame:SetBackdrop(nil)
    end

    if not frame.mkTransmogPanelBackground then
        local background = frame:CreateTexture(nil, "BACKGROUND", nil, -4)
        frame.mkTransmogPanelBackground = background

        local darkOverlay = frame:CreateTexture(nil, "BACKGROUND", nil, -3)
        frame.mkTransmogPanelDarkOverlay = darkOverlay

        local topGradient = frame:CreateTexture(nil, "BACKGROUND", nil, -2)
        topGradient:SetHeight(92)
        frame.mkTransmogPanelTopGradient = topGradient

        local bottomGradient = frame:CreateTexture(nil, "BACKGROUND", nil, -2)
        bottomGradient:SetHeight(92)
        frame.mkTransmogPanelBottomGradient = bottomGradient

        local borderFrame = CreateFrame("Frame", nil, frame)
        local borderInsetX = 8
        borderFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", -borderInsetX, 12)
        borderFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", borderInsetX, -6)
        borderFrame:EnableMouse(false)
        frame.mkTransmogPanelBorderFrame = borderFrame

        local border = borderFrame:CreateTexture(nil, "ARTWORK")
        border:SetAllPoints(borderFrame)
        frame.mkTransmogPanelBorder = border

    end

    local backgroundInset = 5
    local backgroundBottomInset = backgroundInset + 2

    frame.mkTransmogPanelBackground:ClearAllPoints()
    frame.mkTransmogPanelBackground:SetPoint("TOPLEFT", frame, "TOPLEFT", backgroundInset, -backgroundInset)
    frame.mkTransmogPanelBackground:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -backgroundInset, backgroundBottomInset)
    frame.mkTransmogPanelBackground:SetDrawLayer("BACKGROUND", -4)

    if frame.mkTransmogPanelDarkOverlay then
        frame.mkTransmogPanelDarkOverlay:ClearAllPoints()
        frame.mkTransmogPanelDarkOverlay:SetAllPoints(frame.mkTransmogPanelBackground)
        frame.mkTransmogPanelDarkOverlay:SetDrawLayer("BACKGROUND", -1)
    end

    if frame.mkTransmogPanelTopGradient then
        frame.mkTransmogPanelTopGradient:ClearAllPoints()
        frame.mkTransmogPanelTopGradient:SetPoint("TOPLEFT", frame, "TOPLEFT", backgroundInset, -backgroundInset)
        frame.mkTransmogPanelTopGradient:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -backgroundInset, -backgroundInset)
        frame.mkTransmogPanelTopGradient:SetDrawLayer("BACKGROUND", -2)
    end

    if frame.mkTransmogPanelBottomGradient then
        frame.mkTransmogPanelBottomGradient:ClearAllPoints()
        frame.mkTransmogPanelBottomGradient:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", backgroundInset, backgroundBottomInset)
        frame.mkTransmogPanelBottomGradient:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -backgroundInset, backgroundBottomInset)
        frame.mkTransmogPanelBottomGradient:SetDrawLayer("BACKGROUND", -2)
    end

    if frame.mkTransmogPanelBorderFrame then
        frame.mkTransmogPanelBorderFrame:SetFrameLevel(frame:GetFrameLevel() + 12)
    end

    if not TrySetAtlas(frame.mkTransmogPanelBackground, "transmog-outfit-darkbg", false) then
        frame.mkTransmogPanelBackground:SetColorTexture(0.02, 0.018, 0.014, 0.96)
    end
    frame.mkTransmogPanelBackground:SetVertexColor(1, 1, 1, 1)
    if frame.mkTransmogPanelBackground.Show then
        frame.mkTransmogPanelBackground:Show()
    end

    if frame.mkTransmogPanelDarkOverlay then
        ApplyVerticalBlackMask(frame.mkTransmogPanelDarkOverlay, TRANSMOG_PANEL_MASK_TOP_ALPHA, TRANSMOG_PANEL_MASK_BOTTOM_ALPHA)
        frame.mkTransmogPanelDarkOverlay:Show()
    end

    if frame.mkTransmogPanelTopGradient then
        TrySetAtlas(frame.mkTransmogPanelTopGradient, "transmog-outfit-toptexture", false)
        frame.mkTransmogPanelTopGradient:SetVertexColor(1, 1, 1, 1)
        frame.mkTransmogPanelTopGradient:Show()
    end

    if frame.mkTransmogPanelBottomGradient then
        TrySetAtlas(frame.mkTransmogPanelBottomGradient, "transmog-outfit-bottomtexture", false)
        frame.mkTransmogPanelBottomGradient:SetVertexColor(1, 1, 1, 1)
        frame.mkTransmogPanelBottomGradient:Show()
    end

    TrySetAtlas(frame.mkTransmogPanelBorder, "transmog-tabs-frame", false)
    frame.mkTransmogPanelBorder:SetVertexColor(1, 1, 1, 1)

    if frame.mkTransmogHeaderLine then
        frame.mkTransmogHeaderLine:Hide()
    end
end

local OPTIONS_TAB_ATLASES = {
    up = {
        left = "Options_Tab_Left",
        middle = "Options_Tab_Middle",
        right = "Options_Tab_Right",
    },
    selected = {
        left = "Options_Tab_Active_Left",
        middle = "Options_Tab_Active_Middle",
        right = "Options_Tab_Active_Right",
    },
}

local function AssignOptionsTabAtlasKeys(button)
    button.upLeftTexture = OPTIONS_TAB_ATLASES.up.left
    button.upMiddleTexture = OPTIONS_TAB_ATLASES.up.middle
    button.upRightTexture = OPTIONS_TAB_ATLASES.up.right
    button.overLeftTexture = OPTIONS_TAB_ATLASES.up.left
    button.overMiddleTexture = OPTIONS_TAB_ATLASES.up.middle
    button.overRightTexture = OPTIONS_TAB_ATLASES.up.right
    button.selectedLeftTexture = OPTIONS_TAB_ATLASES.selected.left
    button.selectedMiddleTexture = OPTIONS_TAB_ATLASES.selected.middle
    button.selectedRightTexture = OPTIONS_TAB_ATLASES.selected.right
end

local function EnsureOptionsTabPieces(button)
    if not button.Left then
        button.Left = button:CreateTexture(nil, "BACKGROUND")
        button.Left:SetPoint("BOTTOMLEFT")
    end

    if not button.Right then
        button.Right = button:CreateTexture(nil, "BACKGROUND")
        button.Right:SetPoint("BOTTOMRIGHT")
    end

    if not button.Middle then
        button.Middle = button:CreateTexture(nil, "BACKGROUND")
        button.Middle:SetPoint("TOPLEFT", button.Left, "TOPRIGHT")
        button.Middle:SetPoint("TOPRIGHT", button.Right, "TOPLEFT")
    end

    if not button.Text then
        button.Text = button:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    end

    if button._mkTopTabStyle and not button.SelectedHighlight then
        local selectedHighlight = CreateFrame("Frame", nil, button)
        selectedHighlight:SetFrameLevel(button._mkSelectedHighlightFrameLevel or (button:GetFrameLevel() + 10))
        selectedHighlight:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", 2, 5)
        selectedHighlight:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -3, 5)
        selectedHighlight:SetHeight(1)
        selectedHighlight:Hide()

        local highlight = selectedHighlight:CreateTexture(nil, "ARTWORK")
        highlight:SetAllPoints(selectedHighlight)
        TrySetAtlas(highlight, "transmog-tab-hl", false)
        highlight:SetVertexColor(1, 1, 1, 1)

        selectedHighlight.Highlight = highlight
        button.SelectedHighlight = selectedHighlight
    end
end

local function UpdateOptionsTabAtlas(button)
    if not button then
        return
    end

    if button._mkTopTabStyle then
        return
    end

    local atlasSet = button._mkOptionsTabSelected and OPTIONS_TAB_ATLASES.selected or OPTIONS_TAB_ATLASES.up
    TrySetAtlas(button.Left, atlasSet.left, true)
    TrySetAtlas(button.Middle, atlasSet.middle, true)
    TrySetAtlas(button.Right, atlasSet.right, true)
end

local function SetOptionsTabText(button, text)
    button.tabText = text
    button.Text:SetText(text)
    button.Text:SetWidth(0)

    if button._mkTopTabStyle then
        local textWidth = button.Text:GetStringWidth() or 0
        local width = math.max(MAIN_PANEL_TAB_MIN_WIDTH, textWidth + 30)
        width = math.min(MAIN_PANEL_TAB_MAX_WIDTH, width)
        button:SetSize(width, MAIN_PANEL_TAB_HEIGHT)
        button.Text:SetWidth(math.max(1, width - 10))
    else
        local textWidth = button.Text:GetStringWidth() or 0
        button:SetSize(math.max(MAIN_PANEL_TAB_MIN_WIDTH, textWidth + 40), MAIN_PANEL_TAB_HEIGHT)
    end
end

local function SetOptionsTabSelected(button, isSelected)
    if not button or not button.Text then
        return
    end

    button._mkOptionsTabSelected = isSelected
    if button._mkTopTabStyle and button.SetTabSelected then
        local ok = pcall(button.SetTabSelected, button, isSelected)
        if ok then
            if button.SelectedHighlight then
                button.SelectedHighlight:SetShown(isSelected)
            end
            if isSelected then
                button.Text:SetTextColor(1, 1, 1, 1)
            else
                button.Text:SetTextColor(1, 0.82, 0, 1)
            end
            return
        end
    end

    if button.SetSelectedState and button.OnSelected then
        button:SetSelectedState(isSelected)
        button:OnSelected(isSelected)
    else
        UpdateOptionsTabAtlas(button)
        button.Text:ClearAllPoints()
        button.Text:SetPoint("BOTTOM", button, "BOTTOM", 0, isSelected and 6 or 4)
        button.Text:SetFontObject(isSelected and "GameFontHighlightSmall" or "GameFontNormalSmall")
    end
end

local function PlaySystemOptionsTabSound()
    if not PlaySound then
        return
    end

    local sound = _G.SOUNDKIT and _G.SOUNDKIT.IG_CHARACTER_INFO_TAB
    if sound then
        pcall(PlaySound, sound)
    end
end

local function CreateNativeTabButton(parent, text, tabIndex)
    local buttonName = ("MKMainWindowTab%d"):format(tabIndex or 0)
    local button, template = CreateFrameWithTemplateOptions("Button", buttonName, parent, {
        "TabSystemTopButtonTemplate",
        "MinimalTabTemplate",
    })
    button:SetID(tabIndex or 0)
    button._mkOptionsTab = true
    button._mkTopTabStyle = template == "TabSystemTopButtonTemplate" and button.LeftActive and button.MiddleActive and button.RightActive
    button._mkSelectedHighlightFrameLevel = parent and parent.selectedHighlightFrameLevel

    if button._mkTopTabStyle then
        button:SetFrameLevel(parent:GetFrameLevel() + 1)
        button.isTabOnTop = true
        button.selectedFontObject = GameFontHighlight
        button.unselectedFontObject = GameFontNormal
        button.textOffsetY = 3
        button.textPadding = 17
        if button.HandleRotation then
            pcall(button.HandleRotation, button)
        end
    else
        AssignOptionsTabAtlasKeys(button)
    end
    EnsureOptionsTabPieces(button)
    SetOptionsTabText(button, text)
    SetOptionsTabSelected(button, false)

    if not button._mkTopTabStyle then
        button:SetScript("OnEnter", function(tabButton)
            tabButton.over = true
            if tabButton.UpdateAtlas then
                tabButton:UpdateAtlas()
            else
                UpdateOptionsTabAtlas(tabButton)
            end
        end)

        button:SetScript("OnLeave", function(tabButton)
            tabButton.over = nil
            if tabButton.UpdateAtlas then
                tabButton:UpdateAtlas()
            else
                UpdateOptionsTabAtlas(tabButton)
            end
        end)
    end

    return button
end

local function UpdateNativeTabButton(button, isActive)
    if not button or not button.Text then
        return
    end

    if button._mkOptionsTab then
        SetOptionsTabSelected(button, isActive)
        return
    end

    local cr, cg, cb = GetPlayerClassColor()

    if isActive then
        button.Text:SetTextColor(cr, cg, cb, 1)
    else
        button.Text:SetTextColor(1, 1, 1, 0.92)
    end
end

local function PositionTabActiveIndicatorAtButton(indicatorFrame, button)
    if not indicatorFrame or not button then
        return
    end

    indicatorFrame:ClearAllPoints()
    indicatorFrame:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", 0, 0)
    indicatorFrame:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, 0)
    indicatorFrame:SetHeight(2)
end

local function EnsureTabActiveIndicatorFrame(tabsBar)
    if not tabsBar or tabsBar.bkActiveIndicatorFrame then
        return tabsBar and tabsBar.bkActiveIndicatorFrame or nil
    end

    local frame = CreateFrame("Frame", nil, tabsBar)
    frame:SetFrameStrata(tabsBar:GetFrameStrata())
    frame:SetFrameLevel(tabsBar:GetFrameLevel() + 20)
    frame:Hide()

    local texture = frame:CreateTexture(nil, "ARTWORK", nil, 7)
    texture:SetAllPoints()
    do
        local cr, cg, cb = GetPlayerClassColor()
        texture:SetColorTexture(cr, cg, cb, 0.95)
    end

    local animationGroup = frame:CreateAnimationGroup()
    local translation = animationGroup:CreateAnimation("Translation")
    translation:SetDuration(0.16)
    translation:SetSmoothing("OUT")

    animationGroup:SetScript("OnFinished", function()
        translation:SetOffset(0, 0)
        if frame._bkTargetButton then
            PositionTabActiveIndicatorAtButton(frame, frame._bkTargetButton)
            frame:Show()
        else
            frame:Hide()
        end
        frame._bkTargetButton = nil
    end)

    animationGroup:SetScript("OnStop", function()
        translation:SetOffset(0, 0)
        if frame._bkTargetButton then
            PositionTabActiveIndicatorAtButton(frame, frame._bkTargetButton)
            frame:Show()
        end
        frame._bkTargetButton = nil
    end)

    tabsBar.bkActiveIndicatorFrame = frame
    tabsBar.bkActiveIndicatorAnimationGroup = animationGroup
    tabsBar.bkActiveIndicatorTranslation = translation
    return frame
end

local function UpdateTabActiveIndicator(tabsBar, oldButton, newButton)
    if tabsBar and tabsBar.useSystemTabs then
        if tabsBar.bkActiveIndicatorFrame then
            tabsBar.bkActiveIndicatorFrame:Hide()
        end
        return
    end

    local indicatorFrame = EnsureTabActiveIndicatorFrame(tabsBar)
    if not indicatorFrame or not newButton then
        return
    end

    local texture = indicatorFrame:GetRegions()
    if texture then
        local cr, cg, cb = GetPlayerClassColor()
        texture:SetColorTexture(cr, cg, cb, 0.95)
    end

    local animationGroup = tabsBar.bkActiveIndicatorAnimationGroup
    local translation = tabsBar.bkActiveIndicatorTranslation
    if animationGroup and animationGroup:IsPlaying() then
        animationGroup:Stop()
    end

    local shouldAnimate = oldButton and oldButton ~= newButton and oldButton:IsVisible() and newButton:IsVisible()
    if shouldAnimate and animationGroup and translation then
        local oldLeft, oldTop = oldButton:GetLeft(), oldButton:GetTop()
        local newLeft, newTop = newButton:GetLeft(), newButton:GetTop()
        if oldLeft and oldTop and newLeft and newTop then
            PositionTabActiveIndicatorAtButton(indicatorFrame, oldButton)
            indicatorFrame:Show()
            indicatorFrame._bkTargetButton = newButton
            translation:SetOffset(newLeft - oldLeft, newTop - oldTop)
            animationGroup:Play()
            return
        end
    end

    PositionTabActiveIndicatorAtButton(indicatorFrame, newButton)
    indicatorFrame:Show()
end

local function IsMainTabDefinitionVisible(owner, definition)
    if not definition or not definition.debugOnly then
        return true
    end
    return owner and owner.IsDebugModeEnabled and owner:IsDebugModeEnabled()
end

local function LayoutMainTabButtons(frame, owner)
    if not frame or not frame.tabDefinitions or not frame.tabButtons or not frame.tabsBar then
        return
    end

    local previousTab = nil
    local visibleIndex = 0
    local tabIdByGroup = {}
    frame.Tabs = frame.Tabs or {}

    for _, definition in ipairs(frame.tabDefinitions) do
        local button = frame.tabButtons[definition.key]
        if button then
            local isVisible = IsMainTabDefinitionVisible(owner, definition)
            button:ClearAllPoints()
            button:SetShown(isVisible)
            if isVisible then
                visibleIndex = visibleIndex + 1
                button:SetID(visibleIndex)
                tabIdByGroup[definition.key] = visibleIndex
                frame.Tabs[visibleIndex] = button
                if previousTab then
                    button:SetPoint("TOPLEFT", previousTab, "TOPRIGHT", MAIN_PANEL_TAB_GAP, 0)
                else
                    button:SetPoint("TOPLEFT", frame.tabsBar, "TOPLEFT", 0, 0)
                end
                previousTab = button
            end
        end
    end

    for index = visibleIndex + 1, #(frame.Tabs or {}) do
        frame.Tabs[index] = nil
    end

    frame.numTabs = visibleIndex
    frame.tabIdByGroup = tabIdByGroup
end

local function CreateNativeContentAdapter(parentFrame, insetLeft, insetTop, insetRight, insetBottom)
    local adapter = {
        frame = parentFrame,
        children = {},
        insetLeft = insetLeft or 0,
        insetTop = insetTop or 0,
        insetRight = insetRight or 0,
        insetBottom = insetBottom or 0,
    }

    function adapter:LayoutChildFrame(childFrame)
        if not childFrame then
            return
        end

        if childFrame.mkFillDecorFrame then
            local inset = childFrame.mkDecorFrameInset or 0
            local insetLeft = childFrame.mkDecorFrameInsetLeft or inset
            local insetTop = childFrame.mkDecorFrameInsetTop or inset
            local insetRight = childFrame.mkDecorFrameInsetRight or inset
            local insetBottom = childFrame.mkDecorFrameInsetBottom or inset
            childFrame:ClearAllPoints()
            childFrame:SetPoint("TOPLEFT", self.frame, "TOPLEFT", insetLeft, -insetTop)
            childFrame:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", -insetRight, insetBottom)
            return
        end

        local parentWidth = self.frame and self.frame.GetWidth and self.frame:GetWidth() or 0
        local childWidth = math.max(1, parentWidth - self.insetLeft - self.insetRight)
        if childFrame.SetWidth then
            childFrame:SetWidth(childWidth)
        end

        childFrame:ClearAllPoints()
        childFrame:SetPoint("TOP", self.frame, "TOP", 0, -self.insetTop)
        childFrame:SetPoint("BOTTOM", self.frame, "BOTTOM", 0, self.insetBottom)
    end

    function adapter:AddChild(child)
        if not child then
            return
        end

        self.children[#self.children + 1] = child

        local childFrame = child.frame or child
        if childFrame and childFrame.SetParent then
            childFrame:SetParent(self.frame)
            self:LayoutChildFrame(childFrame)
            if childFrame.Show then
                childFrame:Show()
            end
        end
    end

    function adapter:ReleaseChildren()
        for _, child in ipairs(self.children) do
            local childFrame = child and (child.frame or child)
            if childFrame and childFrame.Hide then
                childFrame:Hide()
                childFrame:SetParent(UIParent)
            end
        end

        self.children = {}
    end

    if parentFrame and parentFrame.HookScript then
        parentFrame:HookScript("OnSizeChanged", function()
            for _, child in ipairs(adapter.children) do
                local childFrame = child and (child.frame or child)
                if childFrame and childFrame.GetParent and childFrame:GetParent() == adapter.frame then
                    adapter:LayoutChildFrame(childFrame)
                end
            end
        end)
    end

    return adapter
end

local function CreateNativeScrollContainer(parentFrame, onSizeChanged, scrollFrameRightInset)
    local scrollFrame = CreateFrame("ScrollFrame", nil, parentFrame)
    scrollFrame:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", 0, 0)
    scrollFrame:SetPoint("BOTTOMRIGHT", parentFrame, "BOTTOMRIGHT", scrollFrameRightInset or -22, 0)
    scrollFrame:EnableMouseWheel(true)

    local content = CreateFrame("Frame", nil, scrollFrame)
    content:ClearAllPoints()
    content:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT", 0, 0)
    content:SetWidth(math.max((scrollFrame:GetWidth() or 0), 1))
    content:SetHeight(1)
    scrollFrame:SetScrollChild(content)

    local scrollBar = CreateFrameWithTemplateOptions("EventFrame", nil, parentFrame, {
        "MinimalScrollBar",
    })
    if not scrollBar or (not scrollBar.SetValue and not scrollBar.SetScrollPercentage) then
        scrollBar = CreateFrame("Slider", nil, parentFrame)
        scrollBar:SetOrientation("VERTICAL")
        scrollBar:SetValueStep(1)
    end
    scrollBar:SetPoint("TOPRIGHT", parentFrame, "TOPRIGHT", 23, -2)
    scrollBar:SetPoint("BOTTOMRIGHT", parentFrame, "BOTTOMRIGHT", 23, 2)
    scrollBar:SetWidth(17)
    if scrollBar.SetMinMaxValues then
        scrollBar:SetMinMaxValues(0, 0)
    end
    scrollBar:Hide()

    local useNativeMinimalScrollBar = scrollBar.Init and ScrollUtil and ScrollUtil.InitScrollFrameWithScrollBar
    if useNativeMinimalScrollBar then
        ScrollUtil.InitScrollFrameWithScrollBar(scrollFrame, scrollBar)
    end

    local isSyncingScroll = false
    local scrollMinValue = 0
    local scrollMaxValue = 0
    local function SetScrollBarRange(minValue, maxValue)
        scrollMinValue = minValue or 0
        scrollMaxValue = maxValue or 0
        if scrollBar.SetMinMaxValues then
            scrollBar:SetMinMaxValues(scrollMinValue, scrollMaxValue)
        elseif scrollBar.SetScrollPercentage then
            scrollBar:SetScrollPercentage(0)
        end
    end
    scrollBar.mkSetScrollRange = SetScrollBarRange

    local function GetScrollBarValue()
        if useNativeMinimalScrollBar and scrollBar.GetScrollPercentage then
            return (scrollBar:GetScrollPercentage() or 0) * scrollMaxValue
        end

        if scrollBar.GetValue then
            return scrollBar:GetValue() or 0
        end

        if scrollBar.GetScrollPercentage then
            return (scrollBar:GetScrollPercentage() or 0) * scrollMaxValue
        end

        return 0
    end
    scrollBar.mkGetScrollValue = GetScrollBarValue

    local function SetScrollBarValue(value)
        if useNativeMinimalScrollBar and scrollBar.SetScrollPercentage then
            local percentage = scrollMaxValue > 0 and (value / scrollMaxValue) or 0
            scrollBar:SetScrollPercentage(math.min(math.max(percentage, 0), 1), ScrollBoxConstants and ScrollBoxConstants.NoScrollInterpolation)
        elseif scrollBar.SetValue then
            scrollBar:SetValue(value)
        elseif scrollBar.SetScrollPercentage then
            local percentage = scrollMaxValue > 0 and (value / scrollMaxValue) or 0
            scrollBar:SetScrollPercentage(math.min(math.max(percentage, 0), 1))
        end
    end
    scrollBar.mkSetScrollValue = SetScrollBarValue

    local function ApplyScrollValue(value)
        local nextValue = math.min(math.max(value or 0, scrollMinValue), scrollMaxValue)

        isSyncingScroll = true
        scrollFrame:SetVerticalScroll(nextValue)
        if math.abs(GetScrollBarValue() - nextValue) > 0.5 then
            SetScrollBarValue(nextValue)
        end
        isSyncingScroll = false
    end

    if useNativeMinimalScrollBar then
        -- ScrollUtil.InitScrollFrameWithScrollBar wires the arrows, thumb, mouse wheel, and ScrollFrame sync.
    elseif scrollBar.SetScrollPercentage and scrollBar.RegisterCallback and scrollBar.Event and scrollBar.Event.OnScroll then
        scrollBar:RegisterCallback(scrollBar.Event.OnScroll, function(_, scrollPercentage)
            if isSyncingScroll then
                return
            end
            ApplyScrollValue((scrollPercentage or 0) * scrollMaxValue)
        end)
    elseif scrollBar.SetScript and scrollBar.SetValue then
        scrollBar:SetScript("OnValueChanged", function(_, value)
            if isSyncingScroll then
                return
            end
            ApplyScrollValue(value or GetScrollBarValue())
        end)
    end
    SetScrollBarValue(0)

    if not useNativeMinimalScrollBar then
        scrollFrame:SetScript("OnVerticalScroll", function(_, offset)
            ApplyScrollValue(offset)
        end)

        scrollFrame:SetScript("OnMouseWheel", function(_, delta)
            local nextValue = math.min(math.max(GetScrollBarValue() - delta * 36, scrollMinValue), scrollMaxValue)
            ApplyScrollValue(nextValue)
        end)
    end

    if onSizeChanged then
        scrollFrame:SetScript("OnSizeChanged", function()
            content:SetWidth(math.max((scrollFrame:GetWidth() or 0), 1))
            onSizeChanged()
        end)
    end

    return scrollFrame, content, scrollBar
end
core.CreateNativeScrollContainer = CreateNativeScrollContainer

local function SnapshotBool(value)
    return value and "1" or "0"
end

local function SnapshotValue(value)
    if value == nil then
        return "-"
    end

    return tostring(value)
end

local function SnapshotBestRuns(bestRuns)
    if type(bestRuns) ~= "table" or type(bestRuns.runs) ~= "table" then
        return ""
    end

    local parts = {
        SnapshotValue(bestRuns.score),
    }
    for _, run in ipairs(bestRuns.runs) do
        parts[#parts + 1] = table.concat({
            SnapshotValue(run.mapID),
            SnapshotValue(run.level),
            SnapshotValue(run.score),
            SnapshotValue(run.durationMS),
            SnapshotBool(run.timed),
        }, ":")
    end

    return table.concat(parts, ",")
end


local SaveWindowBounds

---@param widget table|Frame?
---@param statusTable table?
local function MakeFrameDraggable(widget, statusTable)
    local frame = widget and widget.frame
    if not frame then
        return
    end

    local dragHandle = CreateFrame("Frame", nil, frame)
    dragHandle:SetFrameLevel(frame:GetFrameLevel() + MAIN_WINDOW_DRAG_HANDLE_FRAME_LEVEL_OFFSET)
    dragHandle:SetPoint("TOPLEFT", frame, "TOPLEFT", MAIN_WINDOW_DRAG_HANDLE_LEFT_INSET, MAIN_WINDOW_DRAG_HANDLE_TOP_OFFSET)
    dragHandle:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", -MAIN_WINDOW_DRAG_HANDLE_RIGHT_INSET, MAIN_WINDOW_DRAG_HANDLE_BOTTOM_OFFSET)
    dragHandle:EnableMouse(true)
    dragHandle:RegisterForDrag("LeftButton")

    local function beginDrag()
        if frame.mkMainWindowDragging == true then
            return
        end
        frame.mkMainWindowDragging = true
        frame:StartMoving()
    end

    local function endDrag()
        if frame.mkMainWindowDragging ~= true then
            return
        end
        frame.mkMainWindowDragging = nil
        frame:StopMovingOrSizing()
        SaveWindowBounds(frame, statusTable)
    end

    dragHandle:SetScript("OnMouseDown", function(_, mouseButton)
        if mouseButton == "LeftButton" then
            beginDrag()
        end
    end)

    dragHandle:SetScript("OnMouseUp", function()
        endDrag()
    end)

    dragHandle:SetScript("OnDragStart", beginDrag)
    dragHandle:SetScript("OnDragStop", endDrag)
    dragHandle:SetScript("OnHide", endDrag)

    frame.mkMainWindowDragHandle = dragHandle
end

local function HideKeystoneTooltip()
    GameTooltip:Hide()
end
core.HideKeystoneTooltip = HideKeystoneTooltip

core.ResetCardSubFrameSkin = ResetCardSubFrameSkin

local function UpdateCharacterCardsScrollState(widgets)
    if not widgets or not widgets.cardsScrollFrame or not widgets.cardsContent or not widgets.cardsScrollBar then
        return
    end

    local scrollFrame = widgets.cardsScrollFrame
    local content = widgets.cardsContent
    local scrollBar = widgets.cardsScrollBar

    if scrollFrame.UpdateScrollChildRect then
        scrollFrame:UpdateScrollChildRect()
    end

    local viewHeight = scrollFrame:GetHeight() or 0
    local contentHeight = content:GetHeight() or 0
    local frameScrollRange = scrollFrame.GetVerticalScrollRange and scrollFrame:GetVerticalScrollRange() or 0
    local contentOverflow = math.max(contentHeight - viewHeight, 0)
    local hasScrollableContent = contentOverflow > CHARACTER_CARDS_SCROLL_EPSILON
    local maxScroll = hasScrollableContent and math.max(frameScrollRange, contentOverflow) or 0
    local currentValue
    if scrollBar.mkGetScrollValue then
        currentValue = scrollBar.mkGetScrollValue()
    elseif scrollBar.GetValue then
        currentValue = scrollBar:GetValue()
    else
        currentValue = 0
    end
    currentValue = math.min(currentValue or 0, maxScroll)

    if scrollBar.mkSetScrollRange then
        scrollBar.mkSetScrollRange(0, maxScroll)
    elseif scrollBar.SetMinMaxValues then
        scrollBar:SetMinMaxValues(0, maxScroll)
    end
    if scrollBar.SetVisibleExtentPercentage then
        local visibleExtentPercentage = 0
        if viewHeight > 0 then
            visibleExtentPercentage = math.min(math.max(viewHeight / (maxScroll + viewHeight), 0), 1)
        end
        scrollBar:SetVisibleExtentPercentage(visibleExtentPercentage)
    end
    if scrollBar.SetPanExtentPercentage then
        local panExtentPercentage = maxScroll > 0 and math.min(math.max(36 / maxScroll, 0), 1) or 0
        scrollBar:SetPanExtentPercentage(panExtentPercentage)
    end
    if scrollBar.SetScrollAllowed then
        scrollBar:SetScrollAllowed(hasScrollableContent)
    end
    if scrollBar.mkSetScrollValue then
        scrollBar.mkSetScrollValue(currentValue)
    elseif scrollBar.SetValue then
        scrollBar:SetValue(currentValue)
    end
    scrollFrame:SetVerticalScroll(currentValue)

    if hasScrollableContent then
        scrollBar:Show()
    else
        scrollBar:Hide()
        if scrollBar.mkSetScrollValue then
            scrollBar.mkSetScrollValue(0)
        elseif scrollBar.SetValue then
            scrollBar:SetValue(0)
        end
        scrollFrame:SetVerticalScroll(0)
    end

    content:SetWidth(math.max((scrollFrame:GetWidth() or 0), 1))
end
core.UpdateCharacterCardsScrollState = UpdateCharacterCardsScrollState

local function UpdateNativeScrollState(scrollFrame, content, scrollBar)
    if not scrollFrame or not content or not scrollBar then
        return
    end

    if scrollFrame.UpdateScrollChildRect then
        scrollFrame:UpdateScrollChildRect()
    end

    local viewHeight = scrollFrame:GetHeight() or 0
    local contentHeight = content:GetHeight() or 0
    local maxScroll = scrollFrame.GetVerticalScrollRange and scrollFrame:GetVerticalScrollRange() or math.max(contentHeight - viewHeight, 0)
    local currentValue = math.min(scrollBar:GetValue() or 0, maxScroll)

    scrollBar:SetMinMaxValues(0, maxScroll)
    scrollBar:SetValue(currentValue)
    scrollFrame:SetVerticalScroll(currentValue)

    if maxScroll > 0 then
        scrollBar:Show()
    else
        scrollBar:Hide()
        scrollBar:SetValue(0)
        scrollFrame:SetVerticalScroll(0)
    end

    content:SetWidth(math.max((scrollFrame:GetWidth() or 0), 1))
end

CreateCardEdge = function(parent, pointA, relToA, relPointA, xA, yA, pointB, relToB, relPointB, xB, yB, thickness)
    local edge = parent:CreateTexture(nil, "ARTWORK", nil, 1)
    edge:SetTexture("Interface\\Buttons\\WHITE8X8")
    edge:SetPoint(pointA, relToA, relPointA, xA, yA)
    edge:SetPoint(pointB, relToB, relPointB, xB, yB)
    if thickness then
        if pointA:find("TOP") or pointA:find("BOTTOM") then
            edge:SetHeight(thickness)
        else
            edge:SetWidth(thickness)
        end
    end
    return edge
end
core.CreateCardEdge = CreateCardEdge

local function CreateCharacterCardToggle(parent, labelText, width, onClick)
    local button = CreateFrame("Button", nil, parent)
    button:SetSize(width, 18)
    button:EnableMouse(true)

    local boxFrame = CreateFrame("Frame", nil, button, "BackdropTemplate")
    boxFrame:SetPoint("LEFT", button, "LEFT", 0, 0)
    boxFrame:SetSize(14, 14)
    if boxFrame.SetBackdrop then
        boxFrame:SetBackdrop({
            bgFile = nil,
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            edgeSize = 1,
        })
        boxFrame:SetBackdropColor(0, 0, 0, 0)
        boxFrame:SetBackdropBorderColor(1, 1, 1, 0.22)
    end

    local check = boxFrame:CreateTexture(nil, "OVERLAY")
    check:SetPoint("TOPLEFT", boxFrame, "TOPLEFT", 2, -2)
    check:SetPoint("BOTTOMRIGHT", boxFrame, "BOTTOMRIGHT", -2, 2)
    check:SetTexture("Interface\\Buttons\\WHITE8X8")
    check:SetVertexColor(1, 0.82, 0, 0.92)
    check:Hide()

    local label = button:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    label:SetPoint("LEFT", boxFrame, "RIGHT", 6, 0)
    label:SetPoint("RIGHT", button, "RIGHT", 0, 0)
    label:SetJustifyH("LEFT")
    label:SetText(labelText)
    if core.ApplyFont then
        core.ApplyFont(label, 12, "")
    end

    local function updateVisual()
        if button.disabled then
            label:SetTextColor(0.5, 0.5, 0.5, 1)
            if boxFrame.SetBackdropBorderColor then
                boxFrame:SetBackdropBorderColor(1, 1, 1, 0.14)
            end
        else
            label:SetTextColor(1, 1, 1, 0.92)
            if boxFrame.SetBackdropBorderColor then
                boxFrame:SetBackdropBorderColor(1, 1, 1, 0.22)
            end
        end

        if button.checked then
            if button.disabled then
                check:SetVertexColor(1, 0.82, 0, 0.78)
            else
                check:SetVertexColor(1, 0.82, 0, 0.92)
            end
        else
            if button.disabled then
                check:SetVertexColor(0.6, 0.6, 0.6, 0.18)
            else
                check:SetVertexColor(1, 0.82, 0, 0.92)
            end
        end

        if button.checked then
            check:Show()
        else
            check:Hide()
        end
    end

    button:SetScript("OnClick", function(self)
        if self.disabled then
            return
        end

        self.checked = not self.checked
        updateVisual()

        if onClick then
            onClick(self.checked)
        end
    end)

    button.SetChecked = function(self, checked)
        self.checked = checked and true or false
        updateVisual()
    end

    button.SetValue = button.SetChecked

    button.SetDisabledState = function(self, disabled)
        self.disabled = disabled and true or false
        updateVisual()
    end

    button.SetDisabled = button.SetDisabledState

    button.checked = false
    button.disabled = false
    updateVisual()

    button.boxFrame = boxFrame
    button.check = check
    button.label = label

    return button
end

core.CreateCharacterCardToggle = CreateCharacterCardToggle

local function ApplyRoleAtlasToToggle(toggle, roleKey)
    if not toggle then
        return
    end

    local atlasName = ROLE_ATLAS_BY_KEY[roleKey]
    if not atlasName then
        return
    end

    if toggle.label then
        toggle.label:SetText("")
    end

    local icon = toggle.roleIcon
    if not icon then
        icon = toggle:CreateTexture(nil, "OVERLAY")
        icon:SetPoint("LEFT", toggle.boxFrame, "RIGHT", 3, 0)
        icon:SetSize(20, 20)
        toggle.roleIcon = icon
    end

    if icon.SetAtlas then
        icon:SetAtlas(atlasName, false)
    else
        icon:SetTexture("Interface\\LFGFrame\\UI-LFG-ICON-ROLES")
    end
    icon:SetSize(20, 20)
    icon:Show()
end
core.ApplyRoleAtlasToToggle = ApplyRoleAtlasToToggle

local function CreateAtlasDisplay(parent, width, size, atlasName)
    local frame = CreateFrame("Frame", nil, parent)
    frame:SetSize(width, size)

    local icon = frame:CreateTexture(nil, "OVERLAY")
    icon:SetAllPoints(frame)
    if atlasName and icon.SetAtlas then
        icon:SetAtlas(atlasName, false)
    end
    icon:Hide()

    frame.icon = icon
    return frame
end

local function ApplyGroupRowsFrameStyle(frame)
    if not frame or frame.mkTransmogItemCardFrameStyled then
        return
    end

    if frame.SetBackdrop then
        frame:SetBackdrop(nil)
    end

    if frame.NineSlice then
        frame.NineSlice:Hide()
    end
    if frame.Background then
        frame.Background:Hide()
    end
    if frame.Bg then
        frame.Bg:Hide()
    end

    local bgColor = { 0, 0, 0, 0.58 }
    local function createSafeBackgroundPiece(pointA, relPointA, xA, yA, pointB, relPointB, xB, yB)
        local texture = frame:CreateTexture(nil, "BACKGROUND", nil, -7)
        texture:SetPoint(pointA, frame, relPointA, xA, yA)
        texture:SetPoint(pointB, frame, relPointB, xB, yB)
        texture:SetTexture("Interface\\Buttons\\WHITE8X8")
        texture:SetVertexColor(bgColor[1], bgColor[2], bgColor[3], bgColor[4])
        return texture
    end

    frame.mkTransmogItemCardFrameBackground = {
        center = createSafeBackgroundPiece("TOPLEFT", "TOPLEFT", 8, -16, "BOTTOMRIGHT", "BOTTOMRIGHT", -8, 16),
        top = createSafeBackgroundPiece("TOPLEFT", "TOPLEFT", 18, -8, "TOPRIGHT", "TOPRIGHT", -18, -16),
        bottom = createSafeBackgroundPiece("BOTTOMLEFT", "BOTTOMLEFT", 18, 8, "BOTTOMRIGHT", "BOTTOMRIGHT", -18, 16),
        left = createSafeBackgroundPiece("TOPLEFT", "TOPLEFT", 8, -18, "BOTTOMLEFT", "BOTTOMLEFT", 16, 18),
        right = createSafeBackgroundPiece("TOPRIGHT", "TOPRIGHT", -8, -18, "BOTTOMRIGHT", "BOTTOMRIGHT", -16, 18),
    }

    local border = frame:CreateTexture(nil, "ARTWORK", nil, 7)
    border:SetAllPoints(frame)
    if not TrySetAtlas(border, "transmog-itemcard-default", false) then
        border:SetTexture("Interface\\Buttons\\UI-Quickslot2")
    end
    border:SetVertexColor(1, 1, 1, 1)
    frame.mkTransmogItemCardFrameBorder = border

    frame.mkTransmogItemCardFrameStyled = true
end
core.ApplyGroupRowsFrameStyle = ApplyGroupRowsFrameStyle

local function ApplyGroupHeaderButtonStyle(button)
    if not button or button.mkGroupHeaderStyled then
        return
    end

    local fontString = button.Text or button:GetFontString()
    for _, region in ipairs({ button:GetRegions() }) do
        if region ~= fontString and region.SetTexture then
            region:SetTexture(nil)
            region:Hide()
        end
    end

    local function applyDisplayTypeButtonAtlas(texture, atlas)
        if not TrySetAtlas(texture, atlas, false) then
            return false
        end
        return true
    end

    local background = button:CreateTexture(nil, "BACKGROUND", nil, -2)
    background:SetAllPoints(button)
    if not applyDisplayTypeButtonAtlas(background, "common-button-tertiary-normal") then
        background:SetTexture("Interface\\Buttons\\WHITE8X8")
    end
    background:SetVertexColor(1, 1, 1, 1)
    button.mkTransmogHeaderBackground = background

    local pushed = button:CreateTexture(nil, "ARTWORK", nil, -1)
    pushed:SetPoint("TOPLEFT", background, "TOPLEFT", 0, 0)
    pushed:SetPoint("BOTTOMRIGHT", background, "BOTTOMRIGHT", 0, 0)
    if not applyDisplayTypeButtonAtlas(pushed, "common-button-tertiary-pressed") then
        pushed:SetTexture("Interface\\Buttons\\WHITE8X8")
    end
    button:SetPushedTexture(pushed)

    local highlight = button:CreateTexture(nil, "HIGHLIGHT", nil, -1)
    highlight:SetPoint("TOPLEFT", background, "TOPLEFT", 0, 0)
    highlight:SetPoint("BOTTOMRIGHT", background, "BOTTOMRIGHT", 0, 0)
    if not applyDisplayTypeButtonAtlas(highlight, "common-button-tertiary-normal") then
        highlight:SetTexture("Interface\\Buttons\\WHITE8X8")
    end
    highlight:SetBlendMode("ADD")
    highlight:SetAlpha(0.35)
    button:SetHighlightTexture(highlight)

    button.mkGroupHeaderStyled = true
end
core.ApplyGroupHeaderButtonStyle = ApplyGroupHeaderButtonStyle

local function SetScaledGroupHeaderVisualState(button)
    if not button then
        return
    end

    local enabled = not (button.IsEnabled and not button:IsEnabled())
    local pressed = enabled and button.mkScaledGroupHeaderPressed == true
    local hovered = enabled and button.IsMouseOver and button:IsMouseOver()
    button.mkTitleButtonPressedLayout = pressed

    local text = button.mkTitleButtonText
    local constants = addonTable and addonTable.MeetingStoneConstants
    local applyHeaderTextStyle = constants and constants.ApplyMeetingStoneHeaderTextStyle
    if text and applyHeaderTextStyle then
        applyHeaderTextStyle(text, button, {
            pressed = pressed,
            hovered = hovered,
            enabled = enabled,
            applyFont = core.ApplyFont,
        })
    end
end

local function UpdateScaledGroupHeaderButtonStyle(button, width, height)
    if not button then
        return
    end

    SetScaledGroupHeaderVisualState(button)
end
core.UpdateScaledGroupHeaderButtonStyle = UpdateScaledGroupHeaderButtonStyle

local function SetScaledGroupHeaderButtonText(button, text)
    if not button then
        return
    end

    button.mkTitleButtonText = text
    SetScaledGroupHeaderVisualState(button)
end
core.SetScaledGroupHeaderButtonText = SetScaledGroupHeaderButtonText

local function ApplyScaledGroupHeaderButtonStyle(button)
    if not button or button.mkScaledGroupHeaderStyled then
        return
    end

    local fontString = button.Text or button:GetFontString()
    for _, region in ipairs({ button:GetRegions() }) do
        if region ~= fontString and region.SetTexture then
            region:SetTexture(nil)
            region:Hide()
        end
    end

    button:HookScript("OnEnter", SetScaledGroupHeaderVisualState)
    button:HookScript("OnLeave", function(self)
        self.mkScaledGroupHeaderPressed = nil
        SetScaledGroupHeaderVisualState(self)
    end)
    button:HookScript("OnMouseDown", function(self, mouseButton)
        if mouseButton == "LeftButton" then
            self.mkScaledGroupHeaderPressed = true
            SetScaledGroupHeaderVisualState(self)
        end
    end)
    button:HookScript("OnMouseUp", function(self)
        self.mkScaledGroupHeaderPressed = nil
        SetScaledGroupHeaderVisualState(self)
    end)
    button:HookScript("OnEnable", SetScaledGroupHeaderVisualState)
    button:HookScript("OnDisable", SetScaledGroupHeaderVisualState)
    button:HookScript("OnShow", function(self)
        UpdateScaledGroupHeaderButtonStyle(self)
    end)

    button.mkScaledGroupHeaderStyled = true
    UpdateScaledGroupHeaderButtonStyle(button)
end
core.ApplyScaledGroupHeaderButtonStyle = ApplyScaledGroupHeaderButtonStyle


local FIXED_WINDOW_WIDTH = DEFAULT_WINDOW_WIDTH
local FIXED_WINDOW_HEIGHT = DEFAULT_WINDOW_HEIGHT

local function NormalizeMainWindowScale(value)
    value = tonumber(value) or MAIN_WINDOW_SCALE_DEFAULT
    value = math.max(MAIN_WINDOW_SCALE_MIN, math.min(MAIN_WINDOW_SCALE_MAX, value))
    return math.floor(value * 100 + 0.5) / 100
end

function MK:GetMainWindowScale()
    local profile = self.db and self.db.profile
    local window = profile and profile.window
    return NormalizeMainWindowScale(window and window.scale)
end

function MK:ApplyMainWindowScale()
    local frame = self.mainWindow and (self.mainWindow.frame or self.mainWindow)
    if frame and frame.SetScale then
        frame:SetScale(self:GetMainWindowScale())
    end
end

function MK:SetMainWindowScale(value)
    if not (self.db and self.db.profile) then
        return
    end

    self.db.profile.window = self.db.profile.window or {
        width = FIXED_WINDOW_WIDTH,
        height = FIXED_WINDOW_HEIGHT,
    }

    local normalizedValue = NormalizeMainWindowScale(value)
    if self.db.profile.window.scale == normalizedValue then
        self:ApplyMainWindowScale()
        return
    end

    self.db.profile.window.scale = normalizedValue
    self:ApplyMainWindowScale()

    if self.activeTab == "options" and self.RefreshMainWindow and self:IsMainWindowShown() then
        self:RefreshMainWindow("SetMainWindowScale", tostring(normalizedValue))
    end
end

local function GetWindowDimensions(statusTable)
    return FIXED_WINDOW_WIDTH, FIXED_WINDOW_HEIGHT
end

SaveWindowBounds = function(frame, statusTable)
    if not frame or not statusTable then
        return
    end

    statusTable.point = "CENTER"
    statusTable.x = 0
    statusTable.y = 0
    statusTable.top = nil
    statusTable.left = nil
    statusTable.width = FIXED_WINDOW_WIDTH
    statusTable.height = FIXED_WINDOW_HEIGHT
end

local function RestoreWindowBounds(frame, statusTable)
    if not frame then
        return
    end

    frame:ClearAllPoints()
    frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
end


function MK:ToggleMainWindow()
    if self:IsMainWindowShown() then
        self:HideMainWindow()
        return
    end

    self:ShowMainWindow(self:GetStartupTab(), true)
end

function MK:IsMainWindowShown()
    return self.mainWindow and self.mainWindow.frame and self.mainWindow.frame:IsShown()
end

function MK:HideMainWindow()
    local frame = self.mainWindow and (self.mainWindow.frame or self.mainWindow)
    if not frame then
        return
    end

    local wasShown = frame.IsShown and frame:IsShown()
    if frame.StopMovingOrSizing then
        frame:StopMovingOrSizing()
    end

    if self.DetachMeetingStoneNativeInputControls then
        self:DetachMeetingStoneNativeInputControls()
    end
    if self.DetachFindGroupNativeSearchBox then
        self:DetachFindGroupNativeSearchBox()
    end
    frame:Hide()
    if self.RefreshMicroMenuButtonState then
        self:RefreshMicroMenuButtonState()
    end
    if wasShown and PlaySound and SOUNDKIT and SOUNDKIT.IG_CHARACTER_INFO_CLOSE then
        PlaySound(SOUNDKIT.IG_CHARACTER_INFO_CLOSE)
    end
end

function MK:ShowMainWindow(openTab, deferContentBuild)
    if InCombatLockdown and InCombatLockdown() then
        if self.NotifyWindowClosedForCombat then
            self:NotifyWindowClosedForCombat()
        end
        return
    end

    if not self.mainWindow then
        self:CreateMainWindow()
    end

    local wasShown = self.mainWindow.IsShown and self.mainWindow:IsShown()
    if not wasShown then
        core.ResetGroupSortState(self, "group")
    end
    self.mainWindow:Show()
    if self.RefreshMicroMenuButtonState then
        self:RefreshMicroMenuButtonState()
    end
    if not wasShown and PlaySound and SOUNDKIT and SOUNDKIT.IG_CHARACTER_INFO_OPEN then
        PlaySound(SOUNDKIT.IG_CHARACTER_INFO_OPEN)
    end
    if self.mainWindow and self.mainWindow.contentAdapter then
        local targetTab = openTab or self:GetStartupTab()
        local hasActiveEntry = false
        if not openTab and C_LFGList and C_LFGList.HasActiveEntryInfo then
            local ok, activeEntry = pcall(C_LFGList.HasActiveEntryInfo)
            hasActiveEntry = ok and activeEntry == true
        end
        if not openTab and hasActiveEntry then
            targetTab = "meetingstone"
        end
        targetTab = NormalizeMainTabSelection(self, targetTab)
        if deferContentBuild and not wasShown and C_Timer and C_Timer.After then
            self.mkMainWindowDeferredOpenTicket = (self.mkMainWindowDeferredOpenTicket or 0) + 1
            local ticket = self.mkMainWindowDeferredOpenTicket
            C_Timer.After(0, function()
                if self.mkMainWindowDeferredOpenTicket == ticket and self:IsMainWindowShown() then
                    self:SelectMainTab(targetTab)
                end
            end)
        else
            self:SelectMainTab(targetTab)
        end
    end
end

function MK:OpenMainWindowAtStartupTab()
    self:ShowMainWindow(self:GetStartupTab(), true)
end

function MK:OpenMainWindowAtRightClickStartupTab()
    local targetTab = self.GetRightClickStartupTab and self:GetRightClickStartupTab() or self:GetStartupTab()
    self:ShowMainWindow(targetTab, true)
end

function MK:ToggleMainWindowAtTab(targetTab)
    if self:IsMainWindowShown() and self.activeTab == targetTab then
        self:HideMainWindow()
        return
    end

    self:ShowMainWindow(targetTab, true)
end

function MK:ToggleMainWindowAtStartupTab()
    self:ToggleMainWindowAtTab(self:GetStartupTab())
end

function MK:ToggleMainWindowAtRightClickStartupTab()
    local targetTab = self.GetRightClickStartupTab and self:GetRightClickStartupTab() or self:GetStartupTab()
    self:ToggleMainWindowAtTab(targetTab)
end

function MK:OpenOptionsTab()
    if InCombatLockdown and InCombatLockdown() then
        if self.NotifyWindowClosedForCombat then
            self:NotifyWindowClosedForCombat()
        end
        return
    end

    if self:IsMainWindowShown() then
        self:SelectMainTab("options")
    else
        self:ShowMainWindow("options", true)
    end
end

local function AnchorMainWindowMeetingStoneRefreshButton(frame, widgets)
    local button = frame and frame.meetingStoneRefreshButton
    if not button then
        return false
    end

    local scrollBar = widgets and widgets.meetingStoneApplicantScrollBar
    if not scrollBar then
        button:Hide()
        return false
    end

    button:ClearAllPoints()
    button:SetPoint("BOTTOM", scrollBar, "TOP", 0, 6)
    return true
end

local function UpdateMainWindowTopRightButtonMode(frame, activeTab, widgets)
    if not frame then
        return
    end

    if frame.greatVaultButton then
        frame.greatVaultButton:Show()
    end
    if frame.meetingStoneRefreshButton then
        local showMeetingStoneRefresh = activeTab == "meetingstone"
        local anchored = showMeetingStoneRefresh and AnchorMainWindowMeetingStoneRefreshButton(frame, widgets)
        frame.meetingStoneRefreshButton:SetShown(anchored == true)
    end
    if frame.groupScrollBarFrame then
        frame.groupScrollBarFrame:SetShown(activeTab == "group" or activeTab == "carpool")
    end
end

local function IsGreatVaultRewardReady()
    if type(C_WeeklyRewards) ~= "table" then
        return false
    end

    local hasAvailableRewards = false
    if C_WeeklyRewards.HasAvailableRewards then
        local ok, value = pcall(C_WeeklyRewards.HasAvailableRewards)
        hasAvailableRewards = ok and value == true
    end

    if hasAvailableRewards then
        return true
    end

    if C_WeeklyRewards.CanClaimRewards then
        local ok, value = pcall(C_WeeklyRewards.CanClaimRewards)
        return ok and value == true
    end

    return false
end

local function SetGreatVaultReadyBorderFrame(texture, frameIndex)
    if not texture then
        return
    end

    frameIndex = (frameIndex or 0) % GREAT_VAULT_READY_BORDER_FRAME_COUNT
    local column = frameIndex % GREAT_VAULT_READY_BORDER_COLUMNS
    local row = math.floor(frameIndex / GREAT_VAULT_READY_BORDER_COLUMNS)

    texture:SetTexCoord(
        column / GREAT_VAULT_READY_BORDER_COLUMNS,
        (column + 1) / GREAT_VAULT_READY_BORDER_COLUMNS,
        row / GREAT_VAULT_READY_BORDER_ROWS,
        (row + 1) / GREAT_VAULT_READY_BORDER_ROWS
    )
end

local function StopGreatVaultReadyBorderAnimation(button)
    local borderFrame = button and button.greatVaultReadyBorderFrame
    if not borderFrame then
        return
    end

    borderFrame.mkGreatVaultBorderAnimating = nil
    borderFrame:SetScript("OnUpdate", nil)
    borderFrame:Hide()
end

local function StartGreatVaultReadyBorderAnimation(button)
    local borderFrame = button and button.greatVaultReadyBorderFrame
    local texture = borderFrame and borderFrame.texture
    if not (borderFrame and texture) then
        return
    end

    borderFrame:Show()
    if borderFrame.mkGreatVaultBorderAnimating then
        return
    end

    borderFrame.mkGreatVaultBorderAnimating = true
    borderFrame.mkGreatVaultBorderElapsed = 0
    borderFrame.mkGreatVaultBorderFrameIndex = 0
    SetGreatVaultReadyBorderFrame(texture, 0)
    borderFrame:SetScript("OnUpdate", function(frame, elapsed)
        frame.mkGreatVaultBorderElapsed = (frame.mkGreatVaultBorderElapsed or 0) + elapsed
        local frameDuration = 1 / GREAT_VAULT_READY_BORDER_FPS
        if frame.mkGreatVaultBorderElapsed < frameDuration then
            return
        end

        local steps = math.floor(frame.mkGreatVaultBorderElapsed / frameDuration)
        frame.mkGreatVaultBorderElapsed = frame.mkGreatVaultBorderElapsed - (steps * frameDuration)
        frame.mkGreatVaultBorderFrameIndex = ((frame.mkGreatVaultBorderFrameIndex or 0) + steps) % GREAT_VAULT_READY_BORDER_FRAME_COUNT
        SetGreatVaultReadyBorderFrame(frame.texture, frame.mkGreatVaultBorderFrameIndex)
    end)
end

local function EnsureGreatVaultReadyHighlight(button)
    if not button or button.greatVaultReadyBorderFrame then
        return
    end

    local borderFrame = CreateFrame("Frame", nil, button)
    borderFrame:SetPoint("CENTER", button, "CENTER")
    borderFrame:SetSize(GREAT_VAULT_READY_BORDER_SIZE, GREAT_VAULT_READY_BORDER_SIZE)
    borderFrame:SetFrameLevel(button:GetFrameLevel() + 8)
    borderFrame:Hide()

    local border = borderFrame:CreateTexture(nil, "OVERLAY")
    border:SetAllPoints(borderFrame)
    border:SetTexture(GREAT_VAULT_READY_BORDER_TEXTURE)
    border:SetBlendMode("BLEND")
    border:SetAlpha(GREAT_VAULT_READY_BORDER_ALPHA)
    SetGreatVaultReadyBorderFrame(border, 0)

    borderFrame.texture = border
    button.greatVaultReadyBorderFrame = borderFrame
end

local function UpdateGreatVaultReadyHighlight(button, rewardReady)
    EnsureGreatVaultReadyHighlight(button)
    if not button or not button.greatVaultReadyBorderFrame then
        return
    end

    rewardReady = rewardReady == true
    button.greatVaultRewardReady = rewardReady

    if rewardReady then
        StartGreatVaultReadyBorderAnimation(button)
    else
        StopGreatVaultReadyBorderAnimation(button)
    end
end

function MK:SelectMainTab(group)
    if not self.mainWindow or not self.mainWindow.contentAdapter then
        return
    end

    group = NormalizeMainTabSelection(self, group)

    if InCombatLockdown and InCombatLockdown() then
        if self:IsMainWindowShown() then
            self:HideMainWindow()
            if self.NotifyWindowClosedForCombat then
                self:NotifyWindowClosedForCombat()
            end
        end
        return
    end

    local previousGroup = self.activeTab
    if previousGroup == "meetingstone" and group ~= "meetingstone" and self.DetachMeetingStoneNativeInputControls then
        self:DetachMeetingStoneNativeInputControls()
    end
    if previousGroup == "findgroup" and group ~= "findgroup" and self.DetachFindGroupNativeSearchBox then
        self:DetachFindGroupNativeSearchBox()
    end
    self.activeTab = group
    if group == "carpool" and previousGroup ~= "carpool" and core.ResetGroupSortState then
        core.ResetGroupSortState(self, "carpool")
    end
    if previousGroup and previousGroup ~= group then
        PlaySystemOptionsTabSound()
    end

    local oldButton = nil
    local newButton = nil
    if self.mainWindow.tabButtons then
        for tabValue, button in pairs(self.mainWindow.tabButtons) do
            UpdateNativeTabButton(button, tabValue == group)
            if tabValue == previousGroup then
                oldButton = button
            end
            if tabValue == group then
                newButton = button
            end
        end
    end

    local tabId = self.mainWindow.tabIdByGroup and self.mainWindow.tabIdByGroup[group]
    if tabId then
        self.mainWindow.selectedTab = tabId
    end

    if self.mainWindow.tabsBar and newButton then
        UpdateTabActiveIndicator(self.mainWindow.tabsBar, oldButton, newButton)
    end

    self:BuildSelectedTab(self.mainWindow.contentAdapter, group)
    UpdateMainWindowTopRightButtonMode(self.mainWindow, group, self.widgets)
end

function MK:RefreshMainTabVisibility()
    if not self.mainWindow then
        return
    end

    LayoutMainTabButtons(self.mainWindow, self)
    if self.activeTab == "debug" and not (self.IsDebugModeEnabled and self:IsDebugModeEnabled()) then
        self:SelectMainTab("options")
        return
    end

    if self.mainWindow.tabButtons then
        for tabValue, button in pairs(self.mainWindow.tabButtons) do
            UpdateNativeTabButton(button, tabValue == self.activeTab)
        end
    end

    local activeButton = self.mainWindow.tabButtons and self.mainWindow.tabButtons[self.activeTab]
    if self.mainWindow.tabsBar and activeButton and activeButton:IsShown() then
        UpdateTabActiveIndicator(self.mainWindow.tabsBar, activeButton, activeButton)
    end
end

function MK:CreateMainWindow()
    local frame = CreateFrameWithTemplateOptions("Frame", "MKMainWindow", UIParent, {
        "PortraitFrameTemplate",
        "SettingsFrameTemplate",
        "DefaultPanelTemplate",
    })
    frame.frame = frame
    frame:SetFrameStrata(MAIN_WINDOW_FRAME_STRATA)
    frame:SetFrameLevel(MAIN_WINDOW_FRAME_LEVEL)
    if frame.SetToplevel then
        frame:SetToplevel(true)
    end
    frame:SetClampedToScreen(true)
    frame:SetMovable(true)
    frame:SetResizable(false)
    frame:EnableMouse(true)
    frame:Hide()
    frame:SetScript("OnShow", function(window)
        self:ApplyMainWindowScale()
        RestoreWindowBounds(window, self.db.profile.window)
        window:SetFrameStrata(MAIN_WINDOW_FRAME_STRATA)
        window:SetFrameLevel(MAIN_WINDOW_FRAME_LEVEL)
    end)

    self.db.profile.window = self.db.profile.window or {
        width = FIXED_WINDOW_WIDTH,
        height = FIXED_WINDOW_HEIGHT,
    }
    local windowWidth, windowHeight = GetWindowDimensions(self.db.profile.window)
    frame:SetSize(windowWidth, windowHeight)
    frame:SetScale(self:GetMainWindowScale())
    RestoreWindowBounds(frame, self.db.profile.window)

    MakeFrameDraggable(frame, self.db.profile.window)
    frame:HookScript("OnHide", function(window)
        if self.DetachMeetingStoneNativeInputControls then
            self:DetachMeetingStoneNativeInputControls()
        end
        if self.DetachFindGroupNativeSearchBox then
            self:DetachFindGroupNativeSearchBox()
        end
        SaveWindowBounds(window, self.db.profile.window)
        if self.RefreshMicroMenuButtonState then
            self:RefreshMicroMenuButtonState()
        end
    end)

    if type(UISpecialFrames) == "table" then
        table.insert(UISpecialFrames, frame:GetName())
    end

    local titleText = (frame.NineSlice and frame.NineSlice.Text) or frame.TitleText or (frame.TitleContainer and frame.TitleContainer.TitleText) or _G.MKMainWindowTitleText
    if titleText then
        ApplySystemPanelTitleStyle(titleText)
        frame.systemTitleText = titleText
        frame.titletext = titleText
    end
    UpdateMainWindowTitle(frame)

    local closeButton = frame.ClosePanelButton or frame.CloseButton or _G.MKMainWindowCloseButton
    if not closeButton then
        closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButtonDefaultAnchors")
    end
    closeButton:SetScript("OnClick", function()
        self:HideMainWindow()
    end)

    if frame.Bg and frame.Bg.Hide then
        frame.Bg:Hide()
    end

    if frame.mainWindowBackground and frame.mainWindowBackground.Hide then
        frame.mainWindowBackground:Hide()
    end

    if not frame.mainWindowBackgroundPieces then
        frame.mainWindowBackgroundPieces = {}
        for index = 1, 3 do
            frame.mainWindowBackgroundPieces[index] = frame:CreateTexture(nil, "BACKGROUND", nil, 0)
        end
    end

    local backgroundCornerRadius = 1
    local backgroundInset = 2
    local backgroundAlpha = 0.82
    for _, backgroundPiece in ipairs(frame.mainWindowBackgroundPieces) do
        backgroundPiece:ClearAllPoints()
        backgroundPiece:SetDrawLayer("BACKGROUND", -8)
        backgroundPiece:SetColorTexture(0, 0, 0, backgroundAlpha)
        backgroundPiece:SetVertexColor(1, 1, 1, 1)
        backgroundPiece:Show()
    end

    local topBackground = frame.mainWindowBackgroundPieces[1]
    topBackground:SetPoint("TOPLEFT", frame, "TOPLEFT", backgroundInset + backgroundCornerRadius, -backgroundInset)
    topBackground:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -(backgroundInset + backgroundCornerRadius), -backgroundInset)
    topBackground:SetHeight(backgroundCornerRadius)

    local middleBackground = frame.mainWindowBackgroundPieces[2]
    middleBackground:SetPoint("TOPLEFT", frame, "TOPLEFT", backgroundInset, -(backgroundInset + backgroundCornerRadius))
    middleBackground:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -backgroundInset, backgroundInset + backgroundCornerRadius)

    local bottomBackground = frame.mainWindowBackgroundPieces[3]
    bottomBackground:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", backgroundInset + backgroundCornerRadius, backgroundInset)
    bottomBackground:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -(backgroundInset + backgroundCornerRadius), backgroundInset)
    bottomBackground:SetHeight(backgroundCornerRadius)

    if frame.SetPortraitTextureRaw then
        frame:SetPortraitTextureRaw(ADDON_PANEL_PORTRAIT_PATH)
    elseif frame.SetPortraitToAsset then
        frame:SetPortraitToAsset(ADDON_PANEL_PORTRAIT_PATH)
    elseif frame.PortraitContainer and frame.PortraitContainer.portrait then
        frame.PortraitContainer.portrait:SetTexture(ADDON_PANEL_PORTRAIT_PATH)
    end
    if frame.SetPortraitTexCoord then
        frame:SetPortraitTexCoord(0, 1, 0, 1)
    end
    if frame.PortraitContainer and frame.PortraitContainer.portrait then
        local portraitContainer = frame.PortraitContainer
        local portrait = portraitContainer.portrait
        portrait:ClearAllPoints()
        portrait:SetSize(63, 63)
        portrait:SetPoint("TOPLEFT", portraitContainer, "TOPLEFT", -6, 8.5)
        if portraitContainer.CircleMask then
            portraitContainer.CircleMask:ClearAllPoints()
            portraitContainer.CircleMask:SetPoint("TOPLEFT", portraitContainer, "TOPLEFT", -3, 7)
            portraitContainer.CircleMask:SetPoint("BOTTOMRIGHT", portraitContainer, "TOPLEFT", 55, -51)
        end
    end

    local panelBackplate = CreateFrameWithTemplate("Frame", nil, frame, "BackdropTemplate")
    panelBackplate:SetPoint("TOPLEFT", frame, "TOPLEFT", MAIN_PANEL_INSET_LEFT, -MAIN_PANEL_INSET_TOP)
    panelBackplate:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -MAIN_PANEL_INSET_RIGHT, MAIN_PANEL_INSET_BOTTOM)
    panelBackplate:SetFrameLevel(frame:GetFrameLevel() + 11)
    ApplySystemOptionsBackplateStyle(panelBackplate)
    frame.panelBackplate = panelBackplate

    local groupScrollBarFrame = CreateFrame("Frame", "MKMainWindowGroupScrollBarFrame", frame)
    groupScrollBarFrame:SetFrameLevel(panelBackplate:GetFrameLevel() + 8)
    groupScrollBarFrame:SetWidth(GROUP_LIST_SCROLLBAR_SLOT_WIDTH)
    groupScrollBarFrame:EnableMouse(false)
    groupScrollBarFrame:Hide()
    frame.groupScrollBarFrame = groupScrollBarFrame

    local tabsBar = CreateFrame("Frame", "MKMainWindowTabsBar", frame)
    tabsBar.useSystemTabs = true
    tabsBar:SetFrameLevel(panelBackplate:GetFrameLevel() - 2)
    tabsBar.selectedHighlightFrameLevel = panelBackplate:GetFrameLevel() + 20
    tabsBar:SetPoint("TOPLEFT", frame, "TOPLEFT", MAIN_PANEL_INSET_LEFT + 32, -(MAIN_PANEL_INSET_TOP - 35 + 12))
    tabsBar:SetSize(520, MAIN_PANEL_TAB_HEIGHT)

    local function updateGreatVaultButtonState(button)
        local currentDisplaySeason = C_SeasonInfo and C_SeasonInfo.GetCurrentDisplaySeasonID and C_SeasonInfo.GetCurrentDisplaySeasonID()
        button.hasActiveSeason = currentDisplaySeason and currentDisplaySeason > 0
        button.hasGreatVaultRewardReady = button.hasActiveSeason and IsGreatVaultRewardReady()
        if button.NormalTexture and button.NormalTexture.SetDesaturated then
            button.NormalTexture:SetDesaturated(not button.hasActiveSeason)
        end
        UpdateGreatVaultReadyHighlight(
            button,
            button.hasGreatVaultRewardReady and (not button.IsVisible or button:IsVisible())
        )
    end

    local function addTooltipLine(methodName, fallbackMethodName, text, r, g, b, wrap)
        if not text then
            return
        end

        local tooltipMethod = _G[methodName]
        if tooltipMethod then
            tooltipMethod(GameTooltip, text, wrap)
            return
        end

        local fallbackMethod = _G[fallbackMethodName]
        if fallbackMethod then
            fallbackMethod(GameTooltip, text, wrap)
            return
        end

        GameTooltip:AddLine(text, r or 1, g or 1, b or 1, wrap)
    end

    local function createGreatVaultButton(parent)
        local button = CreateFrame("Button", "MKMainWindowGreatVaultButton", parent)
        button:SetSize(GREAT_VAULT_BUTTON_SIZE, GREAT_VAULT_BUTTON_SIZE)
        button:SetFrameLevel(panelBackplate:GetFrameLevel() + 6)

        local normalTexture = button:CreateTexture(nil, "ARTWORK")
        normalTexture:SetAllPoints(button)
        if not TrySetAtlas(normalTexture, "ui-journeys-greatvault-button", false) then
            normalTexture:SetTexture("Interface\\Icons\\INV_TreasureVault_Key01")
            normalTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        end
        button:SetNormalTexture(normalTexture)
        button.NormalTexture = normalTexture

        local pushedTexture = button:CreateTexture(nil, "ARTWORK")
        pushedTexture:SetAllPoints(button)
        if not TrySetAtlas(pushedTexture, "ui-journeys-greatvault-button-pressed", false) then
            pushedTexture:SetTexture("Interface\\Buttons\\UI-Quickslot-Depress")
        end
        pushedTexture:SetBlendMode("ADD")
        button:SetPushedTexture(pushedTexture)
        button.PushedTexture = pushedTexture

        if AlphaHighlightButtonMixin
            and Mixin
            and normalTexture.GetAtlas
            and pushedTexture.GetAtlas
            and normalTexture:GetAtlas()
            and pushedTexture:GetAtlas()
        then
            Mixin(button, AlphaHighlightButtonMixin)
            button:SetScript("OnMouseDown", button.OnMouseDown)
            button:SetScript("OnMouseUp", button.OnMouseUp)
            button:UpdateHighlightForState()
        end

        button:SetScript("OnShow", updateGreatVaultButtonState)
        button:SetScript("OnHide", function(hiddenButton)
            UpdateGreatVaultReadyHighlight(hiddenButton, false)
        end)
        button:SetScript("OnEvent", function(eventButton)
            updateGreatVaultButtonState(eventButton)
        end)
        button:RegisterEvent("PLAYER_ENTERING_WORLD")
        button:RegisterEvent("WEEKLY_REWARDS_UPDATE")
        button:RegisterEvent("WEEKLY_REWARDS_ITEM_CHANGED")
        button:SetScript("OnClick", function(clickedButton)
            updateGreatVaultButtonState(clickedButton)
            if clickedButton.hasActiveSeason then
                self:OpenGreatVaultFrame()
            end
        end)
        button:SetScript("OnEnter", function(hoveredButton)
            updateGreatVaultButtonState(hoveredButton)
            GameTooltip:SetOwner(hoveredButton, "ANCHOR_RIGHT")

            if GameTooltip_SetTitle then
                GameTooltip_SetTitle(GameTooltip, GREAT_VAULT_REWARDS or TRANSLATE("宏伟宝库"))
            else
                GameTooltip:SetText(GREAT_VAULT_REWARDS or TRANSLATE("宏伟宝库"), 1, 0.82, 0, true)
            end

            if hoveredButton.hasActiveSeason then
                addTooltipLine("GameTooltip_AddNormalLine", "GameTooltip_AddHighlightLine", JOURNEYS_GREAT_VAULT_TOOLTIP or TRANSLATE("打开宏伟宝库"), 1, 1, 1, true)
                addTooltipLine("GameTooltip_AddInstructionLine", "GameTooltip_AddNormalLine", WEEKLY_REWARDS_CLICK_TO_PREVIEW_INSTRUCTIONS, 0.8, 0.8, 0.8, true)
            else
                addTooltipLine("GameTooltip_AddDisabledLine", "GameTooltip_AddNormalLine", UNAVAILABLE, 0.5, 0.5, 0.5, true)
                addTooltipLine("GameTooltip_AddNormalLine", "GameTooltip_AddHighlightLine", GREAT_VAULT_REQUIRES_ACTIVE_SEASON or TRANSLATE("打开宏伟宝库"), 1, 1, 1, true)
            end

            GameTooltip:Show()
        end)
        button:SetScript("OnLeave", function(hoveredButton)
            if GameTooltip:GetOwner() == hoveredButton then
                GameTooltip:Hide()
            end
        end)

        updateGreatVaultButtonState(button)
        return button
    end

    local function createMeetingStoneRefreshButton(parent)
        local button = CreateFrame("Button", "MKMainWindowMeetingStoneRefreshButton", parent)
        button:SetSize(35, 35)
        button:SetFrameLevel(panelBackplate:GetFrameLevel() + 6)

        local function setRefreshIconPressed(pressedButton, pressed)
            if not pressedButton.Icon then
                return
            end

            pressedButton.Icon:ClearAllPoints()
            if pressed then
                pressedButton.Icon:SetSize(19, 19)
                pressedButton.Icon:SetPoint("CENTER", pressedButton, "CENTER", 1, -1)
                pressedButton.Icon:SetAlpha(0.86)
            else
                pressedButton.Icon:SetSize(21, 21)
                pressedButton.Icon:SetPoint("CENTER", pressedButton, "CENTER", 0, 0)
                pressedButton.Icon:SetAlpha(1)
            end
        end

        local icon = button:CreateTexture(nil, "OVERLAY")
        icon:SetSize(21, 21)
        icon:SetPoint("CENTER", button, "CENTER", 0, 0)
        icon:SetTexture(MEETINGSTONE_REFRESH_BUTTON_TEXTURE)
        icon:SetTexCoord(0, 1, 0, 1)
        button.Icon = icon

        button:SetScript("OnClick", function()
            if core.PlayPanelButtonClickSound then
                core.PlayPanelButtonClickSound()
            end
            local meetingStoneLayout = core.MeetingStoneLayout
            if meetingStoneLayout and meetingStoneLayout.QueuePublishedDataRefresh then
                meetingStoneLayout.QueuePublishedDataRefresh(self)
            elseif self.RefreshMainWindow then
                self:RefreshMainWindow("MeetingStoneApplicantRefresh")
            end
        end)
        button:SetScript("OnEnter", function(hoveredButton)
            GameTooltip:SetOwner(hoveredButton, "ANCHOR_RIGHT")
            if GameTooltip_SetTitle then
                GameTooltip_SetTitle(GameTooltip, TRANSLATE("申请列表刷新"))
            else
                GameTooltip:SetText(TRANSLATE("申请列表刷新"), 1, 0.82, 0, true)
            end
            addTooltipLine("GameTooltip_AddNormalLine", "GameTooltip_AddHighlightLine", TRANSLATE("刷新集合石申请列表"), 1, 1, 1, true)
            GameTooltip:Show()
        end)
        button:SetScript("OnMouseDown", function(clickedButton, mouseButton)
            if mouseButton == "LeftButton" then
                setRefreshIconPressed(clickedButton, true)
            end
        end)
        button:SetScript("OnMouseUp", function(clickedButton)
            setRefreshIconPressed(clickedButton, false)
        end)
        button:SetScript("OnLeave", function(hoveredButton)
            setRefreshIconPressed(hoveredButton, false)
            if GameTooltip:GetOwner() == hoveredButton then
                GameTooltip:Hide()
            end
        end)

        return button
    end

    local greatVaultButton = createGreatVaultButton(frame)
    greatVaultButton:SetPoint("TOPRIGHT", panelBackplate, "TOPRIGHT", 0, 35)
    local meetingStoneRefreshButton = createMeetingStoneRefreshButton(frame)
    meetingStoneRefreshButton:Hide()

    local contentAdapter = CreateNativeContentAdapter(
        panelBackplate,
        MAIN_PANEL_CONTENT_PADDING_X,
        MAIN_PANEL_CONTENT_PADDING_TOP,
        MAIN_PANEL_CONTENT_PADDING_X,
        MAIN_PANEL_CONTENT_PADDING_BOTTOM
    )

    local tabDefinitions = core.GetMainTabDefinitions and core.GetMainTabDefinitions(true) or {}
    local tabButtons = {}
    frame.Tabs = {}
    for index, definition in ipairs(tabDefinitions) do
        local button = CreateNativeTabButton(tabsBar, TRANSLATE(definition.text), index)
        local tabKey = definition.key
        tabButtons[definition.key] = button
        button:SetScript("OnClick", function()
            self:SelectMainTab(tabKey)
        end)
    end

    self.mainWindow = frame
    self.widgets = {
        tabGroup = contentAdapter,
        groupRows = {},
    }
    frame.contentAdapter = contentAdapter
    frame.tabDefinitions = tabDefinitions
    frame.tabButtons = tabButtons
    frame.tabsBar = tabsBar
    frame.greatVaultButton = greatVaultButton
    frame.meetingStoneRefreshButton = meetingStoneRefreshButton
    LayoutMainTabButtons(frame, self)

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

    self.activeTab = NormalizeMainTabSelection(self, self.activeTab or self:GetStartupTab())
    UpdateMainWindowTopRightButtonMode(frame, self.activeTab, self.widgets)
end

---@param tabGroup table
---@param group string
function MK:BuildSelectedTab(tabGroup, group)
    tabGroup:ReleaseChildren()
    self.tabViews = self.tabViews or {}
    self.lastRenderedSnapshotByTab = self.lastRenderedSnapshotByTab or {}
    self.lastRenderedSnapshotByTab[group] = nil

    local existingView = self.tabViews[group]
    if existingView and existingView.root then
        existingView.tabGroup = tabGroup
        self.widgets = existingView
        tabGroup:AddChild(existingView.root)

        ApplyThemeToTabGroup(tabGroup)
        if core.ApplyFontToFrame and not existingView.skipApplyFontToFrame then
            core.ApplyFontToFrame(existingView.root.frame or existingView.root)
        end

        self:RefreshMainWindow("BuildSelectedTab", group)
        return
    end

    if group == "dungeons" then
        if core.DungeonTab and core.DungeonTab.Build then
            core.DungeonTab.Build(self, tabGroup)
        end
        return
    end

    if group == "group" then
        if core.GroupTab and core.GroupTab.Build then
            core.GroupTab.Build(self, tabGroup)
        end
        return
    end

    if group == "carpool" then
        if core.CarpoolTab and core.CarpoolTab.Build then
            core.CarpoolTab.Build(self, tabGroup)
        end
        return
    end

    if group == "meetingstone" then
        if core.MeetingStoneTab and core.MeetingStoneTab.Build then
            core.MeetingStoneTab.Build(self, tabGroup)
        end
        return
    end

    if group == "findgroup" then
        if core.FindGroupTab and core.FindGroupTab.Build then
            core.FindGroupTab.Build(self, tabGroup)
        end
        return
    end

    if group == "blacklist" then
        if core.BlacklistTab and core.BlacklistTab.Build then
            core.BlacklistTab.Build(self, tabGroup)
        end
        return
    end

    if group == "options" then
        if core.OptionsTab and core.OptionsTab.Build then
            core.OptionsTab.Build(self, tabGroup)
        end
        return
    end

    if group == "debug" then
        if core.DebugTab and core.DebugTab.Build then
            core.DebugTab.Build(self, tabGroup)
        end
        return
    end

    if core.CharacterTab and core.CharacterTab.Build then
        core.CharacterTab.Build(self, tabGroup)
    end
end



function MK:BuildCharactersTabSnapshot()
    local parts = {}

    for _, characterEntry in ipairs(self:GetCharacters()) do
        local character = characterEntry.data or {}
        local roles = self:NormalizeRoles(character)
        parts[#parts + 1] = table.concat({
            SnapshotValue(characterEntry.key),
            SnapshotValue(character.name),
            SnapshotValue(character.class),
            SnapshotValue(character.keyLevel),
            SnapshotValue(character.mapID),
            SnapshotValue(character.dungeonName),
            SnapshotValue(character.keystoneLink),
            SnapshotBool(character.hasKeystone),
            SnapshotValue(character.rating),
            SnapshotBool(self:IsCharacterShownInGroup(character)),
            SnapshotBool(roles.TANK),
            SnapshotBool(roles.HEAL),
            SnapshotBool(roles.DPS),
            SnapshotValue(SnapshotBestRuns(character.bestRuns)),
        }, "~")
    end

    return table.concat(parts, "||")
end

function MK:GetActiveTabSnapshot()
    if self.activeTab == "options" then
        return self:BuildOptionsTabSnapshot()
    end

    if self.activeTab == "debug" then
        if core.DebugTab and core.DebugTab.BuildSnapshot then
            return core.DebugTab.BuildSnapshot(self)
        end
        return "debug"
    end

    if self.activeTab == "meetingstone" then
        return self:BuildMeetingStoneTabSnapshot()
    end

    if self.activeTab == "findgroup" then
        if core.FindGroupTab and core.FindGroupTab.BuildSnapshot then
            return core.FindGroupTab.BuildSnapshot(self)
        end
        return ""
    end

    if self.activeTab == "blacklist" then
        if core.BlacklistTab and core.BlacklistTab.BuildSnapshot then
            return core.BlacklistTab.BuildSnapshot(self)
        end
        return ""
    end

    if self.activeTab == "dungeons" then
        return self:BuildDungeonsTabSnapshot()
    end

    if self.activeTab == "group" then
        return self:BuildGroupTabSnapshot()
    end

    if self.activeTab == "carpool" then
        return self:BuildCarpoolTabSnapshot()
    end

    return self:BuildCharactersTabSnapshot()
end

function MK:RefreshMainWindow(refreshSource, refreshDetail)
    if not self.mainWindow or not self.widgets then
        return
    end

    ---@type BKWidgets
    local widgets = self.widgets

    if not self:IsMainWindowShown() then
        return
    end

    self.lastRenderedSnapshotByTab = self.lastRenderedSnapshotByTab or {}
    local currentTab = self.activeTab or "characters"
    local applicantOnlyRefresh = type(refreshSource) == "string"
        and refreshSource:find("^MeetingStoneApplicant") ~= nil
    if currentTab == "meetingstone" and refreshSource == "NetEaseAPI" then
        applicantOnlyRefresh = true
    end
    local protectedMeetingStoneFormRefresh = currentTab == "meetingstone"
        and self.ShouldPreserveMeetingStoneManagedFormText
        and self:ShouldPreserveMeetingStoneManagedFormText()
        and (
            refreshSource == "MeetingStoneActiveEntryUpdate"
            or refreshSource == "NetEaseAPI"
            or refreshSource == "MeetingStonePublishedDataRefresh"
        )
    if currentTab == "meetingstone"
        and (applicantOnlyRefresh or protectedMeetingStoneFormRefresh)
        and self.RefreshMeetingStoneApplicantsOnly
    then
        self:RefreshMeetingStoneApplicantsOnly(refreshSource, refreshDetail)
        return
    end

    local snapshot = self:GetActiveTabSnapshot()
    local isGroupRowStateRefresh = (currentTab == "group" or currentTab == "carpool")
        and (
            refreshSource == "NetEaseAPI"
            or refreshSource == "GroupUnitRatingLookup"
            or refreshSource == "GroupRatingLookupTimeout"
        )
    local forceGroupRowStateFullRender = false
    if isGroupRowStateRefresh and self.lastRenderedSnapshotByTab[currentTab] == snapshot then
        if self.RefreshVisibleGroupRowStates and self:RefreshVisibleGroupRowStates(widgets, refreshSource, refreshDetail) then
            if self.DebugLog then
                self:DebugLog(
                    "RefreshMainWindow row-state tab=%s source=%s detail=%s",
                    tostring(currentTab),
                    tostring(refreshSource or "unknown"),
                    tostring(refreshDetail or "-")
                )
            end
            return
        end
        forceGroupRowStateFullRender = true
    end

    local forceRender = refreshSource == "NetEaseAPI" and (currentTab == "group" or currentTab == "carpool" or currentTab == "meetingstone")
        or forceGroupRowStateFullRender
    if currentTab == "meetingstone" then
        forceRender = forceRender or refreshSource == "MeetingStoneActiveEntryUpdate"
    end
    if currentTab == "findgroup" then
        forceRender = forceRender
            or refreshSource == "NetEaseAPI"
            or (type(refreshSource) == "string" and refreshSource:find("^FindGroup") ~= nil)
    end
    if currentTab == "blacklist" then
        forceRender = forceRender
            or refreshSource == "BlacklistChanged"
            or refreshSource == "BlacklistNoteChanged"
    end
    if currentTab == "options" then
        forceRender = forceRender
            or refreshSource == "DebugModeChanged"
    end
    if currentTab == "debug" then
        forceRender = forceRender
            or refreshSource == "DebugModeChanged"
            or refreshSource == "DebugAction"
            or refreshSource == "DebugToggle"
            or refreshSource == "BuildSelectedTab"
    end

    if not forceRender and self.lastRenderedSnapshotByTab[currentTab] == snapshot then
        if self.DebugLog then
            self:DebugLog(
                "RefreshMainWindow skipped tab=%s source=%s detail=%s",
                tostring(currentTab),
                tostring(refreshSource or "unknown"),
                tostring(refreshDetail or "-")
            )
        end
        return
    end

    self.lastRenderedSnapshotByTab[currentTab] = snapshot

    if self.DebugLog then
        self:DebugLog(
            "RefreshMainWindow render tab=%s source=%s detail=%s",
            tostring(currentTab),
            tostring(refreshSource or "unknown"),
            tostring(refreshDetail or "-")
        )
    end

    if self.activeTab == "dungeons" then
        if not widgets.dungeonContentFrame or not widgets.dungeonContainer or not widgets.dungeonEmpty then
            return
        end

        local dungeonGroups = self:GetCurrentSeasonDungeonGroups()
        local hasDungeonGroups = #dungeonGroups > 0

        if hasDungeonGroups then
            if widgets.dungeonEmptyContainer then
                widgets.dungeonEmptyContainer:Hide()
            end
            widgets.dungeonEmpty:Hide()
        else
            widgets.dungeonEmpty:SetText(TRANSLATE("无法加载当前赛季。"))
            if widgets.dungeonEmptyContainer then
                widgets.dungeonEmptyContainer:Show()
            end
            widgets.dungeonEmpty:Show()
        end

        self:EnsureDungeonTiles(#dungeonGroups)
        self:LayoutDungeonTiles(#dungeonGroups)

        for index, dungeonGroup in ipairs(dungeonGroups) do
            self:RefreshDungeonTile(widgets.dungeonTiles[index], dungeonGroup)
        end

        for index = #dungeonGroups + 1, #(widgets.dungeonTiles or {}) do
            self:SetDungeonTileVisible(widgets.dungeonTiles[index], false)
        end

        if widgets.dungeonEmptyContainer and widgets.dungeonEmptyContainer:IsShown() then
            local emptyHeight = (widgets.dungeonEmpty:GetStringHeight() or 0) + 12
            widgets.dungeonEmptyContainer:SetHeight(math.max(emptyHeight, 28))
        end

        return
    end

    if self.activeTab == "group" or self.activeTab == "carpool" then
        if self.RefreshGroupListTab then
            self:RefreshGroupListTab(widgets, refreshSource, refreshDetail)
        end
        return
    end

    if self.activeTab == "meetingstone" then
        self:RefreshMeetingStoneTab(widgets, refreshSource)
        return
    end

    if self.activeTab == "findgroup" then
        if core.FindGroupTab and core.FindGroupTab.Refresh then
            core.FindGroupTab.Refresh(self, widgets, refreshSource)
        end
        return
    end

    if self.activeTab == "blacklist" then
        if core.BlacklistTab and core.BlacklistTab.Refresh then
            core.BlacklistTab.Refresh(self, widgets, refreshSource)
        end
        return
    end

    if self.activeTab == "options" then
        if core.OptionsTab and core.OptionsTab.Refresh then
            core.OptionsTab.Refresh(self, widgets, refreshSource)
        end
        return
    end

    if self.activeTab == "debug" then
        if core.DebugTab and core.DebugTab.Refresh then
            core.DebugTab.Refresh(self, widgets, refreshSource)
        end
        return
    end

    if not widgets.cardsContent or not widgets.empty then
        return
    end

    local characters = self.GetWarbandCharacterEntries and self:GetWarbandCharacterEntries() or self:GetCharacters()
    local hasCharacters = #characters > 0

    if self.RefreshCurrentCharacterCard then
        self:RefreshCurrentCharacterCard(widgets.currentCharacterCard)
    end

    if hasCharacters then
        if widgets.emptyContainer then
            widgets.emptyContainer:Hide()
        end
        widgets.empty:Hide()
    else
        widgets.empty:SetText(TRANSLATE("暂无可同步的满级战团角色，切换该战团内其他角色可自动补充数据"))
        if widgets.emptyContainer then
            widgets.emptyContainer:Show()
        end
        widgets.empty:Show()
    end

    self:EnsureCharacterCards(#characters)
    self:LayoutCharacterCards(#characters)

    for index, characterEntry in ipairs(characters) do
        self:RefreshCharacterCard(widgets.characterCards[index], characterEntry)
    end

    for index = #characters + 1, #(widgets.characterCards or {}) do
        self:SetCharacterCardVisible(widgets.characterCards[index], false)
    end

    C_Timer.After(0, function()
        if not self.widgets or not self.widgets.cardsContent or self.activeTab ~= "characters" then
            return
        end

        self:LayoutCharacterCards(#characters)
    end)
end
