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 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_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_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 = 5,
    teleportRowCount = 3,
    completionRowCount = 1,
    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 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(0.45)
    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 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 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 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 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 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

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))
    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", 0, -OPTIONS_SECTION_GAP)
        title:SetPoint("TOPRIGHT", anchorFrame, "BOTTOMRIGHT", 0, -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", 0, -OPTIONS_TITLE_TO_CONTROLS_GAP)
    panel:SetPoint("TOPRIGHT", title, "BOTTOMRIGHT", 0, -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 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 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.findGroup = nil
    profile.autoOpenOnCompletedRun = "off"
    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 = 24
    optionsFrame.mkDecorFrameInsetTop = 4
    optionsFrame.mkDecorFrameInsetRight = 24
    optionsFrame.mkDecorFrameInsetBottom = 10
    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_ACTION_BUTTON_RIGHT_INSET, 0)
    defaultSettingsButton: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 mainWindowScaleSlider = CreateOptionsMainWindowScaleSlider(interfaceRows[5], owner)
    core.AnchorOptionsRowControl(mainWindowScaleSlider, interfaceRows[5])

    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])

    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", 0, -OPTIONS_TITLE_TO_CONTROLS_GAP)
    tacticalAnnouncementPanel:SetPoint("TOPRIGHT", tacticalTitle, "BOTTOMRIGHT", 0, -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,
        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,
        autoOpenDropdown = autoOpenDropdown,
        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.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.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 mainWindowScale = self.GetMainWindowScale and self:GetMainWindowScale() or 1
    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,
        autoOpenOnCompletedRun,
        SnapshotValue(mainWindowScale),
        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 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.1")
    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
