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

core.OptionsTab = core.OptionsTab or {}
local OptionsTab = core.OptionsTab
local ShowOptionsAboutDialog

local OPTIONS_CONTENT_HEIGHT = 452
local OPTIONS_FORM_WIDTH = 820
local OPTIONS_CONTROL_COLUMN_X = 270
local OPTIONS_CONTROL_INSET_X = 14
local OPTIONS_ROW_HEIGHT = 28
local OPTIONS_TITLE_TO_CONTROLS_GAP = 14
local OPTIONS_SECTION_GAP = 16
local OPTIONS_SECTION_BODY_INSET_X = 24
local OPTIONS_SECTION_TITLE_TEXT_INSET_X = 24
local OPTIONS_DECOR_FRAME_INSET_LEFT = 6
local OPTIONS_DECOR_FRAME_INSET_RIGHT = 6
local OPTIONS_DECOR_FRAME_INSET_BOTTOM = 10
local OPTIONS_CONTENT_TOP_OFFSET = 20
local OPTIONS_CONTENT_BOTTOM_PADDING = 20
local OPTIONS_LABEL_TEXT_SIZE = 14
local OPTIONS_CHECK_BUTTON_SIZE = 26
local OPTIONS_DROPDOWN_WIDTH = 140
local OPTIONS_DROPDOWN_HEIGHT = 28
local OPTIONS_TEXT_INPUT_HEIGHT = 28
local OPTIONS_TEXT_INPUT_MAX_LETTERS = 140
local OPTIONS_DESCRIPTION_TEXT_SIZE = 13
local OPTIONS_TACTICAL_MESSAGE_MAX_BYTES = core.DUNGEON_TELEPORT_CHAT_MESSAGE_MAX_BYTES or 255
local OPTIONS_CONFIRM_BUTTON_WIDTH = 64
local OPTIONS_CONFIRM_BUTTON_HEIGHT = 26
local OPTIONS_CONFIRM_BUTTON_GAP = 8
local OPTIONS_CLEAR_BUTTON_WIDTH = 64
local OPTIONS_DEFAULT_BUTTON_WIDTH = 72
local OPTIONS_DEFAULT_BUTTON_HEIGHT = 26
local OPTIONS_PREVIEW_BUTTON_WIDTH = 64
local OPTIONS_PREVIEW_BUTTON_HEIGHT = 26
local OPTIONS_ABOUT_BUTTON_SIZE = 40
local OPTIONS_ABOUT_BUTTON_GAP = 8
local OPTIONS_ACTION_BUTTON_RIGHT_INSET = 16
local OPTIONS_SCROLLBAR_RIGHT_OFFSET = 18
local OPTIONS_MESSAGE_INPUT_WIDTH = OPTIONS_FORM_WIDTH - OPTIONS_CONTROL_COLUMN_X - OPTIONS_CONFIRM_BUTTON_WIDTH - OPTIONS_CONFIRM_BUTTON_GAP
local OPTIONS_PROMPT_DESCRIPTION_GAP = 14
local OPTIONS_SCALE_SLIDER_MIN = 100
local OPTIONS_SCALE_SLIDER_MAX = 150
local OPTIONS_SCALE_SLIDER_STEP = 1
local OPTIONS_SCALE_SLIDER_HEIGHT = 19
local OPTIONS_SCALE_VALUE_GAP = 12
local OPTIONS_SCALE_VALUE_WIDTH = 50
local OPTIONS_SCALE_SLIDER_WIDTH = OPTIONS_MESSAGE_INPUT_WIDTH
local OPTIONS_SCALE_CONTROL_WIDTH = OPTIONS_SCALE_SLIDER_WIDTH + OPTIONS_SCALE_VALUE_GAP + OPTIONS_SCALE_VALUE_WIDTH
local OPTIONS_PREVIEW_TEAMUP_SOURCE_WIDTH = 372
local OPTIONS_PREVIEW_TEAMUP_ICON_SIZE = 116

core.MK_OPTIONS_LAYOUT = core.MK_OPTIONS_LAYOUT or {
    interfaceRowCount = 6,
    teleportRowCount = 3,
    completionRowCount = 2,
    panelRowHeight = 40,
    panelSectionGap = 8,
    panelLabelInsetX = 28,
    panelControlInsetX = 34,
    previewPanelHeight = 78,
}
core.MK_OPTIONS_LAYOUT.panelRowCount = core.MK_OPTIONS_LAYOUT.interfaceRowCount
    + core.MK_OPTIONS_LAYOUT.teleportRowCount
    + core.MK_OPTIONS_LAYOUT.completionRowCount
core.MK_OPTIONS_LAYOUT.settingsPanelHeight = core.MK_OPTIONS_LAYOUT.panelRowCount * core.MK_OPTIONS_LAYOUT.panelRowHeight

core.MK_OPTIONS_PREVIEW = core.MK_OPTIONS_PREVIEW or {
    topGap = 8,
    height = 34,
    iconSize = 22,
    iconInsetX = 10,
    textInsetLeft = 38,
    textInsetRight = 10,
    iconTexture = "Interface\\AddOns\\" .. addonName .. "\\Art\\Ui\\TeamUp.png",
    iconTexCoord = { 0, OPTIONS_PREVIEW_TEAMUP_ICON_SIZE / OPTIONS_PREVIEW_TEAMUP_SOURCE_WIDTH, 0, 1 },
}

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

local function CopyOptionsList(source)
    local options = {}
    for index, option in ipairs(source or {}) do
        options[index] = {
            value = option.value or option.key,
            text = option.text,
        }
    end
    return options
end

local function GetStartupTabOptions()
    if core.GetMainTabDefinitions then
        return CopyOptionsList(core.GetMainTabDefinitions())
    end
    return CopyOptionsList(FALLBACK_STARTUP_TAB_OPTIONS)
end

local function GetAutoOpenCompletedRunOptions()
    local options = {
        { value = "off", text = "关闭" },
    }
    for _, option in ipairs(GetStartupTabOptions()) do
        options[#options + 1] = option
    end
    return options
end

local FIND_GROUP_MEMBER_DISPLAY_MODE_OPTIONS = {
    { value = "role", text = "职责模式" },
    { value = "spec", text = "专精模式" },
}

local function NormalizeOptionsTextInputValue(value, maxBytes)
    value = tostring(value or "")
    if maxBytes and core.TruncateUtf8ToByteLimit then
        return core.TruncateUtf8ToByteLimit(value, maxBytes)
    end
    return value
end

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

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

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

local function CreateOptionsNativeCheckButton(parent, onClick)
    local button = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    button:SetSize(OPTIONS_CHECK_BUTTON_SIZE, OPTIONS_CHECK_BUTTON_SIZE)
    button:EnableMouse(true)
    SkinOptionsNativeCheckButton(button, OPTIONS_CHECK_BUTTON_SIZE)
    button:SetScript("OnClick", function(self)
        if onClick then
            onClick(self:GetChecked() == true)
        end
    end)
    return button
end

local function CreateOptionsTextInput(parent, owner, getValue, setValue, width, options)
    local meetingStoneForm = core.MeetingStoneForm or {}
    local holder = CreateFrame("Frame", nil, parent)
    options = options or {}
    width = width or OPTIONS_MESSAGE_INPUT_WIDTH
    holder:SetSize(width, OPTIONS_TEXT_INPUT_HEIGHT)
    holder.mkMaxBytes = tonumber(options.maxBytes)

    if meetingStoneForm.AddTitleHolderBorder then
        meetingStoneForm.AddTitleHolderBorder(holder)
    end

    local editBox
    if meetingStoneForm.CreateInput then
        editBox = meetingStoneForm.CreateInput(holder, width, OPTIONS_TEXT_INPUT_HEIGHT, false)
        editBox:SetAllPoints(holder)
        if editBox.mkMeetingStoneInputBorder then
            editBox.mkMeetingStoneInputBorder:Hide()
        end
    else
        editBox = CreateFrame("EditBox", nil, holder, "InputBoxTemplate")
        editBox:SetAllPoints(holder)
    end

    editBox:SetAutoFocus(false)
    editBox:SetMaxLetters(tonumber(options.maxLetters) or holder.mkMaxBytes or OPTIONS_TEXT_INPUT_MAX_LETTERS)
    if core.ApplyFont then
        core.ApplyFont(editBox, 14, "")
    end
    editBox:SetTextColor(1, 0.96, 0.86, 1)

    if type(options.placeholder) == "string" and options.placeholder ~= "" then
        local placeholder = editBox:CreateFontString(nil, "OVERLAY", "GameFontDisable")
        placeholder:SetPoint("LEFT", editBox, "LEFT", 12, 0)
        placeholder:SetPoint("RIGHT", editBox, "RIGHT", -12, 0)
        placeholder:SetHeight(OPTIONS_TEXT_INPUT_HEIGHT)
        placeholder:SetJustifyH("LEFT")
        placeholder:SetJustifyV("MIDDLE")
        placeholder:SetText(options.placeholder)
        placeholder:SetTextColor(0.55, 0.55, 0.55, 0.88)
        placeholder:SetWordWrap(false)
        if core.ApplyFont then
            core.ApplyFont(placeholder, 14, "")
        end
        holder.mkPlaceholder = placeholder
        holder.mkRefreshPlaceholder = function(self)
            if not self.mkPlaceholder then
                return
            end
            local value = editBox:GetText() or ""
            if value == "" then
                self.mkPlaceholder:Show()
            else
                self.mkPlaceholder:Hide()
            end
        end
    end

    holder.mkGetCurrentText = function()
        return NormalizeOptionsTextInputValue(editBox:GetText(), holder.mkMaxBytes)
    end
    holder.mkGetSavedText = function()
        if owner and getValue then
            return getValue(owner) or ""
        end
        return ""
    end
    holder.mkRefreshControls = function(self)
        if self.mkRefreshActionButtons then
            self:mkRefreshActionButtons()
        end
    end
    holder.mkCommitText = function(self)
        if setValue and owner then
            setValue(owner, self:mkGetCurrentText())
        end
        self:mkRefreshControls()
    end

    editBox:SetScript("OnEnterPressed", function(self)
        holder:mkCommitText()
        self:ClearFocus()
    end)
    editBox:HookScript("OnTextChanged", function(self)
        local currentText = self:GetText() or ""
        local normalizedText = NormalizeOptionsTextInputValue(currentText, holder.mkMaxBytes)
        if normalizedText ~= currentText then
            self.mkSuppressTextChanged = true
            self:SetText(normalizedText)
            if self.SetCursorPosition then
                self:SetCursorPosition(#normalizedText)
            end
            self.mkSuppressTextChanged = nil
            currentText = normalizedText
        end

        if not self.mkSuppressTextChanged and holder.mkOnTextChanged then
            holder:mkOnTextChanged(currentText)
        end
        if holder.mkRefreshPlaceholder then
            holder:mkRefreshPlaceholder()
        end
        holder:mkRefreshControls()
    end)
    editBox:HookScript("OnEditFocusLost", function()
        holder:mkCommitText()
    end)
    editBox:SetScript("OnEscapePressed", function(self)
        if owner and getValue then
            self.mkSuppressTextChanged = true
            self:SetText(NormalizeOptionsTextInputValue(getValue(owner), holder.mkMaxBytes))
            self.mkSuppressTextChanged = nil
        end
        holder:mkRefreshControls()
        self:ClearFocus()
    end)

    holder.mkEditBox = editBox
    holder.mkSetDisplayText = function(self, value)
        if editBox.HasFocus and editBox:HasFocus() then
            self:mkRefreshControls()
            return
        end

        value = NormalizeOptionsTextInputValue(value, holder.mkMaxBytes)
        if editBox:GetText() == value then
            if self.mkRefreshPlaceholder then
                self:mkRefreshPlaceholder()
            end
            self:mkRefreshControls()
            return
        end

        editBox.mkSuppressTextChanged = true
        editBox:SetText(value)
        editBox.mkSuppressTextChanged = nil
        if self.mkRefreshPlaceholder then
            self:mkRefreshPlaceholder()
        end
        self:mkRefreshControls()
    end

    if owner and getValue then
        holder:mkSetDisplayText(getValue(owner) or "")
    elseif holder.mkRefreshPlaceholder then
        holder:mkRefreshPlaceholder()
    end

    return holder
end

local function SetOptionsActionButtonEnabled(button, enabled)
    if not button then
        return
    end

    enabled = enabled == true
    button.mkOptionsActionEnabled = enabled
    if enabled then
        if button.Enable then
            button:Enable()
        end
        if button.EnableMouse then
            button:EnableMouse(true)
        end
        button:SetAlpha(1)
    else
        if button.Disable then
            button:Disable()
        end
        if button.EnableMouse then
            button:EnableMouse(false)
        end
        button:SetAlpha(button.mkMeetingStoneButtonArtApplied == true and 1 or 0.45)
    end
    if addonTable.MeetingStoneButtonArt and addonTable.MeetingStoneButtonArt.Update then
        addonTable.MeetingStoneButtonArt.Update(button)
    end
end

local function CreateOptionsConfirmButton(parent, onClick)
    local meetingStoneForm = core.MeetingStoneForm or {}
    local button
    if meetingStoneForm.CreateSystemButton then
        button = meetingStoneForm.CreateSystemButton(parent, nil, OPTIONS_CONFIRM_BUTTON_WIDTH, OPTIONS_CONFIRM_BUTTON_HEIGHT, TRANSLATE("确认"), 0)
    else
        button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        button:SetText(TRANSLATE("确认"))
    end

    button:SetSize(OPTIONS_CONFIRM_BUTTON_WIDTH, OPTIONS_CONFIRM_BUTTON_HEIGHT)
    if core.ApplyFont then
        local fontString = button.Text or (button.GetFontString and button:GetFontString())
        core.ApplyFont(fontString, 13, "")
    end
    button:SetScript("OnClick", function()
        if button.mkOptionsActionEnabled == false then
            return
        end
        if core.PlayPanelButtonClickSound then
            core.PlayPanelButtonClickSound()
        end
        if onClick then
            onClick()
        end
    end)
    return button
end

local function CreateOptionsClearButton(parent, onClick)
    local meetingStoneForm = core.MeetingStoneForm or {}
    local button
    if meetingStoneForm.CreateSystemButton then
        button = meetingStoneForm.CreateSystemButton(parent, nil, OPTIONS_CLEAR_BUTTON_WIDTH, OPTIONS_CONFIRM_BUTTON_HEIGHT, TRANSLATE("清空"), 0)
    else
        button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        button:SetText(TRANSLATE("清空"))
    end

    button:SetSize(OPTIONS_CLEAR_BUTTON_WIDTH, OPTIONS_CONFIRM_BUTTON_HEIGHT)
    if core.ApplyFont then
        local fontString = button.Text or (button.GetFontString and button:GetFontString())
        core.ApplyFont(fontString, 13, "")
    end
    button:SetScript("OnClick", function()
        if button.mkOptionsActionEnabled == false then
            return
        end
        if core.PlayPanelButtonClickSound then
            core.PlayPanelButtonClickSound()
        end
        if onClick then
            onClick()
        end
    end)
    return button
end

local function CreateOptionsPromptPreviewButton(parent, onClick)
    local meetingStoneForm = core.MeetingStoneForm or {}
    local button
    if meetingStoneForm.CreateSystemButton then
        button = meetingStoneForm.CreateSystemButton(parent, nil, OPTIONS_PREVIEW_BUTTON_WIDTH, OPTIONS_PREVIEW_BUTTON_HEIGHT, TRANSLATE("预览"), 0)
    else
        button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        button:SetText(TRANSLATE("预览"))
    end

    button:SetSize(OPTIONS_PREVIEW_BUTTON_WIDTH, OPTIONS_PREVIEW_BUTTON_HEIGHT)
    if core.ApplyFont then
        local fontString = button.Text or (button.GetFontString and button:GetFontString())
        core.ApplyFont(fontString, 13, "")
    end
    button:SetScript("OnClick", function()
        if core.PlayPanelButtonClickSound then
            core.PlayPanelButtonClickSound()
        end
        if onClick then
            onClick()
        end
    end)
    return button
end

local function CreateOptionsDefaultButton(parent, onClick)
    local meetingStoneForm = core.MeetingStoneForm or {}
    local button
    if meetingStoneForm.CreateSystemButton then
        button = meetingStoneForm.CreateSystemButton(parent, nil, OPTIONS_DEFAULT_BUTTON_WIDTH, OPTIONS_DEFAULT_BUTTON_HEIGHT, TRANSLATE("默认设置"), 0)
    else
        button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        button:SetText(TRANSLATE("默认设置"))
    end

    button:SetSize(OPTIONS_DEFAULT_BUTTON_WIDTH, OPTIONS_DEFAULT_BUTTON_HEIGHT)
    if core.ApplyFont then
        local fontString = button.Text or (button.GetFontString and button:GetFontString())
        core.ApplyFont(fontString, 13, "")
    end
    button:SetScript("OnClick", function()
        if core.PlayPanelButtonClickSound then
            core.PlayPanelButtonClickSound()
        end
        if onClick then
            onClick()
        end
    end)
    return button
end

local function CreateOptionsAboutButton(parent, tooltipText, onClick)
    local button = CreateFrame("Button", nil, parent)
    button:SetSize(OPTIONS_ABOUT_BUTTON_SIZE, OPTIONS_ABOUT_BUTTON_SIZE)
    button.mkTooltipText = tooltipText

    local icon = button:CreateTexture(nil, "ARTWORK")
    icon:SetTexture("Interface\\Common\\help-i")
    icon:SetSize(OPTIONS_ABOUT_BUTTON_SIZE, OPTIONS_ABOUT_BUTTON_SIZE)
    icon:SetPoint("CENTER")

    local highlight = button:CreateTexture(nil, "HIGHLIGHT")
    highlight:SetTexture("Interface\\Common\\help-i")
    highlight:SetBlendMode("ADD")
    highlight:SetSize(OPTIONS_ABOUT_BUTTON_SIZE, OPTIONS_ABOUT_BUTTON_SIZE)
    highlight:SetPoint("CENTER")

    button:SetScript("OnEnter", function(self)
        local text = self.mkTooltipText
        if type(text) ~= "string" or text == "" or not GameTooltip then
            return
        end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        if GameTooltip_SetTitle then
            GameTooltip_SetTitle(GameTooltip, text)
        else
            GameTooltip:SetText(text)
        end
        GameTooltip:Show()
    end)
    button:SetScript("OnLeave", function()
        if GameTooltip then
            GameTooltip:Hide()
        end
    end)
    button:SetScript("OnClick", function()
        if core.PlayPanelButtonClickSound then
            core.PlayPanelButtonClickSound()
        end
        if onClick then
            onClick()
        end
    end)
    return button
end

function core.FormatRaidTargetIconsForPreview(text)
    if type(text) ~= "string" or text == "" then
        return text or ""
    end

    return text:gsub("{[Rr][Tt]([1-8])}", function(index)
        return "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_" .. index .. ":16:16:0:0|t"
    end)
end

function core.ColorizeOptionsPreviewText(text, channel)
    local value = channel or "SAY"
    local info = (CHAT_TYPE_INFO and CHAT_TYPE_INFO[value]) or (ChatTypeInfo and ChatTypeInfo[value])
    local r = info and info.r or 1
    local g = info and info.g or 1
    local b = info and info.b or 1
    return string.format("|cff%02x%02x%02x%s|r", math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5), math.floor(b * 255 + 0.5), core.FormatRaidTargetIconsForPreview(text) or "")
end

function core.CreateOptionsAnnouncementPreview(parent, owner)
    local style = core.MK_OPTIONS_PREVIEW
    local frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    frame:SetHeight(style.height)
    if frame.SetBackdrop then
        frame:SetBackdrop({
            bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = true,
            tileSize = 16,
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        frame:SetBackdropColor(0, 0, 0, 0.45)
        frame:SetBackdropBorderColor(0.55, 0.45, 0.25, 0.9)
    end

    local icon = frame:CreateTexture(nil, "ARTWORK")
    icon:SetSize(style.iconSize, style.iconSize)
    icon:SetPoint("LEFT", frame, "LEFT", style.iconInsetX, 0)
    icon:SetTexture(style.iconTexture)
    if style.iconTexCoord then
        icon:SetTexCoord(style.iconTexCoord[1], style.iconTexCoord[2], style.iconTexCoord[3], style.iconTexCoord[4])
    end

    local text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    text:SetPoint("TOPLEFT", frame, "TOPLEFT", style.textInsetLeft, 0)
    text:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -style.textInsetRight, 0)
    text:SetJustifyH("LEFT")
    text:SetJustifyV("MIDDLE")
    text:SetWordWrap(false)
    if core.ApplyFont then
        core.ApplyFont(text, 13, "")
    end
    frame.text = text

    frame.mkSetPreviewMessage = function(self, messageTemplate)
        local formatter = core.DungeonTeleport and core.DungeonTeleport.FormatPreviewMessage
        local previewText
        if formatter then
            previewText = formatter(owner, messageTemplate)
        else
            previewText = tostring(messageTemplate or "")
        end

        local channel = owner and owner.GetGroupChannel and owner:GetGroupChannel() or "SAY"
        text:SetText(core.ColorizeOptionsPreviewText(previewText, channel))
    end

    return frame
end

function core.ApplyOptionsFeaturePanelStyle(frame)
    if not frame or not frame.SetBackdrop then
        return
    end

    frame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = false,
        edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    frame:SetBackdropColor(0.03, 0.02, 0.01, 0.30)
    frame:SetBackdropBorderColor(0.55, 0.43, 0.18, 0.75)
end

function core.CreateOptionsFeaturePanel(parent)
    local frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    core.ApplyOptionsFeaturePanelStyle(frame)
    return frame
end

function core.CreateOptionsPanelRule(parent)
    local rule = parent:CreateTexture(nil, "ARTWORK")
    rule:SetTexture("Interface\\Buttons\\WHITE8X8")
    rule:SetVertexColor(0.72, 0.52, 0.22, 0.16)
    return rule
end

local function GetOptionsSettingsPanelHeight(rowCount)
    return math.max(tonumber(rowCount) or 0, 0) * core.MK_OPTIONS_LAYOUT.panelRowHeight
end

function core.CreateOptionsSettingsPanel(parent, rowCount)
    local layout = core.MK_OPTIONS_LAYOUT
    local panel = core.CreateOptionsFeaturePanel(parent)
    panel.rows = {}
    rowCount = math.max(tonumber(rowCount) or layout.panelRowCount, 0)

    local columnDivider = core.CreateOptionsPanelRule(panel)
    columnDivider:SetPoint("TOPLEFT", panel, "TOPLEFT", OPTIONS_CONTROL_COLUMN_X, -1)
    columnDivider:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", OPTIONS_CONTROL_COLUMN_X, 1)
    columnDivider:SetWidth(1)
    panel.columnDivider = columnDivider

    for rowIndex = 1, rowCount do
        local row = CreateFrame("Frame", nil, panel)
        row:SetPoint("TOPLEFT", panel, "TOPLEFT", 0, -((rowIndex - 1) * layout.panelRowHeight))
        row:SetPoint("TOPRIGHT", panel, "TOPRIGHT", 0, -((rowIndex - 1) * layout.panelRowHeight))
        row:SetHeight(layout.panelRowHeight)
        panel.rows[rowIndex] = row

        local rowBackground = row:CreateTexture(nil, "BACKGROUND", nil, -1)
        rowBackground:SetAllPoints(row)
        rowBackground:SetTexture("Interface\\Buttons\\WHITE8X8")
        rowBackground:SetVertexColor(1, 0.82, 0, rowIndex % 2 == 1 and 0.018 or 0.008)
        row.background = rowBackground

        if rowIndex > 1 then
            local rule = core.CreateOptionsPanelRule(panel)
            rule:SetPoint("TOPLEFT", row, "TOPLEFT", 16, 0)
            rule:SetPoint("TOPRIGHT", row, "TOPRIGHT", -16, 0)
            rule:SetHeight(1)
            row.topRule = rule
        end
    end

    return panel
end

function core.CreateOptionsRowLabel(row, text)
    local layout = core.MK_OPTIONS_LAYOUT
    local label = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    label:SetPoint("LEFT", row, "LEFT", layout.panelLabelInsetX, 0)
    label:SetSize(OPTIONS_CONTROL_COLUMN_X - layout.panelLabelInsetX - 16, OPTIONS_ROW_HEIGHT)
    label:SetJustifyH("LEFT")
    label:SetJustifyV("MIDDLE")
    label:SetText(text or "")
    if core.ApplyFont then
        core.ApplyFont(label, OPTIONS_LABEL_TEXT_SIZE, "")
    end
    return label
end

function core.AnchorOptionsRowControl(control, row)
    if not control or not row then
        return
    end

    local layout = core.MK_OPTIONS_LAYOUT
    control:ClearAllPoints()
    control:SetPoint("LEFT", row, "LEFT", OPTIONS_CONTROL_COLUMN_X + layout.panelControlInsetX, 0)
end

function core.AnchorOptionsMessageControls(row, input, confirmButton)
    if not row or not input or not confirmButton then
        return
    end

    local layout = core.MK_OPTIONS_LAYOUT
    confirmButton:ClearAllPoints()
    confirmButton:SetPoint("RIGHT", row, "RIGHT", -OPTIONS_ACTION_BUTTON_RIGHT_INSET, 0)

    input:ClearAllPoints()
    input:SetPoint("LEFT", row, "LEFT", OPTIONS_CONTROL_COLUMN_X + layout.panelControlInsetX, 0)
    input:SetPoint("RIGHT", confirmButton, "LEFT", -OPTIONS_CONFIRM_BUTTON_GAP, 0)
    input:SetHeight(OPTIONS_TEXT_INPUT_HEIGHT)
end

local function AnchorOptionsTacticalMessageControls(row, input, confirmButton, clearButton)
    if not row or not input or not confirmButton or not clearButton then
        return
    end

    local layout = core.MK_OPTIONS_LAYOUT
    clearButton:ClearAllPoints()
    clearButton:SetPoint("RIGHT", row, "RIGHT", -OPTIONS_ACTION_BUTTON_RIGHT_INSET, 0)

    confirmButton:ClearAllPoints()
    confirmButton:SetPoint("RIGHT", clearButton, "LEFT", -OPTIONS_CONFIRM_BUTTON_GAP, 0)

    input:ClearAllPoints()
    input:SetPoint("LEFT", row, "LEFT", OPTIONS_CONTROL_COLUMN_X + layout.panelControlInsetX, 0)
    input:SetPoint("RIGHT", confirmButton, "LEFT", -OPTIONS_CONFIRM_BUTTON_GAP, 0)
    input:SetHeight(OPTIONS_TEXT_INPUT_HEIGHT)
end

local function AnchorOptionsPromptControls(row, toggle, previewButton, descriptionText)
    if not row or not toggle or not previewButton then
        return
    end

    local layout = core.MK_OPTIONS_LAYOUT
    previewButton:ClearAllPoints()
    previewButton:SetPoint("RIGHT", row, "RIGHT", -OPTIONS_ACTION_BUTTON_RIGHT_INSET, 0)

    toggle:ClearAllPoints()
    toggle:SetPoint("LEFT", row, "LEFT", OPTIONS_CONTROL_COLUMN_X + layout.panelControlInsetX, 0)

    if descriptionText then
        descriptionText:ClearAllPoints()
        descriptionText:SetPoint("LEFT", toggle, "RIGHT", OPTIONS_PROMPT_DESCRIPTION_GAP, 0)
        descriptionText:SetPoint("RIGHT", previewButton, "LEFT", -OPTIONS_PROMPT_DESCRIPTION_GAP, 0)
        descriptionText:SetHeight(OPTIONS_ROW_HEIGHT)
    end
end

function core.CreateOptionsPreviewPanel(parent)
    local panel = core.CreateOptionsFeaturePanel(parent)

    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOPLEFT", panel, "TOPLEFT", 16, -6)
    title:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -16, -6)
    title:SetHeight(18)
    title:SetJustifyH("LEFT")
    title:SetJustifyV("MIDDLE")
    title:SetText(TRANSLATE("预览效果"))
    title:SetTextColor(1, 0.82, 0, 1)
    if core.ApplyFont then
        core.ApplyFont(title, 14, "OUTLINE")
    end

    panel.title = title
    return panel
end

local function UpdateOptionsScrollState(widgets)
    if not widgets or not widgets.optionsScrollFrame or not widgets.optionsContent or not widgets.optionsScrollBar then
        return
    end

    local scrollFrame = widgets.optionsScrollFrame
    local content = widgets.optionsContent
    local scrollBar = widgets.optionsScrollBar

    if scrollFrame.UpdateScrollChildRect then
        scrollFrame:UpdateScrollChildRect()
    end

    local viewHeight = scrollFrame:GetHeight() or 0
    content:SetHeight(math.max(content.mkOptionsContentHeight or OPTIONS_CONTENT_HEIGHT, viewHeight))

    local contentHeight = content:GetHeight() or 0
    local frameScrollRange = scrollFrame.GetVerticalScrollRange and scrollFrame:GetVerticalScrollRange() or 0
    local maxScroll = math.max(frameScrollRange, contentHeight - viewHeight, 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(maxScroll > 0)
    end
    if scrollBar.mkSetScrollValue then
        scrollBar.mkSetScrollValue(currentValue)
    elseif scrollBar.SetValue then
        scrollBar:SetValue(currentValue)
    end
    scrollFrame:SetVerticalScroll(currentValue)

    if maxScroll > 0 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

local function GetStartupTabOptionText(value)
    for _, option in ipairs(GetStartupTabOptions()) do
        if option.value == value then
            return TRANSLATE(option.text)
        end
    end

    return TRANSLATE("队伍")
end

local function GetAutoOpenCompletedRunOptionText(value)
    for _, option in ipairs(GetAutoOpenCompletedRunOptions()) do
        if option.value == value then
            return TRANSLATE(option.text)
        end
    end

    return TRANSLATE("关闭")
end

local function GetFindGroupMemberDisplayModeOptionText(value)
    for _, option in ipairs(FIND_GROUP_MEMBER_DISPLAY_MODE_OPTIONS) do
        if option.value == value then
            return TRANSLATE(option.text)
        end
    end

    return TRANSLATE("职责模式")
end

local function NormalizeFindGroupMemberDisplayMode(value)
    for _, option in ipairs(FIND_GROUP_MEMBER_DISPLAY_MODE_OPTIONS) do
        if option.value == value then
            return value
        end
    end

    return "role"
end

local function ClampMainWindowScalePercent(value)
    value = tonumber(value) or OPTIONS_SCALE_SLIDER_MIN
    value = math.floor(value + 0.5)
    return math.max(OPTIONS_SCALE_SLIDER_MIN, math.min(OPTIONS_SCALE_SLIDER_MAX, value))
end

local function FormatMainWindowScalePercent(value)
    return string.format("%d%%", ClampMainWindowScalePercent(value))
end

local function IsAutoOpenCompletedRunTab(value)
    if value == "off" then
        return false
    end

    for _, option in ipairs(GetAutoOpenCompletedRunOptions()) do
        if option.value == value then
            return true
        end
    end

    return false
end

function MK:GetStartupTabOptionText(value)
    return GetStartupTabOptionText(value)
end

function MK:GetStartupTab()
    local value = self.db and self.db.profile and self.db.profile.startupTab or "group"
    for _, option in ipairs(GetStartupTabOptions()) do
        if option.value == value then
            return value
        end
    end

    return "group"
end

function MK:SetStartupTab(value)
    if not self.db or not self.db.profile then
        return
    end

    local normalizedValue = "group"
    for _, option in ipairs(GetStartupTabOptions()) do
        if option.value == value then
            normalizedValue = value
            break
        end
    end

    if self.db.profile.startupTab == normalizedValue then
        return
    end

    self.db.profile.startupTab = normalizedValue

    if self.IsMainWindowShown and self:IsMainWindowShown() then
        self:RefreshMainWindow("SetStartupTab", normalizedValue)
    end
end

function MK:GetRightClickStartupTab()
    local value = self.db and self.db.profile and self.db.profile.rightClickStartupTab or "meetingstone"
    for _, option in ipairs(GetStartupTabOptions()) do
        if option.value == value then
            return value
        end
    end

    return "meetingstone"
end

function MK:SetRightClickStartupTab(value)
    if not self.db or not self.db.profile then
        return
    end

    local normalizedValue = "meetingstone"
    for _, option in ipairs(GetStartupTabOptions()) do
        if option.value == value then
            normalizedValue = value
            break
        end
    end

    if self.db.profile.rightClickStartupTab == normalizedValue then
        return
    end

    self.db.profile.rightClickStartupTab = normalizedValue

    if self.IsMainWindowShown and self:IsMainWindowShown() then
        self:RefreshMainWindow("SetRightClickStartupTab", normalizedValue)
    end
end

function MK:GetAutoOpenOnCompletedRunTab()
    local value = self.db and self.db.profile and self.db.profile.autoOpenOnCompletedRun or "off"
    if value == true then
        local startupTab = self:GetStartupTab()
        return IsAutoOpenCompletedRunTab(startupTab) and startupTab or "group"
    end
    if value == false then
        return "off"
    end

    for _, option in ipairs(GetAutoOpenCompletedRunOptions()) do
        if option.value == value then
            return value
        end
    end

    return "off"
end

function MK:SetAutoOpenOnCompletedRunTab(value)
    if not self.db or not self.db.profile then
        return
    end

    local normalizedValue = "off"
    for _, option in ipairs(GetAutoOpenCompletedRunOptions()) do
        if option.value == value then
            normalizedValue = value
            break
        end
    end

    if self.db.profile.autoOpenOnCompletedRun == normalizedValue then
        return
    end

    self.db.profile.autoOpenOnCompletedRun = normalizedValue

    if self.IsMainWindowShown and self:IsMainWindowShown() then
        self:RefreshMainWindow("SetAutoOpenOnCompletedRunTab", normalizedValue)
    end
end

function MK:GetFindGroupMemberDisplayMode()
    local value = self.db and self.db.profile and self.db.profile.findGroupMemberDisplayMode
    return NormalizeFindGroupMemberDisplayMode(value)
end

function MK:SetFindGroupMemberDisplayMode(value)
    if not self.db or not self.db.profile then
        return
    end

    local normalizedValue = NormalizeFindGroupMemberDisplayMode(value)
    if self.db.profile.findGroupMemberDisplayMode == normalizedValue then
        return
    end

    self.db.profile.findGroupMemberDisplayMode = normalizedValue

    if self.IsMainWindowShown and self:IsMainWindowShown() then
        self:RefreshMainWindow("FindGroupMemberDisplayMode", normalizedValue)
    end
end

local function FormatStartupTabTooltipLine(formatKey, tabText)
    local format = TRANSLATE(formatKey)
    if type(format) ~= "string" or format == "" then
        format = formatKey
    end

    local ok, text = pcall(string.format, format, tabText)
    if ok and type(text) == "string" then
        return text
    end
    return tabText
end

function MK:GetStartupClickTooltipLine(mouseButton)
    local isRightButton = mouseButton == "RightButton" or mouseButton == "right"
    local tabValue = isRightButton and self:GetRightClickStartupTab() or self:GetStartupTab()
    return FormatStartupTabTooltipLine(
        isRightButton and "右键：%s" or "左键：%s",
        GetStartupTabOptionText(tabValue)
    )
end

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

    return tostring(value)
end

local function CreateOptionsSectionTitle(parent, name, text)
    local createTitle = core.CreatePlayerInfoSectionTitle
    local title, titleBg, titleText
    if createTitle then
        title, titleBg, titleText = createTitle(parent, name, TRANSLATE(text))
        if title and title.LeftFade then
            title.LeftFade:Hide()
        end
        if title and titleBg then
            titleBg:ClearAllPoints()
            titleBg:SetAllPoints(title)
        end
        if title and titleText then
            titleText:ClearAllPoints()
            titleText:SetPoint("TOPLEFT", title, "TOPLEFT", OPTIONS_SECTION_TITLE_TEXT_INSET_X, 0)
            titleText:SetPoint("BOTTOMRIGHT", title, "BOTTOMRIGHT", -OPTIONS_SECTION_TITLE_TEXT_INSET_X, 0)
        end
    else
        title = CreateFrame("Frame", name, parent)
        title:SetHeight(40)
        titleText = title:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        titleText:SetPoint("LEFT", title, "LEFT", 14, 0)
        titleText:SetText(TRANSLATE(text))
    end
    return title, titleBg, titleText
end

local function CreateOptionsSettingsGroup(parent, name, titleKey, rowCount, anchorFrame)
    local title, titleBg, titleText = CreateOptionsSectionTitle(parent, name .. "Title", titleKey)
    if anchorFrame then
        title:SetPoint("TOPLEFT", anchorFrame, "BOTTOMLEFT", -OPTIONS_SECTION_BODY_INSET_X, -OPTIONS_SECTION_GAP)
        title:SetPoint("TOPRIGHT", anchorFrame, "BOTTOMRIGHT", OPTIONS_SECTION_BODY_INSET_X, -OPTIONS_SECTION_GAP)
    else
        title:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
        title:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, 0)
    end

    local panelHeight = GetOptionsSettingsPanelHeight(rowCount)
    local panel = core.CreateOptionsSettingsPanel(parent, rowCount)
    panel:SetPoint("TOPLEFT", title, "BOTTOMLEFT", OPTIONS_SECTION_BODY_INSET_X, -OPTIONS_TITLE_TO_CONTROLS_GAP)
    panel:SetPoint("TOPRIGHT", title, "BOTTOMRIGHT", -OPTIONS_SECTION_BODY_INSET_X, -OPTIONS_TITLE_TO_CONTROLS_GAP)
    panel:SetHeight(panelHeight)

    local groupHeight = math.max(title:GetHeight() or 0, 40)
        + OPTIONS_TITLE_TO_CONTROLS_GAP
        + panelHeight

    return {
        title = title,
        titleBg = titleBg,
        titleText = titleText,
        panel = panel,
        rows = panel.rows,
        height = groupHeight,
    }
end

local function GetOptionsTacticalDungeonGroups(owner)
    local sourceGroups = owner and owner.GetCurrentSeasonDungeonGroups and owner:GetCurrentSeasonDungeonGroups() or {}
    local groups = {}
    if type(sourceGroups) ~= "table" then
        return groups
    end

    for _, dungeonGroup in ipairs(sourceGroups) do
        local mapID = tonumber(dungeonGroup and dungeonGroup.mapID)
        if mapID then
            groups[#groups + 1] = {
                mapID = mapID,
                dungeonName = dungeonGroup.dungeonName
                    or (owner.GetLocalizedDungeonName and owner:GetLocalizedDungeonName(mapID))
                    or TRANSLATE("未知地下城"),
            }
        end
    end

    return groups
end

local function CreateOptionsTacticalAnnouncementGetter(mapID)
    return function(target)
        return target:GetDungeonTeleportTacticalAnnouncement(mapID)
    end
end

local function CreateOptionsTacticalAnnouncementSetter(mapID)
    return function(target, value)
        target:SetDungeonTeleportTacticalAnnouncement(mapID, value)
    end
end

local function ConfigureOptionsTacticalAnnouncementActions(input, confirmButton, clearButton)
    if not input then
        return
    end

    input.mkRefreshActionButtons = function(self)
        local currentValue = self.mkGetCurrentText and self:mkGetCurrentText() or ""
        local savedValue = self.mkGetSavedText and self:mkGetSavedText() or ""
        SetOptionsActionButtonEnabled(confirmButton, currentValue ~= savedValue)
        SetOptionsActionButtonEnabled(clearButton, currentValue ~= "")
    end

    if confirmButton then
        confirmButton:SetScript("OnClick", function(button)
            if button.mkOptionsActionEnabled == false then
                return
            end
            if core.PlayPanelButtonClickSound then
                core.PlayPanelButtonClickSound()
            end
            if input.mkCommitText then
                input:mkCommitText()
            end
            if input.mkEditBox then
                input.mkEditBox:ClearFocus()
            end
        end)
    end

    if clearButton then
        clearButton:SetScript("OnClick", function(button)
            if button.mkOptionsActionEnabled == false then
                return
            end
            if core.PlayPanelButtonClickSound then
                core.PlayPanelButtonClickSound()
            end
            if input.mkEditBox then
                input.mkEditBox:SetText("")
                input.mkEditBox:ClearFocus()
            end
            if input.mkCommitText then
                input:mkCommitText()
            end
        end)
    end

    input:mkRefreshActionButtons()
end

local function ResetOptionsTabDefaults(owner)
    if not (owner and owner.db and owner.db.profile) then
        return
    end

    local profile = owner.db.profile
    profile.minimap = profile.minimap or {}
    profile.minimap.hide = false
    profile.microMenuButton = true
    profile.startupTab = "group"
    profile.rightClickStartupTab = "meetingstone"
    profile.findGroupMemberDisplayMode = "role"
    profile.findGroup = nil
    profile.autoOpenOnCompletedRun = "off"
    profile.keystoneAnnouncement = false
    profile.window = profile.window or {}
    profile.window.scale = 1
    profile.dungeonTeleportAnnouncement = true
    profile.dungeonTeleportPrompt = true
    if owner.ResetDungeonTeleportCustomSettings then
        owner:ResetDungeonTeleportCustomSettings()
    end

    if owner.SetMinimapButtonVisible then
        owner:SetMinimapButtonVisible(true)
    end
    if owner.RefreshMicroMenuButtonAnchor then
        owner:RefreshMicroMenuButtonAnchor()
    end
    if owner.RefreshMicroMenuButtonState then
        owner:RefreshMicroMenuButtonState()
    end
    if owner.RefreshQueueStatusButtonOffset then
        owner:RefreshQueueStatusButtonOffset()
    end
    if owner.ApplyMainWindowScale then
        owner:ApplyMainWindowScale()
    end
    if owner.RefreshMainWindow then
        owner:RefreshMainWindow("ResetOptionsTabDefaults")
    end
end

local function CreateOptionsDropdown(parent, owner, config)
    config = config or {}
    local dropdown = CreateFrame("DropdownButton", config.name or nil, parent, "WowStyle1DropdownTemplate")
    dropdown:SetWidth(OPTIONS_DROPDOWN_WIDTH)
    if dropdown.SetHeight then
        dropdown:SetHeight(OPTIONS_DROPDOWN_HEIGHT)
    end
    local getOptions = config.getOptions or function()
        return config.options or GetStartupTabOptions()
    end
    local getOptionText = config.getOptionText or GetStartupTabOptionText
    local getValue = config.getValue or function(target)
        return target:GetStartupTab()
    end
    local setValue = config.setValue or function(target, value)
        target:SetStartupTab(value)
    end
    dropdown:SetSelectionText(function()
        return getOptionText(getValue(owner))
    end)

    dropdown:SetupMenu(function(_, rootDescription)
        rootDescription:SetTag(config.menuTag or "MENU_MK_STARTUP_TAB")
        local selectedValue = getValue(owner)
        for _, option in ipairs(getOptions()) do
            rootDescription:CreateRadio(getOptionText(option.value), function(data)
                return data.value == selectedValue
            end, function(data)
                setValue(owner, data.value)
                if dropdown.SetSelectionText then
                    dropdown:SetSelectionText(function()
                        return getOptionText(getValue(owner))
                    end)
                end
                if dropdown.GenerateMenu then
                    dropdown:GenerateMenu()
                end
                if dropdown.Update then
                    dropdown:Update()
                end
            end, option)
        end
    end)

    return dropdown
end

local function SkinOptionsFallbackSlider(slider, template)
    if not slider or template == "MinimalSliderTemplate" or template == "UISliderTemplate" then
        return
    end

    if slider.SetBackdrop then
        slider:SetBackdrop({
            bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
            edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
            tile = true,
            tileSize = 8,
            edgeSize = 8,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
    end

    if slider.SetThumbTexture then
        slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Horizontal")
        local thumb = slider.GetThumbTexture and slider:GetThumbTexture()
        if thumb then
            thumb:SetSize(32, 32)
        end
    end
end

local function CreateOptionsMainWindowScaleSlider(parent, owner)
    local holder = CreateFrame("Frame", nil, parent)
    holder:SetSize(OPTIONS_SCALE_CONTROL_WIDTH, OPTIONS_ROW_HEIGHT)

    local slider, sliderTemplate
    if core.CreateFrameWithTemplateOptions then
        slider, sliderTemplate = core.CreateFrameWithTemplateOptions("Slider", nil, holder, {
            "MinimalSliderTemplate",
            "UISliderTemplate",
            "BackdropTemplate",
        })
    else
        local ok, frame = pcall(CreateFrame, "Slider", nil, holder, "MinimalSliderTemplate")
        if ok and frame then
            slider = frame
            sliderTemplate = "MinimalSliderTemplate"
        else
            slider = CreateFrame("Slider", nil, holder)
        end
    end

    slider:SetOrientation("HORIZONTAL")
    slider:SetMinMaxValues(OPTIONS_SCALE_SLIDER_MIN, OPTIONS_SCALE_SLIDER_MAX)
    slider:SetValueStep(OPTIONS_SCALE_SLIDER_STEP)
    if slider.SetObeyStepOnDrag then
        slider:SetObeyStepOnDrag(true)
    end
    slider:SetSize(OPTIONS_SCALE_SLIDER_WIDTH, OPTIONS_SCALE_SLIDER_HEIGHT)
    slider:SetPoint("LEFT", holder, "LEFT", 0, 0)
    SkinOptionsFallbackSlider(slider, sliderTemplate)

    local valueText = holder:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    valueText:SetPoint("LEFT", slider, "RIGHT", OPTIONS_SCALE_VALUE_GAP, 0)
    valueText:SetSize(OPTIONS_SCALE_VALUE_WIDTH, OPTIONS_ROW_HEIGHT)
    valueText:SetJustifyH("LEFT")
    valueText:SetJustifyV("MIDDLE")
    if core.ApplyFont then
        core.ApplyFont(valueText, OPTIONS_LABEL_TEXT_SIZE, "")
    end

    local function setDisplayValue(percent)
        valueText:SetText(FormatMainWindowScalePercent(percent))
    end

    holder.mkSetValue = function(self, scale)
        local percent = ClampMainWindowScalePercent((tonumber(scale) or 1) * 100)
        self.mkUpdating = true
        slider:SetValue(percent)
        setDisplayValue(percent)
        self.mkUpdating = false
    end

    slider:SetScript("OnValueChanged", function(_, value)
        local percent = ClampMainWindowScalePercent(value)
        if math.abs((tonumber(value) or percent) - percent) > 0.001 then
            holder.mkUpdating = true
            slider:SetValue(percent)
            holder.mkUpdating = false
        end

        setDisplayValue(percent)
        if holder.mkUpdating then
            return
        end

        if owner and owner.SetMainWindowScale then
            owner:SetMainWindowScale(percent / 100)
        end
    end)

    holder.slider = slider
    holder.valueText = valueText
    holder:mkSetValue(owner and owner.GetMainWindowScale and owner:GetMainWindowScale() or 1)
    return holder
end

function OptionsTab.Build(owner, tabGroup)
    local optionsFrame = CreateFrame("Frame", "MKOptionsPanel", UIParent)
    optionsFrame.mkFillDecorFrame = true
    optionsFrame.mkDecorFrameInsetLeft = OPTIONS_DECOR_FRAME_INSET_LEFT
    optionsFrame.mkDecorFrameInsetTop = 4
    optionsFrame.mkDecorFrameInsetRight = OPTIONS_DECOR_FRAME_INSET_RIGHT
    optionsFrame.mkDecorFrameInsetBottom = OPTIONS_DECOR_FRAME_INSET_BOTTOM
    tabGroup:AddChild(optionsFrame)

    local scrollFrame, content, scrollBar
    local createScrollContainer = core.CreateNativeScrollContainer
    if createScrollContainer then
        scrollFrame, content, scrollBar = createScrollContainer(optionsFrame, function()
            UpdateOptionsScrollState(owner.widgets)
        end, 0)
    end

    if not (scrollFrame and content) then
        content = CreateFrame("Frame", nil, optionsFrame)
        content:SetAllPoints(optionsFrame)
    end
    content:SetHeight(OPTIONS_CONTENT_HEIGHT)

    if scrollBar and tabGroup and tabGroup.frame then
        scrollBar:ClearAllPoints()
        scrollBar:SetPoint("TOPRIGHT", tabGroup.frame, "TOPRIGHT", OPTIONS_SCROLLBAR_RIGHT_OFFSET, -4)
        scrollBar:SetPoint("BOTTOMRIGHT", tabGroup.frame, "BOTTOMRIGHT", OPTIONS_SCROLLBAR_RIGHT_OFFSET, 4)
        scrollBar:SetWidth(17)
    end

    local tacticalDungeonGroups = GetOptionsTacticalDungeonGroups(owner)

    local optionsControls = CreateFrame("Frame", nil, content)
    optionsControls:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -OPTIONS_CONTENT_TOP_OFFSET)
    optionsControls:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, -OPTIONS_CONTENT_TOP_OFFSET)

    local interfaceGroup = CreateOptionsSettingsGroup(
        optionsControls,
        "MKOptionsInterface",
        "界面设置",
        core.MK_OPTIONS_LAYOUT.interfaceRowCount
    )
    local interfaceRows = interfaceGroup.rows

    local defaultSettingsButton = CreateOptionsDefaultButton(interfaceGroup.title, function()
        ResetOptionsTabDefaults(owner)
    end)
    defaultSettingsButton:SetPoint("RIGHT", interfaceGroup.title, "RIGHT", -(OPTIONS_SECTION_BODY_INSET_X + OPTIONS_ACTION_BUTTON_RIGHT_INSET), 0)
    defaultSettingsButton:SetFrameLevel((interfaceGroup.title:GetFrameLevel() or 0) + 4)

    local aboutButton = CreateOptionsAboutButton(interfaceGroup.title, TRANSLATE("关于大秘境钥石助手"), function()
        if ShowOptionsAboutDialog then
            ShowOptionsAboutDialog(owner)
        end
    end)
    aboutButton:SetPoint("RIGHT", defaultSettingsButton, "LEFT", -OPTIONS_ABOUT_BUTTON_GAP, 0)
    aboutButton:SetFrameLevel((interfaceGroup.title:GetFrameLevel() or 0) + 4)

    core.CreateOptionsRowLabel(interfaceRows[1], TRANSLATE("显示小地图图标"))
    local minimapToggle = CreateOptionsNativeCheckButton(interfaceRows[1], function(value)
        owner:SetMinimapButtonVisible(value)
    end)
    core.AnchorOptionsRowControl(minimapToggle, interfaceRows[1])

    core.CreateOptionsRowLabel(interfaceRows[2], TRANSLATE("显示菜单栏按钮"))
    local microMenuToggle = CreateOptionsNativeCheckButton(interfaceRows[2], function(value)
        owner:SetMicroMenuButtonEnabled(value)
    end)
    core.AnchorOptionsRowControl(microMenuToggle, interfaceRows[2])

    core.CreateOptionsRowLabel(interfaceRows[3], TRANSLATE("默认启动页面"))
    local startupTabDropdown = CreateOptionsDropdown(interfaceRows[3], owner, {
        name = "MKStartupTabDropdown",
        menuTag = "MENU_MK_STARTUP_TAB",
        getValue = function(target)
            return target:GetStartupTab()
        end,
        setValue = function(target, value)
            target:SetStartupTab(value)
        end,
    })
    core.AnchorOptionsRowControl(startupTabDropdown, interfaceRows[3])

    core.CreateOptionsRowLabel(interfaceRows[4], TRANSLATE("右键启动页面"))
    local rightClickStartupTabDropdown = CreateOptionsDropdown(interfaceRows[4], owner, {
        name = "MKRightClickStartupTabDropdown",
        menuTag = "MENU_MK_RIGHT_CLICK_STARTUP_TAB",
        getValue = function(target)
            return target:GetRightClickStartupTab()
        end,
        setValue = function(target, value)
            target:SetRightClickStartupTab(value)
        end,
    })
    core.AnchorOptionsRowControl(rightClickStartupTabDropdown, interfaceRows[4])

    core.CreateOptionsRowLabel(interfaceRows[5], TRANSLATE("队伍成员模式"))
    local findGroupMemberModeDropdown = CreateOptionsDropdown(interfaceRows[5], owner, {
        name = "MKFindGroupMemberDisplayModeDropdown",
        menuTag = "MENU_MK_FIND_GROUP_MEMBER_DISPLAY_MODE",
        options = FIND_GROUP_MEMBER_DISPLAY_MODE_OPTIONS,
        getOptionText = GetFindGroupMemberDisplayModeOptionText,
        getValue = function(target)
            return target:GetFindGroupMemberDisplayMode()
        end,
        setValue = function(target, value)
            target:SetFindGroupMemberDisplayMode(value)
        end,
    })
    core.AnchorOptionsRowControl(findGroupMemberModeDropdown, interfaceRows[5])

    core.CreateOptionsRowLabel(interfaceRows[6], TRANSLATE("面板缩放"))
    local mainWindowScaleSlider = CreateOptionsMainWindowScaleSlider(interfaceRows[6], owner)
    core.AnchorOptionsRowControl(mainWindowScaleSlider, interfaceRows[6])

    local teleportGroup = CreateOptionsSettingsGroup(
        optionsControls,
        "MKOptionsTeleport",
        "传送设置",
        core.MK_OPTIONS_LAYOUT.teleportRowCount,
        interfaceGroup.panel
    )
    local teleportRows = teleportGroup.rows

    core.CreateOptionsRowLabel(teleportRows[1], TRANSLATE("跟随传送"))
    local teleportPromptToggle = CreateOptionsNativeCheckButton(teleportRows[1], function(value)
        owner:SetDungeonTeleportFollowEnabled(value)
    end)
    local teleportPromptPreviewButton = CreateOptionsPromptPreviewButton(teleportRows[1], function()
        if owner.ShowDungeonTeleportDialogTest then
            owner:ShowDungeonTeleportDialogTest()
        end
    end)
    local teleportPromptDescription = teleportRows[1]:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    teleportPromptDescription:SetJustifyH("LEFT")
    teleportPromptDescription:SetJustifyV("MIDDLE")
    teleportPromptDescription:SetWordWrap(false)
    teleportPromptDescription:SetTextColor(0.78, 0.73, 0.64, 1)
    teleportPromptDescription:SetText(TRANSLATE("队友传送时弹出跟随面板；自己传送时也会提示队友跟随。"))
    if core.ApplyFont then
        core.ApplyFont(teleportPromptDescription, OPTIONS_DESCRIPTION_TEXT_SIZE, "")
    end
    AnchorOptionsPromptControls(teleportRows[1], teleportPromptToggle, teleportPromptPreviewButton, teleportPromptDescription)

    core.CreateOptionsRowLabel(teleportRows[2], TRANSLATE("传送通报"))
    local teleportAnnouncementToggle = CreateOptionsNativeCheckButton(teleportRows[2], function(value)
        owner:SetDungeonTeleportAnnouncementEnabled(value)
    end)
    core.AnchorOptionsRowControl(teleportAnnouncementToggle, teleportRows[2])

    core.CreateOptionsRowLabel(teleportRows[3], TRANSLATE("自定义通报文案"))
    local teleportDialogMessageBox = CreateOptionsTextInput(teleportRows[3], owner, function(target)
        return target:GetDungeonTeleportDialogMessage()
    end, function(target, value)
        target:SetDungeonTeleportDialogMessage(value)
    end, OPTIONS_MESSAGE_INPUT_WIDTH, {
        maxBytes = OPTIONS_TACTICAL_MESSAGE_MAX_BYTES,
        maxLetters = OPTIONS_TACTICAL_MESSAGE_MAX_BYTES,
    })

    local teleportDialogMessageConfirmButton = CreateOptionsConfirmButton(teleportRows[3], function()
        if teleportDialogMessageBox and teleportDialogMessageBox.mkCommitText then
            teleportDialogMessageBox:mkCommitText()
        end
        if teleportDialogMessageBox and teleportDialogMessageBox.mkEditBox then
            teleportDialogMessageBox.mkEditBox:ClearFocus()
        end
    end)
    core.AnchorOptionsMessageControls(teleportRows[3], teleportDialogMessageBox, teleportDialogMessageConfirmButton)

    local optionsPreviewPanel = core.CreateOptionsPreviewPanel(optionsControls)
    optionsPreviewPanel:SetPoint("TOPLEFT", teleportGroup.panel, "BOTTOMLEFT", 0, -core.MK_OPTIONS_LAYOUT.panelSectionGap)
    optionsPreviewPanel:SetPoint("TOPRIGHT", teleportGroup.panel, "BOTTOMRIGHT", 0, -core.MK_OPTIONS_LAYOUT.panelSectionGap)
    optionsPreviewPanel:SetHeight(core.MK_OPTIONS_LAYOUT.previewPanelHeight)

    local teleportDialogMessagePreview = core.CreateOptionsAnnouncementPreview(optionsPreviewPanel, owner)
    teleportDialogMessagePreview:ClearAllPoints()
    teleportDialogMessagePreview:SetPoint("TOPLEFT", optionsPreviewPanel, "TOPLEFT", 18, -30)
    teleportDialogMessagePreview:SetPoint("BOTTOMRIGHT", optionsPreviewPanel, "BOTTOMRIGHT", -18, 8)
    teleportDialogMessagePreview:mkSetPreviewMessage(owner:GetDungeonTeleportDialogMessage())
    teleportDialogMessageBox.mkOnTextChanged = function(_, value)
        teleportDialogMessagePreview:mkSetPreviewMessage(value)
    end

    local completionGroup = CreateOptionsSettingsGroup(
        optionsControls,
        "MKOptionsCompletion",
        "其他设置",
        core.MK_OPTIONS_LAYOUT.completionRowCount,
        optionsPreviewPanel
    )
    local completionRows = completionGroup.rows

    core.CreateOptionsRowLabel(completionRows[1], TRANSLATE("自动弹出"))
    local autoOpenDropdown = CreateOptionsDropdown(completionRows[1], owner, {
        name = "MKAutoOpenCompletedRunDropdown",
        menuTag = "MENU_MK_AUTO_OPEN_COMPLETED_RUN",
        getOptions = GetAutoOpenCompletedRunOptions,
        getOptionText = GetAutoOpenCompletedRunOptionText,
        getValue = function(target)
            return target:GetAutoOpenOnCompletedRunTab()
        end,
        setValue = function(target, value)
            target:SetAutoOpenOnCompletedRunTab(value)
        end,
    })
    core.AnchorOptionsRowControl(autoOpenDropdown, completionRows[1])

    core.CreateOptionsRowLabel(completionRows[2], TRANSLATE("钥石通报"))
    local keystoneAnnouncementToggle = CreateOptionsNativeCheckButton(completionRows[2], function(value)
        owner:SetKeystoneAnnouncementEnabled(value)
    end)
    core.AnchorOptionsRowControl(keystoneAnnouncementToggle, completionRows[2])
    local keystoneAnnouncementDescription = completionRows[2]:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    keystoneAnnouncementDescription:SetJustifyH("LEFT")
    keystoneAnnouncementDescription:SetJustifyV("MIDDLE")
    keystoneAnnouncementDescription:SetWordWrap(false)
    keystoneAnnouncementDescription:SetTextColor(0.78, 0.73, 0.64, 1)
    keystoneAnnouncementDescription:SetText(TRANSLATE("钥石信息变更后将自动通报到队伍频道。"))
    if core.ApplyFont then
        core.ApplyFont(keystoneAnnouncementDescription, OPTIONS_DESCRIPTION_TEXT_SIZE, "")
    end
    keystoneAnnouncementDescription:ClearAllPoints()
    keystoneAnnouncementDescription:SetPoint("LEFT", keystoneAnnouncementToggle, "RIGHT", OPTIONS_PROMPT_DESCRIPTION_GAP, 0)
    keystoneAnnouncementDescription:SetPoint("RIGHT", completionRows[2], "RIGHT", -OPTIONS_ACTION_BUTTON_RIGHT_INSET, 0)
    keystoneAnnouncementDescription:SetHeight(OPTIONS_ROW_HEIGHT)

    local optionsControlsHeight = interfaceGroup.height
        + OPTIONS_SECTION_GAP
        + teleportGroup.height
        + core.MK_OPTIONS_LAYOUT.panelSectionGap
        + core.MK_OPTIONS_LAYOUT.previewPanelHeight
        + OPTIONS_SECTION_GAP
        + completionGroup.height
    optionsControls:SetHeight(optionsControlsHeight)

    local tacticalTitle, tacticalTitleBg, tacticalTitleText = CreateOptionsSectionTitle(content, "MKOptionsTacticalTitle", "战术通报")
    tacticalTitle:SetPoint("TOPLEFT", optionsControls, "BOTTOMLEFT", 0, -OPTIONS_SECTION_GAP)
    tacticalTitle:SetPoint("TOPRIGHT", optionsControls, "BOTTOMRIGHT", 0, -OPTIONS_SECTION_GAP)

    local tacticalPanelHeight = GetOptionsSettingsPanelHeight(#tacticalDungeonGroups)
    local tacticalAnnouncementPanel = core.CreateOptionsSettingsPanel(content, #tacticalDungeonGroups)
    tacticalAnnouncementPanel:SetPoint("TOPLEFT", tacticalTitle, "BOTTOMLEFT", OPTIONS_SECTION_BODY_INSET_X, -OPTIONS_TITLE_TO_CONTROLS_GAP)
    tacticalAnnouncementPanel:SetPoint("TOPRIGHT", tacticalTitle, "BOTTOMRIGHT", -OPTIONS_SECTION_BODY_INSET_X, -OPTIONS_TITLE_TO_CONTROLS_GAP)
    tacticalAnnouncementPanel:SetHeight(tacticalPanelHeight)

    local tacticalAnnouncementInputs = {}
    for index, dungeonGroup in ipairs(tacticalDungeonGroups) do
        local row = tacticalAnnouncementPanel.rows[index]
        local mapID = dungeonGroup.mapID
        core.CreateOptionsRowLabel(row, dungeonGroup.dungeonName or TRANSLATE("未知地下城"))
        local tacticalConfirmButton = CreateOptionsConfirmButton(row)
        local tacticalClearButton = CreateOptionsClearButton(row)
        local tacticalInput = CreateOptionsTextInput(
            row,
            owner,
            CreateOptionsTacticalAnnouncementGetter(mapID),
            CreateOptionsTacticalAnnouncementSetter(mapID),
            OPTIONS_MESSAGE_INPUT_WIDTH,
            {
                maxBytes = OPTIONS_TACTICAL_MESSAGE_MAX_BYTES,
                maxLetters = OPTIONS_TACTICAL_MESSAGE_MAX_BYTES,
                placeholder = TRANSLATE("战术通报将在副本传送时自动发送至聊天频道"),
            }
        )
        tacticalInput.mkMapID = mapID
        tacticalInput.mkConfirmButton = tacticalConfirmButton
        tacticalInput.mkClearButton = tacticalClearButton
        AnchorOptionsTacticalMessageControls(row, tacticalInput, tacticalConfirmButton, tacticalClearButton)
        ConfigureOptionsTacticalAnnouncementActions(tacticalInput, tacticalConfirmButton, tacticalClearButton)
        tacticalAnnouncementInputs[#tacticalAnnouncementInputs + 1] = tacticalInput
    end

    local tacticalTitleHeight = math.max(tacticalTitle:GetHeight() or 0, 40)
    content.mkOptionsContentHeight = math.max(
        OPTIONS_CONTENT_HEIGHT,
        OPTIONS_CONTENT_TOP_OFFSET
            + optionsControlsHeight
            + OPTIONS_SECTION_GAP
            + tacticalTitleHeight
            + OPTIONS_TITLE_TO_CONTROLS_GAP
            + tacticalPanelHeight
            + OPTIONS_CONTENT_BOTTOM_PADDING
    )
    content:SetHeight(content.mkOptionsContentHeight)

    owner.widgets = {
        tabGroup = owner.widgets and owner.widgets.tabGroup or tabGroup,
        root = optionsFrame,
        optionsFrame = optionsFrame,
        optionsScrollFrame = scrollFrame,
        optionsScrollBar = scrollBar,
        optionsContent = content,
        defaultSettingsButton = defaultSettingsButton,
        aboutButton = aboutButton,
        optionsControls = optionsControls,
        optionsInterfaceTitle = interfaceGroup.title,
        optionsInterfacePanel = interfaceGroup.panel,
        optionsTeleportTitle = teleportGroup.title,
        optionsTeleportPanel = teleportGroup.panel,
        optionsCompletionTitle = completionGroup.title,
        optionsCompletionPanel = completionGroup.panel,
        optionsPreviewPanel = optionsPreviewPanel,
        tacticalTitle = tacticalTitle,
        tacticalTitleBg = tacticalTitleBg,
        tacticalTitleText = tacticalTitleText,
        tacticalAnnouncementPanel = tacticalAnnouncementPanel,
        tacticalAnnouncementInputs = tacticalAnnouncementInputs,
        minimapToggle = minimapToggle,
        microMenuToggle = microMenuToggle,
        startupTabDropdown = startupTabDropdown,
        rightClickStartupTabDropdown = rightClickStartupTabDropdown,
        findGroupMemberModeDropdown = findGroupMemberModeDropdown,
        autoOpenDropdown = autoOpenDropdown,
        keystoneAnnouncementToggle = keystoneAnnouncementToggle,
        mainWindowScaleSlider = mainWindowScaleSlider,
        teleportAnnouncementToggle = teleportAnnouncementToggle,
        teleportPromptToggle = teleportPromptToggle,
        teleportPromptDescription = teleportPromptDescription,
        teleportPromptPreviewButton = teleportPromptPreviewButton,
        teleportDialogToggle = teleportAnnouncementToggle,
        teleportDialogMessageBox = teleportDialogMessageBox,
        teleportDialogMessageConfirmButton = teleportDialogMessageConfirmButton,
        teleportDialogMessagePreview = teleportDialogMessagePreview,
    }
    owner.tabViews = owner.tabViews or {}
    owner.tabViews.options = owner.widgets

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

    C_Timer.After(0, function()
        if core.FinalizeFlatTabLayout then
            core.FinalizeFlatTabLayout(tabGroup)
        end
        if owner.activeTab == "options" and owner.widgets and owner.widgets.optionsContent == content and scrollFrame then
            UpdateOptionsScrollState(owner.widgets)
        end
    end)

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

function OptionsTab.Refresh(owner, widgets)
    if not widgets or not widgets.minimapToggle then
        return
    end

    widgets.minimapToggle:SetChecked(not owner.db.profile.minimap.hide)
    if widgets.microMenuToggle then
        widgets.microMenuToggle:SetChecked(owner:IsMicroMenuButtonEnabled())
    end
    if widgets.teleportAnnouncementToggle then
        widgets.teleportAnnouncementToggle:SetChecked(owner:IsDungeonTeleportAnnouncementEnabled())
    elseif widgets.teleportDialogToggle then
        widgets.teleportDialogToggle:SetChecked(owner:IsDungeonTeleportAnnouncementEnabled())
    end
    if widgets.teleportPromptToggle then
        widgets.teleportPromptToggle:SetChecked(owner:IsDungeonTeleportFollowEnabled())
    end
    if widgets.keystoneAnnouncementToggle then
        widgets.keystoneAnnouncementToggle:SetChecked(owner:IsKeystoneAnnouncementEnabled())
    end
    if widgets.aboutButton then
        widgets.aboutButton.mkTooltipText = TRANSLATE("关于大秘境钥石助手")
    end
    if widgets.teleportDialogMessageBox and widgets.teleportDialogMessageBox.mkSetDisplayText then
        widgets.teleportDialogMessageBox:mkSetDisplayText(owner:GetDungeonTeleportDialogMessage())
    end
    if widgets.teleportDialogMessagePreview and widgets.teleportDialogMessagePreview.mkSetPreviewMessage then
        local previewValue = owner:GetDungeonTeleportDialogMessage()
        if widgets.teleportDialogMessageBox and widgets.teleportDialogMessageBox.mkEditBox
            and widgets.teleportDialogMessageBox.mkEditBox.HasFocus
            and widgets.teleportDialogMessageBox.mkEditBox:HasFocus()
        then
            previewValue = widgets.teleportDialogMessageBox.mkEditBox:GetText() or previewValue
        end
        widgets.teleportDialogMessagePreview:mkSetPreviewMessage(previewValue)
    end
    if widgets.tacticalAnnouncementInputs then
        for _, tacticalInput in ipairs(widgets.tacticalAnnouncementInputs) do
            if tacticalInput.mkMapID and tacticalInput.mkSetDisplayText then
                tacticalInput:mkSetDisplayText(owner:GetDungeonTeleportTacticalAnnouncement(tacticalInput.mkMapID))
            end
        end
    end
    if widgets.startupTabDropdown then
        if widgets.startupTabDropdown.GenerateMenu then
            widgets.startupTabDropdown:GenerateMenu()
        end
        if widgets.startupTabDropdown.SetSelectionText then
            widgets.startupTabDropdown:SetSelectionText(function()
                return GetStartupTabOptionText(owner:GetStartupTab())
            end)
        end
        if widgets.startupTabDropdown.Update then
            widgets.startupTabDropdown:Update()
        end
    end
    if widgets.rightClickStartupTabDropdown then
        if widgets.rightClickStartupTabDropdown.GenerateMenu then
            widgets.rightClickStartupTabDropdown:GenerateMenu()
        end
        if widgets.rightClickStartupTabDropdown.SetSelectionText then
            widgets.rightClickStartupTabDropdown:SetSelectionText(function()
                return GetStartupTabOptionText(owner:GetRightClickStartupTab())
            end)
        end
        if widgets.rightClickStartupTabDropdown.Update then
            widgets.rightClickStartupTabDropdown:Update()
        end
    end
    if widgets.findGroupMemberModeDropdown then
        if widgets.findGroupMemberModeDropdown.GenerateMenu then
            widgets.findGroupMemberModeDropdown:GenerateMenu()
        end
        if widgets.findGroupMemberModeDropdown.SetSelectionText then
            widgets.findGroupMemberModeDropdown:SetSelectionText(function()
                return GetFindGroupMemberDisplayModeOptionText(owner:GetFindGroupMemberDisplayMode())
            end)
        end
        if widgets.findGroupMemberModeDropdown.Update then
            widgets.findGroupMemberModeDropdown:Update()
        end
    end
    if widgets.autoOpenDropdown then
        if widgets.autoOpenDropdown.GenerateMenu then
            widgets.autoOpenDropdown:GenerateMenu()
        end
        if widgets.autoOpenDropdown.SetSelectionText then
            widgets.autoOpenDropdown:SetSelectionText(function()
                return GetAutoOpenCompletedRunOptionText(owner:GetAutoOpenOnCompletedRunTab())
            end)
        end
        if widgets.autoOpenDropdown.Update then
            widgets.autoOpenDropdown:Update()
        end
    end
    if widgets.mainWindowScaleSlider and widgets.mainWindowScaleSlider.mkSetValue then
        widgets.mainWindowScaleSlider:mkSetValue(owner.GetMainWindowScale and owner:GetMainWindowScale() or 1)
    end
    UpdateOptionsScrollState(widgets)
end

function MK:BuildOptionsTabSnapshot()
    local minimapHidden = self.db and self.db.profile and self.db.profile.minimap and self.db.profile.minimap.hide and "1" or "0"
    local warbandTag = self:GetWarbandTag() or ""
    local startupTab = self:GetStartupTab()
    local rightClickStartupTab = self:GetRightClickStartupTab()
    local autoOpenOnCompletedRun = self:GetAutoOpenOnCompletedRunTab()
    local findGroupMemberDisplayMode = self:GetFindGroupMemberDisplayMode()
    local mainWindowScale = self.GetMainWindowScale and self:GetMainWindowScale() or 1
    local keystoneAnnouncement = self:IsKeystoneAnnouncementEnabled() and "1" or "0"
    local dungeonTeleportAnnouncement = self:IsDungeonTeleportAnnouncementEnabled() and "1" or "0"
    local dungeonTeleportPrompt = self:IsDungeonTeleportFollowEnabled() and "1" or "0"
    local microMenuButton = self:IsMicroMenuButtonEnabled() and "1" or "0"
    local dungeonTeleportDialogMessage = self:GetDungeonTeleportDialogMessage() or ""
    local tacticalParts = {}
    for _, dungeonGroup in ipairs(GetOptionsTacticalDungeonGroups(self)) do
        local mapID = dungeonGroup.mapID
        tacticalParts[#tacticalParts + 1] = table.concat({
            SnapshotValue(mapID),
            SnapshotValue(self:GetDungeonTeleportTacticalAnnouncement(mapID)),
        }, "~")
    end

    return table.concat({
        "options",
        minimapHidden,
        microMenuButton,
        warbandTag,
        startupTab,
        rightClickStartupTab,
        findGroupMemberDisplayMode,
        autoOpenOnCompletedRun,
        SnapshotValue(mainWindowScale),
        keystoneAnnouncement,
        dungeonTeleportAnnouncement,
        dungeonTeleportPrompt,
        dungeonTeleportDialogMessage,
        table.concat(tacticalParts, "||"),
    }, "|")
end

local SETTINGS_ADDON_NAME = core.ADDON_NAME or addonName or "MyKeyStone"
local SETTINGS_ADDON_DISPLAY_NAME_KEY = core.ADDON_DISPLAY_NAME_KEY or "大秘境钥石助手"
local SETTINGS_ADDON_LOGO_PATH = core.ADDON_LOGO_PATH or ("Interface\\AddOns\\" .. tostring(addonName or "MyKeyStone") .. "\\Art\\Logo\\MyKeyStone-logo.png")

local function GetLocalizedAddonDisplayName()
    local displayName = TRANSLATE(SETTINGS_ADDON_DISPLAY_NAME_KEY)
    if type(displayName) ~= "string" or displayName == "" then
        return SETTINGS_ADDON_NAME
    end
    return displayName
end

local function GetAddonMetadata(fieldName, fallback)
    local getter = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetadata
    if getter then
        local ok, value = pcall(getter, addonName, fieldName)
        if ok and type(value) == "string" and value ~= "" then
            return value
        end
    end

    return fallback
end

local ABOUT_DIALOG_FOOTER_TEXT = "COPYRIGHT (C) 2026 GAICAS.COM ALL RIGHTS RESERVED."

local function ApplyAboutDialogFont(fontString, size, flags)
    if not fontString then
        return
    end
    if core.ApplyFont then
        core.ApplyFont(fontString, size, flags or "")
    end
end

local ABOUT_REPLICA_DIALOG_WIDTH = 760
local ABOUT_REPLICA_DIALOG_HEIGHT = 688
local ABOUT_REPLICA_BODY_LEFT = 28
local ABOUT_REPLICA_BODY_RIGHT = 28
local ABOUT_REPLICA_BODY_TOP = -52
local ABOUT_REPLICA_BODY_BOTTOM = 26
local ABOUT_REPLICA_BODY_BG_ALPHA = 0.92
local ABOUT_REPLICA_CONTENT_WIDTH = ABOUT_REPLICA_DIALOG_WIDTH - ABOUT_REPLICA_BODY_LEFT - ABOUT_REPLICA_BODY_RIGHT
local ABOUT_REPLICA_CONTENT_HEIGHT = ABOUT_REPLICA_DIALOG_HEIGHT + ABOUT_REPLICA_BODY_TOP - ABOUT_REPLICA_BODY_BOTTOM
local ABOUT_REPLICA_TITLE_ATLAS_TEXTURE = "Interface\\AddOns\\" .. tostring(addonName or "MyKeyStone") .. "\\Art\\Ui\\InfoTittle.png"
local ABOUT_REPLICA_INFO_BOX_TEXTURE = "Interface\\AddOns\\" .. tostring(addonName or "MyKeyStone") .. "\\Art\\Ui\\InfoTM.png"
local ABOUT_REPLICA_LOGO_TEXTURE = core.ADDON_PANEL_PORTRAIT_PATH or SETTINGS_ADDON_LOGO_PATH
local ABOUT_REPLICA_TITLE_ATLAS_WIDTH = 1689
local ABOUT_REPLICA_TITLE_ATLAS_HEIGHT = 348
local ABOUT_REPLICA_INFO_BOX_ATLAS_WIDTH = 1507
local ABOUT_REPLICA_INFO_BOX_ATLAS_HEIGHT = 324
local ABOUT_REPLICA_TITLE_BAND_HEIGHT = 180
local ABOUT_REPLICA_TITLE_BAND_INSET_X = -16
local ABOUT_REPLICA_TITLE_BAND_TOP_Y = -28
local ABOUT_REPLICA_LOGO_SIZE = 72
local ABOUT_REPLICA_BRAND_TOP = -50
local ABOUT_REPLICA_TITLE_DESC_GAP = 12
local ABOUT_REPLICA_INTRO_WIDTH = 548
local ABOUT_REPLICA_INFO_BOX_HEIGHT = 200
local ABOUT_REPLICA_INFO_BOX_INSET_X = 24
local ABOUT_REPLICA_INFO_BOX_BOTTOM_Y = 38
local ABOUT_REPLICA_NOTICE_SCROLL_INSET_LEFT = 24
local ABOUT_REPLICA_NOTICE_SCROLL_INSET_RIGHT = 32
local ABOUT_REPLICA_NOTICE_SCROLL_INSET_TOP = 22
local ABOUT_REPLICA_NOTICE_SCROLL_INSET_BOTTOM = 18
local ABOUT_REPLICA_NOTICE_SCROLLBAR_GAP = 2
local ABOUT_REPLICA_NOTICE_SCROLLBAR_WIDTH = 8
local ABOUT_REPLICA_NOTICE_TITLE_GAP = 12
local ABOUT_REPLICA_NOTICE_VERSION_GAP = 8
local ABOUT_REPLICA_NOTICE_LINE_GAP = 6
local ABOUT_REPLICA_NOTICE_SECTION_GAP = 12
local ABOUT_REPLICA_NOTICE_RULE_GAP = 8
local ABOUT_REPLICA_NOTICE_TEXT_WIDTH = ABOUT_REPLICA_CONTENT_WIDTH
    - ABOUT_REPLICA_INFO_BOX_INSET_X * 2
    - ABOUT_REPLICA_NOTICE_SCROLL_INSET_LEFT
    - ABOUT_REPLICA_NOTICE_SCROLL_INSET_RIGHT
local ABOUT_REPLICA_NOTICE_SCROLL_HEIGHT = ABOUT_REPLICA_INFO_BOX_HEIGHT
    - ABOUT_REPLICA_NOTICE_SCROLL_INSET_TOP
    - ABOUT_REPLICA_NOTICE_SCROLL_INSET_BOTTOM
local ABOUT_REPLICA_COMMAND_TOP_GAP = 18
local ABOUT_REPLICA_INFO_LEFT_X = 64
local ABOUT_REPLICA_INFO_RIGHT_X = 388
local ABOUT_REPLICA_INFO_TOP_GAP = 12
local ABOUT_REPLICA_GOLD = { 1, 0.82, 0, 1 }
local ABOUT_REPLICA_BODY_TEXT = { 238 / 255, 228 / 255, 205 / 255, 1 }
local ABOUT_REPLICA_LINK_BLUE = { 130 / 255, 204 / 255, 1, 1 }
local ABOUT_REPLICA_COMMAND_ORANGE = { 1, 0.45, 0.16, 1 }
local ABOUT_REPLICA_FOOTER_GRAY = { 0.5, 0.5, 0.5, 1 }
local ABOUT_REPLICA_NOTICE_GRAY = { 0.42, 0.42, 0.42, 1 }
local ABOUT_REPLICA_FONT_BRAND_TITLE = 30
local ABOUT_REPLICA_FONT_VERSION_INLINE = 12
local ABOUT_REPLICA_FONT_DESCRIPTION_TEXT = 14
local ABOUT_REPLICA_FONT_COMMAND_TEXT = 20
local ABOUT_REPLICA_FONT_COMMAND_TITLE = 18
local ABOUT_REPLICA_FONT_INFO_TEXT = 15
local ABOUT_REPLICA_FONT_NOTICE_TEXT = 13
local ABOUT_REPLICA_FONT_NOTICE_TITLE = 18
local ABOUT_REPLICA_FONT_NOTICE_VERSION = 16
local ABOUT_REPLICA_FONT_FOOTER_TEXT = 12

local ABOUT_REPLICA_NOTICE_ENTRIES = {
    {
        version = "1.0.10",
        lines = {
            "优化：队伍成员按照职“坦克、治疗、输出”职责排序，专精图标右上角增加职责图标。",
        },
    },
    {
        version = "1.0.9",
        lines = {
            "新增：队伍列表新增队伍成员模式，可切换职责 / 专精视图；",
            "新增：钥石信息变化将在队伍频道自动通报修复；",
            "优化：无招募管理权限时，队伍面板下创建招募功能将转变为钥石通报功能；",
            "优化：关闭主面板或切出寻找队伍后，借用的系统队伍查找器搜索框会立即归还。",
        },
    },
    {
        version = "1.0.8",
        lines = {
            "优化：全界面统一新版逃兵状态图标，覆盖队伍、车队、集合石申请、寻队列表；",
            "升级：升级内置 NetEaseAPI 至 minor=2026062501；广播中间件新增频道消息 secret 加密保护，提升版本号解决旧库抢占加载问题；",
            "修复：服务器失联时聊天栏弹出 S1Alliance/S1Horde 未找到提示，Socket 处理器采用短名 + 全名双匹配逻辑。",
        },
    },
    {
        version = "1.0.7",
        lines = {
            "优化：为“自动进组”功能新增适用范围判断。该功能现在仅对通过本插件创建的招募队伍生效，避免影响系统原生队伍或其他插件创建的招募流程。",
            "修复：修复寻找队伍界面借用暴雪原生搜索框后，字体、占位文字、搜索图标、清除按钮或禁用状态可能残留到系统原生队伍查找器及其他插件界面的问题。",
        },
    },
    {
        version = "1.0.6",
        lines = {
            "重构：招募队伍名称、组队说明输入框声明周期，修复输入失焦、招募信息更新提交失败问题。",
        },
    },
    {
        version = "1.0.5",
        lines = {
            "修复：战斗中网易频道广播收到 secret 聊天消息时可能触发 Lua 错误的问题。",
            "修复：车队页面中队友共享的战团候选角色可能继承分享者当前队伍职责的问题，改为使用候选角色自身同步来的职责。",
        },
    },
    {
        version = "1.0.4",
        lines = {
            "新增：在申请列表中，增加了 tooltip 队长标识。",
            "优化：将“集合石”更名为“创建招募”，集合石独立插件即将上线，敬请期待。",
            "优化：寻找队伍搜索逻辑优化，减少搜索后界面卡顿。",
            "优化：针对网易集合石的账号类型 API 接口进行兼容重构，当前已不依赖“网易集合石插件”。",
        },
    },
    {
        version = "1.0.3",
        lines = {
            "新增： 同步更新网易集合石 API 版本。",
        },
    },
    {
        version = "1.0.2",
        lines = {
            "新增：当网易集合石 API 故障或不可达时， 根据 API 接口的返回暂停对 API 的持续查询调取。",
        },
    },
    {
        version = "1.0.1",
        lines = {
            "适配 12.0.7，增加调试功能",
        },
    },
    {
        version = "1.0.0",
        lines = {
            "由于 CurseForge 分发名称问题，MythicKeystoneAssistant 现更名为 MyKeyStone。功能未变，体验升级，欢迎下载体验。",
            "注：在安装 MyKeyStone 前，你需要先手动删除 MythicKeystoneAssistant 的安装文件。",
        },
    },
}

local function SetAboutReplicaTextColor(fontString, color)
    if fontString and color then
        fontString:SetTextColor(color[1], color[2], color[3], color[4] or 1)
    end
end

local function CreateAboutReplicaText(parent, template, size, color, flags)
    local fontString = parent:CreateFontString(nil, "ARTWORK", template or "GameFontHighlight")
    ApplyAboutDialogFont(fontString, size or 12, flags or "")
    SetAboutReplicaTextColor(fontString, color or ABOUT_REPLICA_BODY_TEXT)
    return fontString
end

local function SetAboutReplicaTexCoordByPixels(texture, sourceWidth, sourceHeight, left, right, top, bottom)
    texture:SetTexCoord(left / sourceWidth, right / sourceWidth, top / sourceHeight, bottom / sourceHeight)
end

local function CreateAboutReplicaSlicedAtlas(parent, texturePath, sourceWidth, sourceHeight, caps, layer, subLevel)
    local frame = CreateFrame("Frame", nil, parent)
    frame.parts = {}
    local names = {
        "topLeft", "top", "topRight",
        "left", "center", "right",
        "bottomLeft", "bottom", "bottomRight",
    }
    for _, name in ipairs(names) do
        local texture = frame:CreateTexture(nil, layer or "BACKGROUND", nil, subLevel or 0)
        texture:SetTexture(texturePath)
        frame.parts[name] = texture
    end

    local sourceLeft = caps.left or 0
    local sourceRight = caps.right or 0
    local sourceTop = caps.top or 0
    local sourceBottom = caps.bottom or 0
    local scale = caps.scale or 1
    local left = sourceLeft * scale
    local right = sourceRight * scale
    local top = sourceTop * scale
    local bottom = sourceBottom * scale
    local srcLeft = sourceLeft
    local srcRight = sourceWidth - sourceRight
    local srcTop = sourceTop
    local srcBottom = sourceHeight - sourceBottom
    local parts = frame.parts

    parts.topLeft:SetSize(left, top)
    parts.topLeft:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.topLeft, sourceWidth, sourceHeight, 0, srcLeft, 0, srcTop)

    parts.top:SetHeight(top)
    parts.top:SetPoint("TOPLEFT", parts.topLeft, "TOPRIGHT", 0, 0)
    parts.top:SetPoint("TOPRIGHT", parts.topRight, "TOPLEFT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.top, sourceWidth, sourceHeight, srcLeft, srcRight, 0, srcTop)

    parts.topRight:SetSize(right, top)
    parts.topRight:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.topRight, sourceWidth, sourceHeight, srcRight, sourceWidth, 0, srcTop)

    parts.left:SetWidth(left)
    parts.left:SetPoint("TOPLEFT", parts.topLeft, "BOTTOMLEFT", 0, 0)
    parts.left:SetPoint("BOTTOMLEFT", parts.bottomLeft, "TOPLEFT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.left, sourceWidth, sourceHeight, 0, srcLeft, srcTop, srcBottom)

    parts.center:SetPoint("TOPLEFT", parts.topLeft, "BOTTOMRIGHT", 0, 0)
    parts.center:SetPoint("BOTTOMRIGHT", parts.bottomRight, "TOPLEFT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.center, sourceWidth, sourceHeight, srcLeft, srcRight, srcTop, srcBottom)

    parts.right:SetWidth(right)
    parts.right:SetPoint("TOPRIGHT", parts.topRight, "BOTTOMRIGHT", 0, 0)
    parts.right:SetPoint("BOTTOMRIGHT", parts.bottomRight, "TOPRIGHT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.right, sourceWidth, sourceHeight, srcRight, sourceWidth, srcTop, srcBottom)

    parts.bottomLeft:SetSize(left, bottom)
    parts.bottomLeft:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.bottomLeft, sourceWidth, sourceHeight, 0, srcLeft, srcBottom, sourceHeight)

    parts.bottom:SetHeight(bottom)
    parts.bottom:SetPoint("BOTTOMLEFT", parts.bottomLeft, "BOTTOMRIGHT", 0, 0)
    parts.bottom:SetPoint("BOTTOMRIGHT", parts.bottomRight, "BOTTOMLEFT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.bottom, sourceWidth, sourceHeight, srcLeft, srcRight, srcBottom, sourceHeight)

    parts.bottomRight:SetSize(right, bottom)
    parts.bottomRight:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
    SetAboutReplicaTexCoordByPixels(parts.bottomRight, sourceWidth, sourceHeight, srcRight, sourceWidth, srcBottom, sourceHeight)

    return frame
end

local function GetAboutReplicaTextWidth(fontString, fallback)
    local width = fontString and fontString.GetStringWidth and fontString:GetStringWidth() or 0
    if type(width) ~= "number" or width <= 0 then
        return fallback or 0
    end
    return width
end

local function RefreshAboutReplicaBrandLayout(frame)
    if not frame then
        return
    end
    local titleWidth = math.ceil(GetAboutReplicaTextWidth(frame.BrandTitle, 220))
    local versionWidth = math.ceil(GetAboutReplicaTextWidth(frame.VersionInline, 36))
    frame.BrandLine:SetSize(ABOUT_REPLICA_CONTENT_WIDTH, 42)
    frame.BrandTitle:SetWidth(titleWidth + 8)
    frame.BrandTitle:ClearAllPoints()
    frame.BrandTitle:SetPoint("CENTER", frame.BrandLine, "CENTER", 0, 0)
    frame.VersionInline:SetWidth(versionWidth + 8)
    frame.VersionInline:ClearAllPoints()
    frame.VersionInline:SetPoint("BOTTOMLEFT", frame.BrandTitle, "BOTTOMRIGHT", 4, 6)
end

local function ClearAboutReplicaNoticeRows(frame)
    frame.NoticeRows = frame.NoticeRows or {}
    for _, fontString in ipairs(frame.NoticeRows) do
        fontString:Hide()
    end
end

local function AcquireAboutReplicaNoticeText(frame, index, template, size, color, flags)
    frame.NoticeRows = frame.NoticeRows or {}
    local fontString = frame.NoticeRows[index]
    if not fontString then
        fontString = CreateAboutReplicaText(frame.NoticeBody, template, size, color, flags)
        frame.NoticeRows[index] = fontString
    else
        fontString:SetParent(frame.NoticeBody)
        ApplyAboutDialogFont(fontString, size or 12, flags or "")
        SetAboutReplicaTextColor(fontString, color or ABOUT_REPLICA_BODY_TEXT)
    end
    fontString:ClearAllPoints()
    fontString:SetWidth(ABOUT_REPLICA_NOTICE_TEXT_WIDTH)
    fontString:SetJustifyH("LEFT")
    fontString:SetWordWrap(true)
    fontString:Show()
    return fontString
end

local function AddAboutReplicaNoticeLine(frame, index, y, text, template, size, color, flags, gap, justify)
    local fontString = AcquireAboutReplicaNoticeText(frame, index, template, size, color, flags)
    fontString:SetText(text or "")
    fontString:SetPoint("TOPLEFT", frame.NoticeBody, "TOPLEFT", 0, y)
    fontString:SetJustifyH(justify or "LEFT")
    local height = fontString.GetStringHeight and fontString:GetStringHeight() or 0
    if type(height) ~= "number" or height <= 0 then
        height = size or ABOUT_REPLICA_FONT_NOTICE_TEXT
    end
    height = math.ceil(height)
    fontString:SetHeight(height + 2)
    return index + 1, y - height - (gap or ABOUT_REPLICA_NOTICE_LINE_GAP)
end

local function AddAboutReplicaNoticeRule(frame, index, y)
    frame.NoticeRules = frame.NoticeRules or {}
    local rule = frame.NoticeRules[index]
    if not rule then
        rule = frame.NoticeBody:CreateTexture(nil, "ARTWORK")
        frame.NoticeRules[index] = rule
    end
    rule:ClearAllPoints()
    rule:SetColorTexture(1, 0.82, 0, 0.22)
    rule:SetPoint("TOPLEFT", frame.NoticeBody, "TOPLEFT", 0, y)
    rule:SetPoint("TOPRIGHT", frame.NoticeBody, "TOPRIGHT", 0, y)
    rule:SetHeight(1)
    rule:Show()
    return index + 1, y - ABOUT_REPLICA_NOTICE_RULE_GAP
end

local function HideUnusedAboutReplicaNoticeRules(frame, startIndex)
    if not frame.NoticeRules then
        return
    end
    for index = startIndex, #frame.NoticeRules do
        frame.NoticeRules[index]:Hide()
    end
end

local function UpdateAboutReplicaNoticeScroll(frame, contentHeight)
    local scrollFrame = frame and frame.NoticeScroll
    local scrollBar = frame and frame.NoticeScrollBar
    if not (scrollFrame and scrollBar) then
        return
    end

    if scrollFrame.UpdateScrollChildRect then
        scrollFrame:UpdateScrollChildRect()
    end

    local viewHeight = scrollFrame.GetHeight and scrollFrame:GetHeight() or ABOUT_REPLICA_NOTICE_SCROLL_HEIGHT
    local maxScroll = math.max((contentHeight or ABOUT_REPLICA_NOTICE_SCROLL_HEIGHT) - viewHeight, 0)
    if scrollBar.mkSetScrollRange then
        scrollBar.mkSetScrollRange(0, maxScroll)
    elseif scrollBar.SetMinMaxValues then
        scrollBar:SetMinMaxValues(0, maxScroll)
    end
    if scrollBar.mkSetScrollValue then
        scrollBar.mkSetScrollValue(0)
    elseif scrollBar.SetValue then
        scrollBar:SetValue(0)
    elseif scrollBar.SetScrollPercentage then
        scrollBar:SetScrollPercentage(0)
    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(maxScroll > 0)
    end
    scrollFrame:SetVerticalScroll(0)
    if maxScroll > 0 then
        scrollBar:Show()
    else
        scrollBar:Hide()
    end
end

local function RefreshAboutReplicaNoticeContent(frame)
    local entries = ABOUT_REPLICA_NOTICE_ENTRIES
    if type(entries) ~= "table" or #entries == 0 then
        ClearAboutReplicaNoticeRows(frame)
        HideUnusedAboutReplicaNoticeRules(frame, 1)
        frame.NoticeScroll:Hide()
        if frame.NoticeScrollBar then
            frame.NoticeScrollBar:Hide()
        end
        frame.NoticeEmpty:SetText(TRANSLATE("无通知"))
        frame.NoticeEmpty:Show()
        return
    end

    frame.NoticeEmpty:Hide()
    frame.NoticeScroll:Show()
    ClearAboutReplicaNoticeRows(frame)
    local rowIndex = 1
    local ruleIndex = 1
    local y = 0
    rowIndex, y = AddAboutReplicaNoticeLine(
        frame,
        rowIndex,
        y,
        TRANSLATE("更新日志"),
        "GameFontNormalLarge",
        ABOUT_REPLICA_FONT_NOTICE_TITLE,
        ABOUT_REPLICA_GOLD,
        "OUTLINE",
        ABOUT_REPLICA_NOTICE_TITLE_GAP,
        "CENTER"
    )
    ruleIndex, y = AddAboutReplicaNoticeRule(frame, ruleIndex, y)
    y = y - ABOUT_REPLICA_NOTICE_SECTION_GAP

    for entryIndex, entry in ipairs(entries) do
        if type(entry) == "table" then
            local version = entry.version or ""
            if version ~= "" then
                rowIndex, y = AddAboutReplicaNoticeLine(
                    frame,
                    rowIndex,
                    y,
                    version,
                    "GameFontNormalLarge",
                    ABOUT_REPLICA_FONT_NOTICE_VERSION,
                    ABOUT_REPLICA_GOLD,
                    "OUTLINE",
                    ABOUT_REPLICA_NOTICE_VERSION_GAP
                )
            end
            local lines = entry.lines
            if type(lines) == "table" then
                for _, line in ipairs(lines) do
                    rowIndex, y = AddAboutReplicaNoticeLine(
                        frame,
                        rowIndex,
                        y,
                        line,
                        "GameFontHighlight",
                        ABOUT_REPLICA_FONT_NOTICE_TEXT,
                        ABOUT_REPLICA_BODY_TEXT,
                        "",
                        ABOUT_REPLICA_NOTICE_LINE_GAP
                    )
                end
            end
            if entryIndex < #entries then
                y = y - ABOUT_REPLICA_NOTICE_SECTION_GAP
                ruleIndex, y = AddAboutReplicaNoticeRule(frame, ruleIndex, y)
                y = y - ABOUT_REPLICA_NOTICE_SECTION_GAP
            end
        end
    end

    HideUnusedAboutReplicaNoticeRules(frame, ruleIndex)
    local contentHeight = math.max(ABOUT_REPLICA_NOTICE_SCROLL_HEIGHT, math.ceil(-y + ABOUT_REPLICA_NOTICE_SCROLL_INSET_BOTTOM))
    frame.NoticeBody:SetSize(ABOUT_REPLICA_NOTICE_TEXT_WIDTH, contentHeight)
    UpdateAboutReplicaNoticeScroll(frame, contentHeight)
end

local function CreateAboutReplicaInfoPair(parent, point, relativeTo, relativePoint, x, y)
    local row = CreateFrame("Frame", nil, parent)
    row:SetPoint(point, relativeTo, relativePoint, x, y)
    row:SetSize(280, 24)
    row.Label = CreateAboutReplicaText(row, "GameFontNormal", ABOUT_REPLICA_FONT_INFO_TEXT, ABOUT_REPLICA_GOLD, "")
    row.Label:SetPoint("LEFT", row, "LEFT", 0, 0)
    row.Label:SetSize(82, 20)
    row.Label:SetJustifyH("LEFT")
    row.Value = CreateAboutReplicaText(row, "GameFontHighlight", ABOUT_REPLICA_FONT_INFO_TEXT, ABOUT_REPLICA_BODY_TEXT, "")
    row.Value:SetPoint("LEFT", row.Label, "RIGHT", 0, 0)
    row.Value:SetSize(190, 20)
    row.Value:SetJustifyH("LEFT")
    return row
end

local function CreateAboutReplicaScrollContainer(parent)
    if core.CreateNativeScrollContainer then
        return core.CreateNativeScrollContainer(parent, nil, 0)
    end

    local scrollFrame = CreateFrame("ScrollFrame", nil, parent)
    scrollFrame:EnableMouseWheel(true)
    local content = CreateFrame("Frame", nil, scrollFrame)
    scrollFrame:SetScrollChild(content)
    local scrollBar = CreateFrame("Slider", nil, parent)
    scrollBar:SetOrientation("VERTICAL")
    scrollBar:SetValueStep(1)
    scrollBar:SetMinMaxValues(0, 0)
    scrollBar.mkSetScrollRange = function(minValue, maxValue)
        scrollBar:SetMinMaxValues(minValue or 0, maxValue or 0)
    end
    scrollBar.mkSetScrollValue = function(value)
        scrollBar:SetValue(value or 0)
    end
    scrollBar:SetScript("OnValueChanged", function(_, value)
        scrollFrame:SetVerticalScroll(value or 0)
    end)
    scrollFrame:SetScript("OnMouseWheel", function(_, delta)
        local minValue, maxValue = scrollBar:GetMinMaxValues()
        local nextValue = math.min(math.max((scrollBar:GetValue() or 0) - (delta or 0) * 36, minValue or 0), maxValue or 0)
        scrollBar:SetValue(nextValue)
    end)
    return scrollFrame, content, scrollBar
end

local function GetOptionsAboutReplicaDialogFrame(owner)
    if owner.optionsAboutDialog and owner.optionsAboutDialog.mkAboutReplica == true then
        return owner.optionsAboutDialog
    end

    local frame
    if core.CreateFrameWithTemplateOptions then
        frame = core.CreateFrameWithTemplateOptions("Frame", "MKAboutDialog", UIParent, {
            "SettingsFrameTemplate",
            "PortraitFrameTemplate",
            "BackdropTemplate",
        })
    end
    frame = frame or CreateFrame("Frame", "MKAboutDialog", UIParent, "BackdropTemplate")
    frame.mkAboutReplica = true
    frame:SetSize(ABOUT_REPLICA_DIALOG_WIDTH, ABOUT_REPLICA_DIALOG_HEIGHT)
    frame:SetFrameStrata("DIALOG")
    frame:SetFrameLevel(220)
    frame:SetClampedToScreen(true)
    if frame.SetToplevel then
        frame:SetToplevel(true)
    end
    frame:EnableMouse(true)
    frame:Hide()
    owner.optionsAboutDialog = frame

    if type(UISpecialFrames) == "table" and frame.GetName and frame:GetName() then
        local frameName = frame:GetName()
        local exists = false
        for _, name in ipairs(UISpecialFrames) do
            if name == frameName then
                exists = true
                break
            end
        end
        if not exists then
            table.insert(UISpecialFrames, frameName)
        end
    end

    local titleText = frame.TitleText
        or frame.title
        or (frame.NineSlice and frame.NineSlice.Text)
        or _G.MKAboutDialogTitleText
    if titleText then
        frame.TitleText = titleText
        ApplyAboutDialogFont(titleText, 14, "")
        titleText:SetTextColor(1, 0.82, 0, 1)
        titleText:SetText(TRANSLATE("关于大秘境钥石助手"))
    end

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

    if frame.Bg and frame.Bg.Hide then
        frame.Bg:Hide()
    end
    frame.BodyBackground = frame:CreateTexture(nil, "BACKGROUND", nil, -8)
    frame.BodyBackground:SetPoint("TOPLEFT", frame, "TOPLEFT", 7, -18)
    frame.BodyBackground:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -2, 3)
    frame.BodyBackground:SetColorTexture(5 / 255, 4 / 255, 2 / 255, ABOUT_REPLICA_BODY_BG_ALPHA)

    frame.Content = CreateFrame("Frame", nil, frame)
    frame.Content:SetPoint("TOPLEFT", frame, "TOPLEFT", ABOUT_REPLICA_BODY_LEFT, ABOUT_REPLICA_BODY_TOP)
    frame.Content:SetSize(ABOUT_REPLICA_CONTENT_WIDTH, ABOUT_REPLICA_CONTENT_HEIGHT)
    frame.Content:SetFrameLevel(frame:GetFrameLevel() + 5)

    frame.TitleAtlas = CreateAboutReplicaSlicedAtlas(frame.Content, ABOUT_REPLICA_TITLE_ATLAS_TEXTURE, ABOUT_REPLICA_TITLE_ATLAS_WIDTH, ABOUT_REPLICA_TITLE_ATLAS_HEIGHT, {
        left = 30,
        right = 52,
        top = 4,
        bottom = 7,
        scale = 0.5,
    }, "ARTWORK", 0)
    frame.TitleAtlas:SetFrameLevel(frame.Content:GetFrameLevel() + 1)
    frame.TitleAtlas:SetPoint("TOPLEFT", frame.Content, "TOPLEFT", ABOUT_REPLICA_TITLE_BAND_INSET_X, ABOUT_REPLICA_TITLE_BAND_TOP_Y)
    frame.TitleAtlas:SetPoint("TOPRIGHT", frame.Content, "TOPRIGHT", -ABOUT_REPLICA_TITLE_BAND_INSET_X, ABOUT_REPLICA_TITLE_BAND_TOP_Y)
    frame.TitleAtlas:SetHeight(ABOUT_REPLICA_TITLE_BAND_HEIGHT)

    frame.LogoFrame = CreateFrame("Frame", nil, frame.Content)
    frame.LogoFrame:SetFrameLevel(frame.Content:GetFrameLevel() + 20)
    frame.LogoFrame:SetSize(ABOUT_REPLICA_LOGO_SIZE, ABOUT_REPLICA_LOGO_SIZE)
    frame.LogoFrame:SetPoint("CENTER", frame.TitleAtlas, "TOP", 0, -1)
    frame.Logo = frame.LogoFrame:CreateTexture(nil, "OVERLAY", nil, 7)
    frame.Logo:SetAllPoints(frame.LogoFrame)

    frame.BrandLine = CreateFrame("Frame", nil, frame.Content)
    frame.BrandLine:SetPoint("TOP", frame.TitleAtlas, "TOP", 0, ABOUT_REPLICA_BRAND_TOP)
    frame.BrandLine:SetSize(340, 42)
    frame.BrandTitle = CreateAboutReplicaText(frame.BrandLine, "GameFontNormalHuge", ABOUT_REPLICA_FONT_BRAND_TITLE, ABOUT_REPLICA_GOLD, "OUTLINE")
    frame.BrandTitle:SetPoint("CENTER", frame.BrandLine, "CENTER", 0, 0)
    frame.BrandTitle:SetSize(260, 40)
    frame.BrandTitle:SetJustifyH("CENTER")
    frame.VersionInline = CreateAboutReplicaText(frame.BrandLine, "GameFontHighlight", ABOUT_REPLICA_FONT_VERSION_INLINE, ABOUT_REPLICA_GOLD, "")
    frame.VersionInline:SetPoint("BOTTOMLEFT", frame.BrandTitle, "BOTTOMRIGHT", 4, 6)
    frame.VersionInline:SetSize(60, 18)
    frame.VersionInline:SetJustifyH("LEFT")

    frame.IntroText = CreateAboutReplicaText(frame.Content, "GameFontHighlight", ABOUT_REPLICA_FONT_DESCRIPTION_TEXT, ABOUT_REPLICA_BODY_TEXT, "")
    frame.IntroText:SetPoint("TOP", frame.BrandLine, "BOTTOM", 0, -ABOUT_REPLICA_TITLE_DESC_GAP)
    frame.IntroText:SetSize(ABOUT_REPLICA_INTRO_WIDTH, 44)
    frame.IntroText:SetJustifyH("CENTER")
    frame.IntroText:SetWordWrap(true)
    if frame.IntroText.SetSpacing then
        frame.IntroText:SetSpacing(3)
    end

    frame.CommandText = CreateAboutReplicaText(frame.Content, "GameFontHighlightLarge", ABOUT_REPLICA_FONT_COMMAND_TEXT, ABOUT_REPLICA_COMMAND_ORANGE, "")
    frame.CommandText:SetPoint("TOP", frame.TitleAtlas, "BOTTOM", 0, -ABOUT_REPLICA_COMMAND_TOP_GAP)
    frame.CommandText:SetSize(ABOUT_REPLICA_CONTENT_WIDTH, 28)
    frame.CommandText:SetJustifyH("CENTER")
    frame.CommandTitle = CreateAboutReplicaText(frame.Content, "GameFontNormalLarge", ABOUT_REPLICA_FONT_COMMAND_TITLE, ABOUT_REPLICA_GOLD, "OUTLINE")
    frame.CommandTitle:SetPoint("TOP", frame.CommandText, "BOTTOM", 0, -8)
    frame.CommandTitle:SetSize(ABOUT_REPLICA_CONTENT_WIDTH, 26)
    frame.CommandTitle:SetJustifyH("CENTER")

    frame.VersionRow = CreateAboutReplicaInfoPair(frame.Content, "TOPLEFT", frame.CommandTitle, "BOTTOMLEFT", ABOUT_REPLICA_INFO_LEFT_X, -ABOUT_REPLICA_INFO_TOP_GAP)
    frame.AuthorRow = CreateAboutReplicaInfoPair(frame.Content, "TOPLEFT", frame.VersionRow, "BOTTOMLEFT", 0, -10)
    frame.FeedbackRow = CreateAboutReplicaInfoPair(frame.Content, "TOPLEFT", frame.CommandTitle, "BOTTOMLEFT", ABOUT_REPLICA_INFO_RIGHT_X, -ABOUT_REPLICA_INFO_TOP_GAP)
    frame.HomepageRow = CreateAboutReplicaInfoPair(frame.Content, "TOPLEFT", frame.FeedbackRow, "BOTTOMLEFT", 0, -10)
    SetAboutReplicaTextColor(frame.FeedbackRow.Value, ABOUT_REPLICA_LINK_BLUE)
    SetAboutReplicaTextColor(frame.HomepageRow.Value, ABOUT_REPLICA_LINK_BLUE)

    frame.NoticeBox = CreateAboutReplicaSlicedAtlas(frame.Content, ABOUT_REPLICA_INFO_BOX_TEXTURE, ABOUT_REPLICA_INFO_BOX_ATLAS_WIDTH, ABOUT_REPLICA_INFO_BOX_ATLAS_HEIGHT, {
        left = 18,
        right = 18,
        top = 18,
        bottom = 18,
        scale = 0.3,
    }, "BACKGROUND", 0)
    frame.NoticeBox:SetPoint("BOTTOMLEFT", frame.Content, "BOTTOMLEFT", ABOUT_REPLICA_INFO_BOX_INSET_X, ABOUT_REPLICA_INFO_BOX_BOTTOM_Y)
    frame.NoticeBox:SetPoint("BOTTOMRIGHT", frame.Content, "BOTTOMRIGHT", -ABOUT_REPLICA_INFO_BOX_INSET_X, ABOUT_REPLICA_INFO_BOX_BOTTOM_Y)
    frame.NoticeBox:SetHeight(ABOUT_REPLICA_INFO_BOX_HEIGHT)

    local noticeScroll, noticeBody, noticeScrollBar = CreateAboutReplicaScrollContainer(frame.NoticeBox)
    frame.NoticeScroll = noticeScroll
    frame.NoticeBody = noticeBody
    frame.NoticeScrollBar = noticeScrollBar
    noticeScroll:ClearAllPoints()
    noticeScroll:SetPoint("TOPLEFT", frame.NoticeBox, "TOPLEFT", ABOUT_REPLICA_NOTICE_SCROLL_INSET_LEFT, -ABOUT_REPLICA_NOTICE_SCROLL_INSET_TOP)
    noticeScroll:SetPoint("BOTTOMRIGHT", frame.NoticeBox, "BOTTOMRIGHT", -ABOUT_REPLICA_NOTICE_SCROLL_INSET_RIGHT, ABOUT_REPLICA_NOTICE_SCROLL_INSET_BOTTOM)
    noticeScroll:SetFrameLevel(frame.NoticeBox:GetFrameLevel() + 2)
    noticeBody:ClearAllPoints()
    noticeBody:SetPoint("TOPLEFT", noticeScroll, "TOPLEFT", 0, 0)
    noticeBody:SetSize(ABOUT_REPLICA_NOTICE_TEXT_WIDTH, ABOUT_REPLICA_NOTICE_SCROLL_HEIGHT)
    if noticeScrollBar then
        noticeScrollBar:ClearAllPoints()
        noticeScrollBar:SetPoint("TOPLEFT", noticeScroll, "TOPRIGHT", ABOUT_REPLICA_NOTICE_SCROLLBAR_GAP, 0)
        noticeScrollBar:SetPoint("BOTTOMLEFT", noticeScroll, "BOTTOMRIGHT", ABOUT_REPLICA_NOTICE_SCROLLBAR_GAP, 0)
        noticeScrollBar:SetWidth(ABOUT_REPLICA_NOTICE_SCROLLBAR_WIDTH)
    end

    frame.NoticeEmpty = CreateAboutReplicaText(frame.NoticeBox, "GameFontDisable", ABOUT_REPLICA_FONT_NOTICE_TEXT, ABOUT_REPLICA_NOTICE_GRAY, "")
    frame.NoticeEmpty:SetPoint("CENTER", frame.NoticeBox, "CENTER", 0, -2)
    frame.NoticeEmpty:SetSize(260, 20)
    frame.NoticeEmpty:SetJustifyH("CENTER")

    frame.Footer = CreateAboutReplicaText(frame, "GameFontDisableSmall", ABOUT_REPLICA_FONT_FOOTER_TEXT, ABOUT_REPLICA_FOOTER_GRAY, "")
    frame.Footer:SetPoint("BOTTOM", frame, "BOTTOM", 0, 15)
    frame.Footer:SetSize(ABOUT_REPLICA_CONTENT_WIDTH, 16)
    frame.Footer:SetJustifyH("CENTER")

    frame:HookScript("OnHide", function(self)
        local restoreOwner = self.mkRestoreOwner
        local restoreTab = self.mkRestoreTab or "options"
        local shouldRestore = self.mkRestoreMainWindow == true
        self.mkRestoreMainWindow = nil
        self.mkRestoreOwner = nil
        self.mkRestoreTab = nil

        if shouldRestore and restoreOwner and restoreOwner.ShowMainWindow then
            restoreOwner:ShowMainWindow(restoreTab, true)
        end
    end)

    return frame
end

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

    local displayName = GetLocalizedAddonDisplayName()
    local addonVersion = GetAddonMetadata("Version", "1.0.10")
    local introText = TRANSLATE("大秘境钥石助手是一款集钥石管理、角色数据统计、集合石辅助、车队管理、传送通报与大秘分数查询于一体的大秘境综合管理插件，帮助玩家高效组队、核验阵容并快速完成战前准备。")

    if frame.TitleText then
        frame.TitleText:SetText(TRANSLATE("关于大秘境钥石助手"))
    end
    frame.Logo:SetTexture(ABOUT_REPLICA_LOGO_TEXTURE)
    frame.BrandTitle:SetText(displayName)
    frame.VersionInline:SetText(addonVersion)
    frame.IntroText:SetText(introText)
    frame.CommandText:SetText(TRANSLATE("/mk"))
    frame.CommandTitle:SetText(TRANSLATE("快捷呼出代码"))

    frame.VersionRow.Label:SetText(TRANSLATE("当前版本") .. ":")
    frame.VersionRow.Value:SetText(addonVersion)
    frame.AuthorRow.Label:SetText(TRANSLATE("作者信息") .. ":")
    frame.AuthorRow.Value:SetText(TRANSLATE("草东-白银之手-国服"))
    frame.FeedbackRow.Label:SetText(TRANSLATE("信息反馈") .. ":")
    frame.FeedbackRow.Value:SetText(TRANSLATE("me@gaicas.com"))
    frame.HomepageRow.Label:SetText(TRANSLATE("作者主页") .. ":")
    frame.HomepageRow.Value:SetText(TRANSLATE("https://www.gaicas.com"))
    frame.Footer:SetText(ABOUT_DIALOG_FOOTER_TEXT)

    RefreshAboutReplicaNoticeContent(frame)
    RefreshAboutReplicaBrandLayout(frame)
end

local function HideMainWindowForOptionsAbout(owner, dialog)
    if not (owner and dialog and owner.IsMainWindowShown and owner:IsMainWindowShown()) then
        return
    end

    dialog.mkRestoreMainWindow = true
    dialog.mkRestoreOwner = owner
    dialog.mkRestoreTab = owner.activeTab or "options"

    local frame = owner.mainWindow and (owner.mainWindow.frame or owner.mainWindow)
    if not frame then
        return
    end
    if frame.StopMovingOrSizing then
        frame:StopMovingOrSizing()
    end
    if owner.DetachMeetingStoneNativeInputControls then
        owner:DetachMeetingStoneNativeInputControls()
    end
    if owner.DetachFindGroupNativeSearchBox then
        owner:DetachFindGroupNativeSearchBox()
    end
    frame:Hide()
    if owner.RefreshMicroMenuButtonState then
        owner:RefreshMicroMenuButtonState()
    end
end

ShowOptionsAboutDialog = function(owner)
    if not owner then
        return
    end

    local dialog = GetOptionsAboutReplicaDialogFrame(owner)
    RefreshOptionsAboutReplicaDialog(dialog)
    if dialog:IsShown() then
        return
    end

    dialog:ClearAllPoints()
    dialog:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    HideMainWindowForOptionsAbout(owner, dialog)
    dialog:Show()
end

local function CreateSystemSettingsInfoBlock(parent, title, value, width, valueHeight)
    valueHeight = valueHeight or 28

    local block = CreateFrame("Frame", nil, parent)
    block:SetSize(width or 560, valueHeight + 24)

    block.Title = block:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
    block.Title:SetPoint("TOPLEFT")
    block.Title:SetText(title)
    block.Title:SetTextColor(1, 0.82, 0)

    block.Value = block:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    block.Value:SetPoint("TOPLEFT", block.Title, "BOTTOMLEFT", 0, -5)
    block.Value:SetSize(width or 560, valueHeight)
    block.Value:SetJustifyH("LEFT")
    block.Value:SetJustifyV("TOP")
    if block.Value.SetWordWrap then
        block.Value:SetWordWrap(true)
    end
    if block.Value.SetNonSpaceWrap then
        block.Value:SetNonSpaceWrap(true)
    end
    block.Value:SetText(value)

    return block
end

local function RegisterSystemSettingsCategory(owner, panel)
    if Settings and Settings.RegisterCanvasLayoutCategory and Settings.RegisterAddOnCategory then
        local ok, category = pcall(Settings.RegisterCanvasLayoutCategory, panel, GetLocalizedAddonDisplayName())
        if ok and category then
            category.ID = SETTINGS_ADDON_NAME
            local registerOk = pcall(Settings.RegisterAddOnCategory, category)
            if registerOk then
                owner.systemSettingsCategory = category
                return
            end
        end
    end

    if InterfaceOptions_AddCategory then
        pcall(InterfaceOptions_AddCategory, panel)
    end
end

local function CreateSystemSettingsPanel(owner)
    local panel = CreateFrame("Frame", "MyKeyStoneSettingsPanel")
    local displayName = GetLocalizedAddonDisplayName()
    local addonVersion = GetAddonMetadata("Version", "1.0.10")
    local addonAuthor = TRANSLATE("草东-白银之手-国服")
    local settingsInfoWidth = 560

    panel.name = displayName

    panel.Icon = panel:CreateTexture(nil, "ARTWORK")
    panel.Icon:SetPoint("TOPLEFT", 24, -24)
    panel.Icon:SetSize(46, 46)
    panel.Icon:SetTexture(SETTINGS_ADDON_LOGO_PATH)

    panel.Title = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    panel.Title:SetPoint("TOPLEFT", panel.Icon, "TOPRIGHT", 14, -2)
    panel.Title:SetText(displayName)
    panel.Title:SetTextColor(1, 0.82, 0)

    panel.TitleVersion = panel:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
    panel.TitleVersion:SetPoint("TOPLEFT", panel.Title, "BOTTOMLEFT", 0, -6)
    panel.TitleVersion:SetText(string.format(TRANSLATE("当前版本：%s"), addonVersion))
    panel.TitleVersion:SetTextColor(0.72, 0.72, 0.72)

    panel.IntroBlock = CreateSystemSettingsInfoBlock(panel, TRANSLATE("插件简介"), TRANSLATE("大秘境钥石助手是一款集钥石管理、角色数据统计、集合石辅助、车队管理、传送通报与大秘分数查询于一体的大秘境综合管理插件，帮助玩家高效组队、核验阵容并快速完成战前准备。"), settingsInfoWidth, 62)
    panel.IntroBlock:SetPoint("TOPLEFT", panel.Icon, "BOTTOMLEFT", 0, -28)

    panel.ShortcutBlock = CreateSystemSettingsInfoBlock(panel, TRANSLATE("快捷呼出代码"), TRANSLATE("/mk"), settingsInfoWidth)
    panel.ShortcutBlock:SetPoint("TOPLEFT", panel.IntroBlock, "BOTTOMLEFT", 0, -20)
    panel.ShortcutBlock.Value:SetTextColor(0.52, 0.76, 1)

    panel.VersionBlock = CreateSystemSettingsInfoBlock(panel, TRANSLATE("版本信息"), string.format(TRANSLATE("当前版本：%s"), addonVersion), settingsInfoWidth)
    panel.VersionBlock:SetPoint("TOPLEFT", panel.ShortcutBlock, "BOTTOMLEFT", 0, -18)
    panel.VersionBlock.Value:SetTextColor(0.86, 0.86, 0.86)

    panel.AuthorBlock = CreateSystemSettingsInfoBlock(panel, TRANSLATE("作者信息"), addonAuthor, settingsInfoWidth)
    panel.AuthorBlock:SetPoint("TOPLEFT", panel.VersionBlock, "BOTTOMLEFT", 0, -18)
    panel.AuthorBlock.Value:SetTextColor(0.78, 0.72, 0.62)

    panel.HomepageBlock = CreateSystemSettingsInfoBlock(panel, TRANSLATE("作者主页"), TRANSLATE("https://www.gaicas.com"), settingsInfoWidth)
    panel.HomepageBlock:SetPoint("TOPLEFT", panel.AuthorBlock, "BOTTOMLEFT", 0, -18)
    panel.HomepageBlock.Value:SetTextColor(0.52, 0.76, 1)

    panel.FeedbackBlock = CreateSystemSettingsInfoBlock(panel, TRANSLATE("信息反馈"), TRANSLATE("me@gaicas.com"), settingsInfoWidth)
    panel.FeedbackBlock:SetPoint("TOPLEFT", panel.HomepageBlock, "BOTTOMLEFT", 0, -18)
    panel.FeedbackBlock.Value:SetTextColor(0.52, 0.76, 1)

    panel.OpenButton = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    panel.OpenButton:SetSize(148, 26)
    panel.OpenButton:SetPoint("TOPLEFT", panel.FeedbackBlock, "BOTTOMLEFT", 0, -20)
    panel.OpenButton:SetText(TRANSLATE("打开配置面板"))
    if addonTable.MeetingStoneButtonArt and addonTable.MeetingStoneButtonArt.Apply then
        addonTable.MeetingStoneButtonArt.Apply(panel.OpenButton)
    end
    panel.OpenButton:SetScript("OnClick", function()
        if owner.OpenOptionsTab then
            owner:OpenOptionsTab()
        end
    end)

    return panel
end

function MK:SetupOptions()
    self.optionsFrame = nil
    self.profilesFrame = nil

    if self.systemSettingsPanel then
        return
    end

    self.systemSettingsPanel = CreateSystemSettingsPanel(self)
    RegisterSystemSettingsCategory(self, self.systemSettingsPanel)
end
