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

core.MeetingStoneListing = core.MeetingStoneListing or {}
local MeetingStoneListing = core.MeetingStoneListing
local MeetingStoneConstants = core.MeetingStoneConstants or {}
local MEETINGSTONE_PLAY_STYLE_OPTIONS = MeetingStoneConstants.MEETINGSTONE_PLAY_STYLE_OPTIONS
local MEETINGSTONE_FALLBACK_CHALLENGE_MAP_IDS = MeetingStoneConstants.MEETINGSTONE_FALLBACK_CHALLENGE_MAP_IDS
local MEETINGSTONE_FALLBACK_DUNGEON_GROUP_IDS = MeetingStoneConstants.MEETINGSTONE_FALLBACK_DUNGEON_GROUP_IDS
local MEETINGSTONE_FALLBACK_DUNGEON_ACTIVITY_IDS = MeetingStoneConstants.MEETINGSTONE_FALLBACK_DUNGEON_ACTIVITY_IDS
local MEETINGSTONE_CREATION_TITLE_MAX_LETTERS = MeetingStoneConstants.MEETINGSTONE_CREATION_TITLE_MAX_LETTERS
local MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS = MeetingStoneConstants.MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS
local MEETINGSTONE_PUBLISH_CONFIRM_TIMEOUT = MeetingStoneConstants.MEETINGSTONE_PUBLISH_CONFIRM_TIMEOUT
local MEETINGSTONE_MISSING_ADDON_MESSAGE_KEY = MeetingStoneConstants.MEETINGSTONE_MISSING_ADDON_MESSAGE_KEY
local MeetingStoneBridge = core.MeetingStoneBridge or {}
local MEETINGSTONE_LOCAL_REMOVAL_PENDING_SECONDS = 8

local GetMeetingStoneNativeEntryCreationFrame = MeetingStoneBridge.GetNativeEntryCreationFrame
local ReadMeetingStoneTextControl = MeetingStoneBridge.ReadTextControl
local SetMeetingStoneTextControl = MeetingStoneBridge.SetTextControl
local IsMeetingStoneControlParentedTo = MeetingStoneBridge.IsControlParentedTo
local CloseMeetingStoneAssistantForNativePanel = MeetingStoneBridge.CloseAssistantForNativePanel
local HideMeetingStoneNativeGroupFinderFrames = MeetingStoneBridge.HideNativeGroupFinderFrames

local CanManageMeetingStoneListing
local HasMeetingStoneActiveListing
local IsMeetingStoneActiveSeasonDungeonListing

local function IsMeetingStoneNativeLFGAvailable()
    return C_LFGList
        and C_LFGList.GetActivityInfoTable
        and C_LFGList.CreateListing
        and C_LFGList.UpdateListing
        and C_LFGList.HasActiveEntryInfo
        and C_LFGList.GetApplicants
        and true
        or false
end

function MK:IsMeetingStoneManagementAvailable()
    return IsMeetingStoneNativeLFGAvailable()
end

local function GetMeetingStonePlayStyleValue(option)
    if not option then
        return 1
    end

    local generalPlaystyle = Enum and Enum.LFGEntryGeneralPlaystyle
    if generalPlaystyle and option.enumKey and generalPlaystyle[option.enumKey] then
        return generalPlaystyle[option.enumKey]
    end

    return option.fallback or 1
end

local function GetMeetingStoneDefaultPlayStyle()
    return GetMeetingStonePlayStyleValue(MEETINGSTONE_PLAY_STYLE_OPTIONS[1])
end

local function GetMeetingStoneDefaultEntryPlaystyle()
    local entryPlaystyle = Enum and Enum.LFGEntryPlaystyle
    if entryPlaystyle and entryPlaystyle.None ~= nil then
        return entryPlaystyle.None
    end
    return 0
end

local function IsMeetingStonePlayStyleValueEqual(left, right)
    return left == right or (tonumber(left) ~= nil and tonumber(left) == tonumber(right))
end

local function GetMeetingStonePlayStyleText(value)
    local resolvedValue = value or GetMeetingStoneDefaultPlayStyle()
    for _, option in ipairs(MEETINGSTONE_PLAY_STYLE_OPTIONS) do
        if IsMeetingStonePlayStyleValueEqual(GetMeetingStonePlayStyleValue(option), resolvedValue) then
            return TRANSLATE(option.textKey)
        end
    end

    return TRANSLATE(MEETINGSTONE_PLAY_STYLE_OPTIONS[1].textKey)
end

local function TrimMeetingStoneText(text)
    text = tostring(text or "")
    if strtrim then
        return strtrim(text)
    end
    return string.match(text, "^%s*(.-)%s*$") or text
end

local function LimitMeetingStoneText(text, maxLetters)
    text = TrimMeetingStoneText(text)
    if maxLetters and strlen and strlen(text) > maxLetters then
        return strsub(text, 1, maxLetters)
    end
    return text
end

local function IsMeetingStoneTextFilled(text)
    if type(text) ~= "string" then
        return false
    end
    return TrimMeetingStoneText(text) ~= ""
end

local function IsMeetingStoneNativePlaceholderText(text)
    text = TrimMeetingStoneText(text)
    if text == "" then
        return true
    end
    return text:find("你的队伍在列表中", 1, true)
        or text:find("Your group is listed", 1, true)
        or text:find("关于你的队伍", 1, true)
        or text:find("Description of your group", 1, true)
        or text:find("请输入", 1, true)
        or text:find("請輸入", 1, true)
        or text:find("Enter a group name", 1, true)
        or text:find("Enter additional group requirements", 1, true)
        or text:find("请填写活动标题", 1, true)
        or text:find("Activity title is required", 1, true)
        or false
end

local function IsMeetingStoneUsableText(text)
    return IsMeetingStoneTextFilled(text) and not IsMeetingStoneNativePlaceholderText(text)
end

local function IsMeetingStoneApplicantOnlyRefreshSource(refreshSource)
    return refreshSource == "MeetingStoneApplicantListUpdated"
        or refreshSource == "MeetingStoneApplicantUpdated"
        or refreshSource == "MeetingStoneApplicantRefresh"
        or refreshSource == "MeetingStoneApplicantHeaderSort"
        or refreshSource == "MeetingStoneApplicantInvite"
end

local function IsMeetingStoneTextDirtySuppressed(owner)
    return owner and tonumber(owner.mkMeetingStoneTextDirtySuppressed or 0) > 0
end

local function PushMeetingStoneTextDirtySuppression(owner)
    if not owner then
        return
    end
    owner.mkMeetingStoneTextDirtySuppressed = (owner.mkMeetingStoneTextDirtySuppressed or 0) + 1
end

local function PopMeetingStoneTextDirtySuppression(owner)
    if not owner then
        return
    end
    owner.mkMeetingStoneTextDirtySuppressed = math.max((owner.mkMeetingStoneTextDirtySuppressed or 1) - 1, 0)
end

local function MarkMeetingStoneDraftTextDirty(owner)
    if not owner or owner.mkBuildingMeetingStoneTab or IsMeetingStoneTextDirtySuppressed(owner) then
        return
    end
    if not HasMeetingStoneActiveListing() then
        return
    end

    owner.meetingStoneDraftTextDirty = true
    owner.meetingStoneDraftTextDirtyAt = GetTime and GetTime() or time and time() or nil
end

local function GetMeetingStoneDraftNumber(draft, key)
    local value = tonumber(draft and draft[key])
    if not value or value < 0 then
        return 0
    end
    return math.floor(value)
end

local function IsMeetingStoneItemLevelAboveSelfFailure(reason)
    local text = tostring(reason or "")
    if text == "" then
        return false
    end

    local lowerText = string.lower(text)
    local hasItemLevelText = text:find("物品等级", 1, true)
        or text:find("物品等級", 1, true)
        or text:find("装等", 1, true)
        or text:find("裝等", 1, true)
        or lowerText:find("item level", 1, true)
    local hasAboveText = text:find("高于", 1, true)
        or text:find("高於", 1, true)
        or lowerText:find("higher", 1, true)
        or lowerText:find("greater", 1, true)

    return hasItemLevelText and hasAboveText
end

local function NormalizeMeetingStonePublishFailureReason(reason)
    if IsMeetingStoneItemLevelAboveSelfFailure(reason) then
        return TRANSLATE("创建失败：最低装等不能高于自己的装备等级")
    end

    return tostring(reason or TRANSLATE("活动创建失败，请重试。"))
end

local function FormatMeetingStonePublishFailureMessage(reason)
    local normalizedReason = NormalizeMeetingStonePublishFailureReason(reason)
    if normalizedReason == TRANSLATE("创建失败：最低装等不能高于自己的装备等级") then
        return normalizedReason
    end

    return string.format(TRANSLATE("集合石发布失败：%s"), normalizedReason)
end

local function GetMeetingStoneLeaderItemLevelText()
    if not GetAverageItemLevel then
        return ""
    end

    local ok, averageItemLevel, equippedItemLevel = pcall(GetAverageItemLevel)
    if not ok then
        return ""
    end

    local itemLevel = tonumber(equippedItemLevel) or tonumber(averageItemLevel)
    if not itemLevel or itemLevel <= 0 then
        return ""
    end

    return tostring(math.max(0, math.floor(itemLevel + 0.5) - 5))
end

local function CopyMeetingStoneListingDraftForStorage(draft)
    if type(draft) ~= "table" then
        return nil
    end

    return {
        mapID = tonumber(draft.mapID),
        dungeonName = draft.dungeonName,
        activityCode = draft.activityCode,
        activityID = tonumber(draft.activityID),
        groupID = tonumber(draft.groupID),
        categoryID = tonumber(draft.categoryID),
        baseFilter = tonumber(draft.baseFilter),
        customID = tonumber(draft.customID) or 0,
        activityName = draft.activityName,
        title = tostring(draft.title or ""),
        summary = tostring(draft.summary or ""),
        itemLevel = tostring(draft.itemLevel or "0"),
        score = tostring(draft.score or "0"),
        privateGroup = draft.privateGroup == true,
        crossFaction = draft.crossFaction ~= false,
        playStyle = draft.playStyle,
    }
end

local function GetMeetingStoneActiveEntryInfo()
    if not (C_LFGList and C_LFGList.GetActiveEntryInfo) then
        return nil
    end

    local ok, activeInfo = pcall(C_LFGList.GetActiveEntryInfo)
    if ok and type(activeInfo) == "table" then
        return activeInfo
    end
    return nil
end

local ApplyMeetingStoneNativeEntryCreationContext

local function GetMeetingStoneActiveEntryActivityID(activeInfo)
    activeInfo = activeInfo or GetMeetingStoneActiveEntryInfo()
    if not activeInfo then
        return nil
    end
    if activeInfo.activityIDs and activeInfo.activityIDs[1] then
        return tonumber(activeInfo.activityIDs[1])
    end
    return tonumber(activeInfo.activityID or activeInfo.ActivityID)
end

local function CopyMeetingStoneActiveEntryInfoToNativeCreationFields()
    local entryCreation = GetMeetingStoneNativeEntryCreationFrame()
    if not entryCreation then
        return nil, false
    end

    local copied = false
    if _G.LFGListEntryCreation_SetEditMode then
        local ok = pcall(_G.LFGListEntryCreation_SetEditMode, entryCreation, true)
        copied = ok == true
    end
    if not copied and C_LFGList and C_LFGList.CopyActiveEntryInfoToCreationFields then
        local ok = pcall(C_LFGList.CopyActiveEntryInfoToCreationFields)
        copied = ok == true
    end
    ApplyMeetingStoneNativeEntryCreationContext(entryCreation)
    return entryCreation, copied
end

local function GetMeetingStoneActivityInfo(activityID)
    if not activityID or not C_LFGList or not C_LFGList.GetActivityInfoTable then
        return nil
    end

    local ok, activityInfo = pcall(C_LFGList.GetActivityInfoTable, activityID)
    if ok then
        return activityInfo
    end
    return nil
end

local function GetMeetingStoneCategoryInfo(categoryID)
    if not categoryID or not C_LFGList or not C_LFGList.GetLfgCategoryInfo then
        return nil
    end

    local ok, categoryInfo = pcall(C_LFGList.GetLfgCategoryInfo, categoryID)
    if ok then
        return categoryInfo
    end
    return nil
end

local function GetMeetingStoneCrossFactionAllowed(draft)
    local activityInfo = GetMeetingStoneActivityInfo(draft and draft.activityID)
    local categoryInfo = GetMeetingStoneCategoryInfo(activityInfo and activityInfo.categoryID or draft and draft.categoryID)
    if not (categoryInfo and categoryInfo.allowCrossFaction) then
        return false, activityInfo, categoryInfo
    end
    if not (activityInfo and activityInfo.allowCrossFaction) then
        return false, activityInfo, categoryInfo
    end
    return draft and draft.crossFaction ~= false, activityInfo, categoryInfo
end

local function BuildMeetingStoneCreateArguments(activity, draft, activeEntryInfo)
    local createData = {}
    if activity and activity.GetCreateArguments then
        local ok, args = pcall(activity.GetCreateArguments, activity, false)
        if ok and type(args) == "table" then
            createData = args
        end
    end

    local activityID = tonumber(draft.activityID) or draft.activityID
    createData.activityIDs = { activityID }
    createData.questID = activeEntryInfo and activeEntryInfo.questID or createData.questID
    if createData.questID == nil then
        createData.questID = 0
    end
    createData.isAutoAccept = false
    createData.isCrossFactionListing = GetMeetingStoneCrossFactionAllowed(draft)
    createData.isPrivateGroup = draft.privateGroup == true
    createData.newPlayerFriendly = createData.newPlayerFriendly == true
    createData.playstyle = (activeEntryInfo and activeEntryInfo.playstyle) or GetMeetingStoneDefaultEntryPlaystyle()
    createData.generalPlaystyle = draft.playStyle
        or (activeEntryInfo and activeEntryInfo.generalPlaystyle)
        or createData.generalPlaystyle
        or GetMeetingStoneDefaultPlayStyle()
    createData.requiredDungeonScore = GetMeetingStoneDraftNumber(draft, "score")
    createData.requiredItemLevel = GetMeetingStoneDraftNumber(draft, "itemLevel")
    createData.requiredPvpRating = tonumber(createData.requiredPvpRating) or 0
    createData.name = LimitMeetingStoneText(draft and draft.title, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
    createData.comment = LimitMeetingStoneText(draft and draft.summary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS)
    createData.voiceChat = activeEntryInfo and activeEntryInfo.voiceChat or createData.voiceChat or ""

    return createData
end

local function FormatMeetingStoneDebugValue(value, emptyText)
    if value == nil or value == "" then
        return emptyText or "-"
    end
    return tostring(value)
end

local function FormatMeetingStoneDebugBool(value)
    if value == true then
        return "是"
    elseif value == false then
        return "否"
    end
    return "-"
end

local function GetMeetingStoneCreateDebugText(createData, draft)
    local activityID = createData and createData.activityIDs and createData.activityIDs[1] or draft and draft.activityID
    local activityInfo = GetMeetingStoneActivityInfo(activityID)
    local categoryID = activityInfo and activityInfo.categoryID
    local authenticated = nil
    local keystoneForActivity = nil
    local validForEndgameEdits = nil

    if C_LFGList and C_LFGList.IsPlayerAuthenticatedForLFG and categoryID then
        local ok, value = pcall(C_LFGList.IsPlayerAuthenticatedForLFG, categoryID)
        if ok then
            authenticated = value == true
        end
    end

    if C_LFGList and C_LFGList.GetKeystoneForActivity and activityID then
        local ok, value = pcall(C_LFGList.GetKeystoneForActivity, activityID)
        if ok then
            keystoneForActivity = value
        end
    end

    if C_LFGList and C_LFGList.IsPlayerValidForEndgameFieldEdits then
        local ok, value = pcall(C_LFGList.IsPlayerValidForEndgameFieldEdits)
        if ok then
            validForEndgameEdits = value == true
        end
    end

    return string.format(
        "活动：%s（ID %s，分组 %s）；标题：%s；原生名称：%s；描述长度：%s；钥石识别：%s；账号认证：%s；文本可编辑：%s；跨阵营：%s；风格：%s；装等：%s；评分：%s；questID：%s",
        FormatMeetingStoneDebugValue(activityInfo and (activityInfo.fullName or activityInfo.shortName), "未知活动"),
        FormatMeetingStoneDebugValue(activityID),
        FormatMeetingStoneDebugValue(activityInfo and activityInfo.groupFinderActivityGroupID or draft and draft.groupID),
        FormatMeetingStoneDebugValue(draft and draft.title),
        FormatMeetingStoneDebugValue(createData and createData.name),
        tostring(#tostring(createData and createData.comment or "")),
        FormatMeetingStoneDebugValue(keystoneForActivity, "未识别"),
        FormatMeetingStoneDebugBool(authenticated),
        FormatMeetingStoneDebugBool(validForEndgameEdits),
        FormatMeetingStoneDebugBool(createData and createData.isCrossFactionListing),
        FormatMeetingStoneDebugValue(createData and createData.generalPlaystyle),
        FormatMeetingStoneDebugValue(createData and createData.requiredItemLevel),
        FormatMeetingStoneDebugValue(createData and createData.requiredDungeonScore),
        FormatMeetingStoneDebugValue(createData and createData.questID)
    )
end

local function FormatMeetingStoneEventArgs(...)
    local count = select("#", ...)
    if count == 0 then
        return "无"
    end

    local parts = {}
    for index = 1, count do
        parts[index] = tostring(select(index, ...))
    end
    return table.concat(parts, ", ")
end

local function GetMeetingStoneNativeCreationFailureDebugText()
    local entryCreation = GetMeetingStoneNativeEntryCreationFrame()
    if not entryCreation then
        return "原生创建面板：未加载"
    end

    local title = ReadMeetingStoneTextControl(entryCreation.Name)
    local summaryBox = entryCreation.Description and entryCreation.Description.EditBox
    local summary = ReadMeetingStoneTextControl(summaryBox)
    local listButton = entryCreation.ListGroupButton

    return string.format(
        "原生活动：%s；原生分组：%s；原生分类：%s；编辑模式：%s；原生风格：%s；原生标题：%s；描述长度：%s；按钮错误：%s；装等警告：%s；评分警告：%s",
        FormatMeetingStoneDebugValue(entryCreation.selectedActivity),
        FormatMeetingStoneDebugValue(entryCreation.selectedGroup),
        FormatMeetingStoneDebugValue(entryCreation.selectedCategory),
        FormatMeetingStoneDebugValue(entryCreation.editMode),
        FormatMeetingStoneDebugValue(entryCreation.generalPlaystyle),
        FormatMeetingStoneDebugValue(title),
        tostring(#summary),
        FormatMeetingStoneDebugValue(listButton and listButton.errorText, "无"),
        FormatMeetingStoneDebugValue(entryCreation.ItemLevel and entryCreation.ItemLevel.warningText, "无"),
        FormatMeetingStoneDebugValue(entryCreation.MythicPlusRating and entryCreation.MythicPlusRating.warningText, "无")
    )
end

local function RefreshMeetingStoneNativeCreationValidation()
    local entryCreation = GetMeetingStoneNativeEntryCreationFrame()
    if not entryCreation then
        return false, TRANSLATE("暴雪集合石创建面板未加载。")
    end

    if _G.LFGListEntryCreation_UpdateAuthenticatedState then
        pcall(_G.LFGListEntryCreation_UpdateAuthenticatedState, entryCreation)
    end
    if _G.LFGListEntryCreation_UpdateValidState then
        pcall(_G.LFGListEntryCreation_UpdateValidState, entryCreation)
    end

    local listButton = entryCreation.ListGroupButton
    local errorText = listButton and listButton.errorText
    if errorText and errorText ~= "" then
        return false, tostring(errorText)
    end
    return true, "通过"
end

ApplyMeetingStoneNativeEntryCreationContext = function(entryCreation)
    if not entryCreation then
        return
    end

    for _, frame in ipairs({
        entryCreation.Name,
        entryCreation.Description,
        entryCreation.Description and entryCreation.Description.EditBox,
        entryCreation.VoiceChat,
        entryCreation.VoiceChat and entryCreation.VoiceChat.EditBox,
    }) do
        if frame then
            frame.selectedActivity = entryCreation.selectedActivity
            frame.selectedCategory = entryCreation.selectedCategory
        end
    end
end

local function SetMeetingStoneNativeCreationEditMode(entryCreation, enabled)
    if not entryCreation then
        return
    end

    local editMode = enabled == true
    local currentEditMode = entryCreation.editMode == true
    if currentEditMode == editMode then
        entryCreation.editMode = editMode
        return
    end

    if _G.LFGListEntryCreation_SetEditMode then
        pcall(_G.LFGListEntryCreation_SetEditMode, entryCreation, editMode)
    end
    entryCreation.editMode = editMode
end

local function SetMeetingStoneNativeRequirementValue(requirementFrame, value)
    if not (requirementFrame and requirementFrame.EditBox) then
        return
    end

    local numberValue = tonumber(value) or 0
    local text = numberValue > 0 and tostring(math.floor(numberValue)) or ""
    local editBox = requirementFrame.EditBox
    if editBox.SetEnabled then
        pcall(editBox.SetEnabled, editBox, true)
    end
    SetMeetingStoneTextControl(editBox, text)
    if requirementFrame.CheckButton then
        pcall(requirementFrame.CheckButton.SetChecked, requirementFrame.CheckButton, text ~= "")
    end
    if _G.LFGListRequirement_Validate then
        pcall(_G.LFGListRequirement_Validate, requirementFrame, text)
    end
    if _G.InputBoxInstructions_OnTextChanged then
        pcall(_G.InputBoxInstructions_OnTextChanged, editBox)
    end
end

local SetMeetingStoneNativePublishText

local function RestoreMeetingStoneNativeTextBeforeClick(entryCreation, draft)
    if not (entryCreation and type(draft) == "table") then
        return
    end

    local title = LimitMeetingStoneText(draft.title, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
    if IsMeetingStoneUsableText(title) and entryCreation.Name then
        SetMeetingStoneNativePublishText(entryCreation.Name, title)
    end

    local summaryBox = entryCreation.Description and entryCreation.Description.EditBox
    local summary = LimitMeetingStoneText(draft.summary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS)
    if IsMeetingStoneUsableText(summary) and summaryBox then
        SetMeetingStoneNativePublishText(summaryBox, summary)
    end

    if _G.LFGListEntryCreation_UpdateValidState then
        pcall(_G.LFGListEntryCreation_UpdateValidState, entryCreation)
    end
end

local function ApplyMeetingStoneNativeCreationOptions(entryCreation, draft, createData)
    if not (entryCreation and draft) then
        return
    end

    ApplyMeetingStoneNativeEntryCreationContext(entryCreation)
    SetMeetingStoneNativeRequirementValue(entryCreation.ItemLevel, createData and createData.requiredItemLevel or draft.itemLevel)
    SetMeetingStoneNativeRequirementValue(entryCreation.MythicPlusRating, createData and createData.requiredDungeonScore or draft.score)

    if entryCreation.PvpItemLevel then
        SetMeetingStoneNativeRequirementValue(entryCreation.PvpItemLevel, 0)
    end
    if entryCreation.PVPRating then
        SetMeetingStoneNativeRequirementValue(entryCreation.PVPRating, 0)
    end

    if entryCreation.PrivateGroup and entryCreation.PrivateGroup.CheckButton then
        pcall(entryCreation.PrivateGroup.CheckButton.SetChecked, entryCreation.PrivateGroup.CheckButton, draft.privateGroup == true)
    end
    if entryCreation.CrossFactionGroup and entryCreation.CrossFactionGroup.CheckButton then
        local crossFactionAllowed = GetMeetingStoneCrossFactionAllowed(draft)
        pcall(entryCreation.CrossFactionGroup.CheckButton.SetChecked, entryCreation.CrossFactionGroup.CheckButton, crossFactionAllowed ~= true)
    end
    if entryCreation.AutoAcceptButton and entryCreation.AutoAcceptButton.SetChecked then
        pcall(entryCreation.AutoAcceptButton.SetChecked, entryCreation.AutoAcceptButton, false)
    end

    local generalPlaystyle = createData and createData.generalPlaystyle or draft.playStyle or GetMeetingStoneDefaultPlayStyle()
    entryCreation.generalPlaystyle = generalPlaystyle
end

local function BuildMeetingStoneAutoAcceptDisabledData(activeInfo)
    if type(activeInfo) ~= "table" or type(activeInfo.activityIDs) ~= "table" then
        return nil
    end
    local isCrossFaction = activeInfo.isCrossFactionListing
    if isCrossFaction == nil then
        isCrossFaction = activeInfo.isCrossFaction == true
    end

    return {
        activityIDs = activeInfo.activityIDs,
        questID = activeInfo.questID or 0,
        isAutoAccept = false,
        isCrossFactionListing = isCrossFaction == true,
        isPrivateGroup = activeInfo.privateGroup == true or activeInfo.isPrivateGroup == true,
        playstyle = activeInfo.playstyle or GetMeetingStoneDefaultEntryPlaystyle(),
        generalPlaystyle = activeInfo.generalPlaystyle or GetMeetingStoneDefaultPlayStyle(),
        requiredDungeonScore = tonumber(activeInfo.requiredDungeonScore) or 0,
        requiredItemLevel = tonumber(activeInfo.requiredItemLevel) or 0,
        requiredPvpRating = tonumber(activeInfo.requiredPvpRating) or 0,
        voiceChat = activeInfo.voiceChat or "",
    }
end

local function DisableMeetingStoneManagedAutoAccept(owner, activeInfo)
    if not (owner and activeInfo and activeInfo.autoAccept == true) then
        return false
    end
    if owner.meetingStoneDisablingAutoAccept then
        return false
    end
    if not (C_LFGList and C_LFGList.UpdateListing and CanManageMeetingStoneListing()) then
        return false
    end
    if not (owner.IsMeetingStoneActiveListingManagedByAssistant and owner:IsMeetingStoneActiveListingManagedByAssistant()) then
        return false
    end

    local createData = BuildMeetingStoneAutoAcceptDisabledData(activeInfo)
    if not createData then
        return false
    end

    owner.meetingStoneDisablingAutoAccept = true
    local ok, reason = pcall(C_LFGList.UpdateListing, createData)
    owner.meetingStoneDisablingAutoAccept = nil
    if owner.DebugLog then
        owner:DebugLog(
            "集合石自动邀请保护：%s",
            ok and "已关闭系统 autoAccept" or tostring(reason or "关闭失败")
        )
    end
    return ok == true
end

local function ClickMeetingStoneNativeCreateButton(activeEntryInfo, draft)
    local entryCreation = GetMeetingStoneNativeEntryCreationFrame()
    if not entryCreation then
        return false, TRANSLATE("暴雪集合石创建面板未加载。")
    end

    SetMeetingStoneNativeCreationEditMode(entryCreation, activeEntryInfo ~= nil)
    RestoreMeetingStoneNativeTextBeforeClick(entryCreation, draft)

    local listButton = entryCreation.ListGroupButton
    if listButton and listButton.IsEnabled then
        local okEnabled, enabled = pcall(listButton.IsEnabled, listButton)
        if okEnabled and not enabled then
            return false, tostring(listButton.errorText or TRANSLATE("原生创建按钮不可用。"))
        end
    end

    if listButton and listButton.Click then
        local ok, reason = pcall(listButton.Click, listButton)
        if ok then
            return true, "原生创建按钮"
        end
        return false, tostring(reason or TRANSLATE("原生创建按钮调用失败。"))
    end

    if _G.LFGListEntryCreation_ListGroup then
        local ok, reason = pcall(_G.LFGListEntryCreation_ListGroup, entryCreation)
        if ok then
            return true, "原生创建函数"
        end
        return false, tostring(reason or TRANSLATE("原生创建函数调用失败。"))
    end

    return false, TRANSLATE("原生创建按钮不可用。")
end

local function CaptureMeetingStoneDraftText(draft)
    if type(draft) ~= "table" then
        return nil
    end

    return {
        title = LimitMeetingStoneText(draft.title, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS),
        summary = LimitMeetingStoneText(draft.summary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS),
    }
end

local function RestoreMeetingStoneDraftText(draft, entryCreation, textSnapshot)
    if type(draft) ~= "table" or type(textSnapshot) ~= "table" then
        return
    end

    if IsMeetingStoneUsableText(textSnapshot.title) then
        draft.title = textSnapshot.title
        if entryCreation and entryCreation.Name then
            SetMeetingStoneTextControl(entryCreation.Name, textSnapshot.title)
        end
    end

    if IsMeetingStoneUsableText(textSnapshot.summary) then
        draft.summary = textSnapshot.summary
        local summaryBox = entryCreation and entryCreation.Description and entryCreation.Description.EditBox
        if summaryBox then
            SetMeetingStoneTextControl(summaryBox, textSnapshot.summary)
        end
    end
end

local function SyncMeetingStoneNativeEntryCreationState(draft, createData, preserveUserText, editMode)
    local entryCreation = GetMeetingStoneNativeEntryCreationFrame()
    if not entryCreation or not _G.LFGListEntryCreation_Select then
        return false, TRANSLATE("暴雪集合石创建面板不可用。")
    end
    local textSnapshot = CaptureMeetingStoneDraftText(draft)
    SetMeetingStoneNativeCreationEditMode(entryCreation, editMode == true)

    local baseFilter = draft and draft.baseFilter
    if not baseFilter and Enum and Enum.LFGListFilter then
        baseFilter = Enum.LFGListFilter.PvE
    end
    baseFilter = baseFilter or 0

    if _G.LFGListEntryCreation_SetBaseFilters then
        pcall(_G.LFGListEntryCreation_SetBaseFilters, entryCreation, baseFilter)
    else
        entryCreation.baseFilters = baseFilter
    end

    local activityInfo
    if draft and draft.activityID and C_LFGList and C_LFGList.GetActivityInfoTable then
        local ok, info = pcall(C_LFGList.GetActivityInfoTable, draft.activityID)
        activityInfo = ok and info or nil
    end
    local groupID = activityInfo and activityInfo.groupFinderActivityGroupID or draft and draft.groupID
    if not (draft and draft.activityID and groupID) then
        RestoreMeetingStoneDraftText(draft, entryCreation, textSnapshot)
        return false, TRANSLATE("集合石活动信息不可用。")
    end
    local playstyle = createData and createData.playstyle
    local generalPlaystyle = createData and createData.generalPlaystyle or draft and draft.playStyle or GetMeetingStoneDefaultPlayStyle()
    entryCreation.generalPlaystyle = generalPlaystyle

    local selectionMatches = preserveUserText
        and entryCreation.selectedCategory == (draft and draft.categoryID or 2)
        and entryCreation.selectedGroup == groupID
        and entryCreation.selectedActivity == (draft and draft.activityID)

    local categoryID = draft and draft.categoryID or 2
    if not selectionMatches then
        local ok, reason = pcall(_G.LFGListEntryCreation_Select, entryCreation, 0, categoryID, groupID, draft and draft.activityID)
        if not ok then
            RestoreMeetingStoneDraftText(draft, entryCreation, textSnapshot)
            return false, tostring(reason)
        end
    end

    entryCreation.selectedPlaystyle = playstyle
    entryCreation.generalPlaystyle = generalPlaystyle
    ApplyMeetingStoneNativeEntryCreationContext(entryCreation)
    local function readNativeTitle()
        if not (entryCreation.Name and entryCreation.Name.GetText) then
            return ""
        end
        local ok, text = pcall(entryCreation.Name.GetText, entryCreation.Name)
        if ok then
            return text or ""
        end
        return ""
    end

    local nativeTitle = readNativeTitle()
    local preserveNativeTitle = preserveUserText == true and IsMeetingStoneUsableText(nativeTitle)
    local suppressNativeTitleAutofill = draft and draft.suppressNativeTitleAutofill == true
    local generalPlaystyleOK = "missing"
    if preserveNativeTitle then
        generalPlaystyleOK = "preserved"
    elseif _G.LFGListEntryCreation_OnGeneralPlayStyleSelectedInternal then
        local ok = pcall(_G.LFGListEntryCreation_OnGeneralPlayStyleSelectedInternal, entryCreation, generalPlaystyle)
        generalPlaystyleOK = tostring(ok)
    end
    local playstyleOK = "skipped"
    if preserveNativeTitle then
        playstyleOK = "preserved"
    elseif _G.LFGListEntryCreation_OnPlayStyleSelectedInternal and generalPlaystyle ~= nil then
        local ok = pcall(_G.LFGListEntryCreation_OnPlayStyleSelectedInternal, entryCreation, generalPlaystyle)
        playstyleOK = tostring(ok)
    end
    local authUpdateOK = "missing"
    if preserveNativeTitle then
        authUpdateOK = "preserved"
    elseif _G.LFGListEntryCreation_UpdateAuthenticatedState then
        local ok = pcall(_G.LFGListEntryCreation_UpdateAuthenticatedState, entryCreation)
        authUpdateOK = tostring(ok)
    end
    local validUpdateOK = "missing"
    if preserveNativeTitle then
        validUpdateOK = "preserved"
    elseif _G.LFGListEntryCreation_UpdateValidState then
        local ok = pcall(_G.LFGListEntryCreation_UpdateValidState, entryCreation)
        validUpdateOK = tostring(ok)
    end
    local titleFromInfoOK = "missing"
    if preserveNativeTitle then
        titleFromInfoOK = "preserved"
    elseif not suppressNativeTitleAutofill and _G.LFGListEntryCreation_SetTitleFromActivityInfo then
        local ok = pcall(_G.LFGListEntryCreation_SetTitleFromActivityInfo, entryCreation)
        titleFromInfoOK = tostring(ok)
    end

    local setTitleOK = preserveNativeTitle and "preserved" or false
    local setTitlePlaystyle = playstyle
    nativeTitle = readNativeTitle()
    if not preserveNativeTitle and not suppressNativeTitleAutofill and C_LFGList and C_LFGList.SetEntryTitle and draft and draft.activityID and groupID then
        setTitleOK = pcall(C_LFGList.SetEntryTitle, draft.activityID, groupID, playstyle, generalPlaystyle)
        nativeTitle = readNativeTitle()
        local defaultPlaystyle = Enum and Enum.LFGEntryPlaystyle and Enum.LFGEntryPlaystyle.None
        if not IsMeetingStoneUsableText(nativeTitle) and defaultPlaystyle ~= nil and playstyle ~= defaultPlaystyle then
            setTitlePlaystyle = defaultPlaystyle
            setTitleOK = pcall(C_LFGList.SetEntryTitle, draft.activityID, groupID, defaultPlaystyle, generalPlaystyle)
            nativeTitle = readNativeTitle()
        end
    end
    if not preserveNativeTitle and _G.LFGListEntryCreation_UpdateValidState then
        local ok = pcall(_G.LFGListEntryCreation_UpdateValidState, entryCreation)
        validUpdateOK = tostring(ok)
    end
    nativeTitle = readNativeTitle()
    local restoredSnapshotText = false
    if textSnapshot and IsMeetingStoneUsableText(textSnapshot.title) then
        RestoreMeetingStoneDraftText(draft, entryCreation, textSnapshot)
        restoredSnapshotText = true
    elseif draft and not suppressNativeTitleAutofill and IsMeetingStoneUsableText(nativeTitle) then
        draft.title = LimitMeetingStoneText(nativeTitle, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
    end
    if not restoredSnapshotText and textSnapshot and IsMeetingStoneUsableText(textSnapshot.summary) then
        RestoreMeetingStoneDraftText(draft, entryCreation, { summary = textSnapshot.summary })
    end

    local nameShown = "n/a"
    if entryCreation.Name and entryCreation.Name.IsShown then
        local ok, value = pcall(entryCreation.Name.IsShown, entryCreation.Name)
        if ok then
            nameShown = tostring(value)
        end
    end
    local nameEnabled = "n/a"
    if entryCreation.Name and entryCreation.Name.IsEnabled then
        local ok, value = pcall(entryCreation.Name.IsEnabled, entryCreation.Name)
        if ok then
            nameEnabled = tostring(value)
        end
    end

    return true, string.format(
        "原生活动：%s；原生分组：%s；风格：%s",
        FormatMeetingStoneDebugValue(entryCreation.selectedActivity),
        FormatMeetingStoneDebugValue(entryCreation.selectedGroup),
        FormatMeetingStoneDebugValue(generalPlaystyle)
    )
end

local function PrepareMeetingStoneNativeCreationFields(draft, activeEntryInfo, widgets)
    local title = LimitMeetingStoneText(draft and draft.title, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
    local summary = LimitMeetingStoneText(draft and draft.summary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS)
    if not C_LFGList or not C_LFGList.GetActivityInfoTable then
        return false, TRANSLATE("集合石标题接口不可用。")
    end

    local activityInfo
    if draft and draft.activityID then
        local ok, info = pcall(C_LFGList.GetActivityInfoTable, draft.activityID)
        activityInfo = ok and info or nil
    end
    local groupID = activityInfo and activityInfo.groupFinderActivityGroupID or draft and draft.groupID
    if not draft or not draft.activityID or not groupID then
        return false, TRANSLATE("集合石活动信息不可用。")
    end

    local entryCreation = GetMeetingStoneNativeEntryCreationFrame()
    local nativeTitle = entryCreation and ReadMeetingStoneTextControl(entryCreation.Name) or ""
    local summaryBox = entryCreation and entryCreation.Description and entryCreation.Description.EditBox
    local nativeSummary = ReadMeetingStoneTextControl(summaryBox)
    local useAttachedNativeTitle = entryCreation
        and widgets
        and widgets.meetingStoneUsesNativeTextControls == true
        and widgets.meetingStoneTitleBox == entryCreation.Name
        and IsMeetingStoneControlParentedTo(entryCreation.Name, widgets.meetingStoneTitleHolder)
    local useAttachedNativeSummary = entryCreation
        and widgets
        and widgets.meetingStoneUsesNativeTextControls == true
        and widgets.meetingStoneSummaryBox == summaryBox
        and IsMeetingStoneControlParentedTo(entryCreation and entryCreation.Description, widgets.meetingStoneSummaryHolder)

    if useAttachedNativeTitle then
        if IsMeetingStoneUsableText(title) and entryCreation.Name then
            SetMeetingStoneNativePublishText(entryCreation.Name, title)
            nativeTitle = ReadMeetingStoneTextControl(entryCreation.Name)
        elseif IsMeetingStoneUsableText(nativeTitle) then
            title = LimitMeetingStoneText(nativeTitle, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
        end
    elseif entryCreation and entryCreation.Name then
        SetMeetingStoneNativePublishText(entryCreation.Name, title)
        nativeTitle = ReadMeetingStoneTextControl(entryCreation.Name)
    end
    if useAttachedNativeSummary then
        if IsMeetingStoneUsableText(summary) and summaryBox then
            SetMeetingStoneNativePublishText(summaryBox, summary)
            nativeSummary = ReadMeetingStoneTextControl(summaryBox)
        elseif IsMeetingStoneUsableText(nativeSummary) then
            summary = LimitMeetingStoneText(nativeSummary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS)
        end
    elseif summaryBox then
        SetMeetingStoneNativePublishText(summaryBox, summary)
        nativeSummary = ReadMeetingStoneTextControl(summaryBox)
    end

    if useAttachedNativeTitle and not IsMeetingStoneUsableText(title) and IsMeetingStoneUsableText(nativeTitle) then
        title = LimitMeetingStoneText(nativeTitle, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
    end
    if useAttachedNativeSummary and IsMeetingStoneUsableText(nativeSummary) then
        summary = LimitMeetingStoneText(nativeSummary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS)
    end

    if entryCreation then
        if entryCreation.Name and _G.InputBoxInstructions_OnTextChanged then
            pcall(_G.InputBoxInstructions_OnTextChanged, entryCreation.Name)
        end
        if summaryBox and _G.InputBoxInstructions_OnTextChanged then
            pcall(_G.InputBoxInstructions_OnTextChanged, summaryBox)
        end
        if _G.LFGListEntryCreation_UpdateValidState then
            pcall(_G.LFGListEntryCreation_UpdateValidState, entryCreation)
        end
    end

    if not IsMeetingStoneUsableText(title) then
        return false, TRANSLATE("请填写活动标题。")
    end

    draft.title = title
    draft.summary = summary

    local writtenTitle = entryCreation and ReadMeetingStoneTextControl(entryCreation.Name) or title
    local writtenSummary = ReadMeetingStoneTextControl(summaryBox)
    if entryCreation and entryCreation.Name and not IsMeetingStoneUsableText(writtenTitle) and IsMeetingStoneUsableText(title) then
        SetMeetingStoneNativePublishText(entryCreation.Name, title)
        writtenTitle = ReadMeetingStoneTextControl(entryCreation.Name)
    end
    if not IsMeetingStoneUsableText(writtenTitle) and entryCreation and C_LFGList and C_LFGList.SetEntryTitle then
        local defaultPlaystyle = GetMeetingStoneDefaultEntryPlaystyle()
        local generalPlaystyle = draft.playStyle or GetMeetingStoneDefaultPlayStyle()
        pcall(C_LFGList.SetEntryTitle, draft.activityID, groupID, defaultPlaystyle, generalPlaystyle)
        writtenTitle = ReadMeetingStoneTextControl(entryCreation.Name)
    end

    return true, string.format(
        "原生标题：%s；描述长度：%s",
        FormatMeetingStoneDebugValue(writtenTitle),
        tostring(#writtenSummary)
    )
end

local function ApplyMeetingStoneCreateTextFields(createData, draft)
    if type(createData) ~= "table" or type(draft) ~= "table" then
        return
    end

    createData.name = LimitMeetingStoneText(draft.title, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
    createData.comment = LimitMeetingStoneText(draft.summary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS)
    createData.voiceChat = createData.voiceChat or ""
end

local function SendMeetingStoneListingServerInfo(activity, title, summary)
    return true
end

local function GetEditBoxText(editBox)
    if not editBox or not editBox.GetText then
        return ""
    end

    local ok, text = pcall(editBox.GetText, editBox)
    if ok then
        return text or ""
    end
    return ""
end

local function GetNumericEditBoxValue(editBox)
    local value = tonumber(GetEditBoxText(editBox))
    if not value or value < 0 then
        return 0
    end
    return math.floor(value)
end

local function IsMeetingStoneEditBoxFocused(editBox)
    if not editBox then
        return false
    end
    if editBox.HasFocus then
        local ok, focused = pcall(editBox.HasFocus, editBox)
        if ok and focused == true then
            return true
        end
    end
    local getCurrentKeyboardFocus = GetCurrentKeyBoardFocus or GetCurrentKeyboardFocus
    if getCurrentKeyboardFocus then
        local ok, focusedFrame = pcall(getCurrentKeyboardFocus)
        if ok and focusedFrame == editBox then
            return true
        end
    end
    return false
end

local function IsMeetingStoneWidgetTextChangedFromDraft(widgetText, draftText)
    local text = TrimMeetingStoneText(widgetText)
    if not IsMeetingStoneUsableText(text) then
        return false
    end
    return text ~= TrimMeetingStoneText(draftText)
end

local function HasMeetingStoneManagedFormTextProtection(owner)
    if not owner or not HasMeetingStoneActiveListing or not HasMeetingStoneActiveListing() then
        return false
    end

    local widgets = owner.widgets
    if not widgets then
        return false
    end

    local titleBox = widgets.meetingStoneTitleBox
    local summaryBox = widgets.meetingStoneSummaryBox
    if IsMeetingStoneEditBoxFocused(titleBox) or IsMeetingStoneEditBoxFocused(summaryBox) then
        return true
    end

    local draft = owner.meetingStoneDraft
    if type(draft) ~= "table" or widgets.meetingStoneDraftID ~= owner.meetingStoneDraftID then
        return false
    end

    if IsMeetingStoneWidgetTextChangedFromDraft(GetEditBoxText(titleBox), draft.title) then
        return true
    end
    if IsMeetingStoneWidgetTextChangedFromDraft(GetEditBoxText(summaryBox), draft.summary) then
        return true
    end
    return false
end

local function SetMeetingStoneWidgetText(widget, text)
    if not widget then
        return
    end
    if widget.SetEnabled then
        pcall(widget.SetEnabled, widget, true)
    elseif widget.Enable then
        pcall(widget.Enable, widget)
    end
    if widget.SetText then
        widget.mkMeetingStoneProgrammaticSetText = true
        pcall(widget.SetText, widget, text or "")
        widget.mkMeetingStoneProgrammaticSetText = nil
    end
    if widget.SetCursorPosition and widget.GetText then
        local ok, currentText = pcall(widget.GetText, widget)
        pcall(widget.SetCursorPosition, widget, ok and strlen(currentText or "") or 0)
    end
    if _G.InputBoxInstructions_OnTextChanged then
        pcall(_G.InputBoxInstructions_OnTextChanged, widget)
    end
end

local function FillMeetingStoneDraftTextFromCandidate(target, candidate)
    if type(target) ~= "table" or type(candidate) ~= "table" then
        return
    end

    if target.activityID and candidate.activityID and tonumber(target.activityID) ~= tonumber(candidate.activityID) then
        return
    end

    if not IsMeetingStoneUsableText(target.title) and IsMeetingStoneUsableText(candidate.title) then
        target.title = tostring(candidate.title or "")
    end
    if not IsMeetingStoneUsableText(target.summary) and IsMeetingStoneUsableText(candidate.summary) then
        target.summary = tostring(candidate.summary or "")
    end
end

local function FillMeetingStoneDraftTextFromWidgets(owner, draft)
    if type(owner) ~= "table" or type(draft) ~= "table" then
        return draft
    end

    FillMeetingStoneDraftTextFromCandidate(draft, owner.meetingStoneDraft)

    local widgets = owner.widgets
    if widgets then
        local title = GetEditBoxText(widgets.meetingStoneTitleBox)
        if not IsMeetingStoneUsableText(draft.title) and IsMeetingStoneUsableText(title) then
            draft.title = LimitMeetingStoneText(title, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
        end

        local summary = GetEditBoxText(widgets.meetingStoneSummaryBox)
        if not IsMeetingStoneUsableText(draft.summary) and IsMeetingStoneUsableText(summary) then
            draft.summary = LimitMeetingStoneText(summary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS)
        end
    end

    return draft
end

SetMeetingStoneNativePublishText = function(control, text)
    if control and control.SetEnabled then
        pcall(control.SetEnabled, control, true)
    elseif control and control.Enable then
        pcall(control.Enable, control)
    end
    SetMeetingStoneTextControl(control, text)
end

local function GetMeetingStoneSeasonDungeonTables()
    return MEETINGSTONE_FALLBACK_CHALLENGE_MAP_IDS or {}, MEETINGSTONE_FALLBACK_DUNGEON_GROUP_IDS or {}, MEETINGSTONE_FALLBACK_DUNGEON_ACTIVITY_IDS or {}
end

local meetingStoneChallengeMapNameCache = {}

local function AddMeetingStoneChallengeMapID(mapIDs, seen, mapID)
    mapID = tonumber(mapID)
    if mapID and not seen[mapID] then
        seen[mapID] = true
        mapIDs[#mapIDs + 1] = mapID
    end
end

local function GetMeetingStoneCandidateChallengeMapIDs()
    local mapIDs = {}
    local seen = {}

    if C_ChallengeMode and C_ChallengeMode.GetMapTable then
        local ok, currentSeasonMaps = pcall(C_ChallengeMode.GetMapTable)
        if ok and type(currentSeasonMaps) == "table" then
            for _, mapID in ipairs(currentSeasonMaps) do
                AddMeetingStoneChallengeMapID(mapIDs, seen, mapID)
            end
        end
    end

    for _, mapID in ipairs(MEETINGSTONE_FALLBACK_CHALLENGE_MAP_IDS or {}) do
        AddMeetingStoneChallengeMapID(mapIDs, seen, mapID)
    end

    return mapIDs
end

local function GetMeetingStoneChallengeMapName(mapID)
    mapID = tonumber(mapID)
    if not mapID then
        return nil
    end
    if meetingStoneChallengeMapNameCache[mapID] ~= nil then
        return meetingStoneChallengeMapNameCache[mapID] or nil
    end

    local mapName
    if C_ChallengeMode and C_ChallengeMode.GetMapUIInfo then
        local ok, name = pcall(C_ChallengeMode.GetMapUIInfo, mapID)
        if ok and type(name) == "string" and name ~= "" then
            mapName = name
        end
    end

    meetingStoneChallengeMapNameCache[mapID] = mapName or false
    return mapName
end

local function NormalizeMeetingStoneDungeonName(value)
    if type(value) ~= "string" then
        return ""
    end

    value = value:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
    value = value:gsub("史诗钥石", ""):gsub("Mythic Keystone", ""):gsub("Mythic%+", "")
    value = value:gsub("%s+", "")
    value = value:gsub("%(", ""):gsub("%)", ""):gsub("%[", ""):gsub("%]", "")
    value = value:gsub("（", ""):gsub("）", ""):gsub("【", ""):gsub("】", "")
    value = value:gsub("：", ":"):gsub("－", "-"):gsub("—", "-"):gsub("–", "-")
    return string.lower(value)
end

local function DoesMeetingStoneActivityNameMatchMapName(activityName, mapName)
    if type(activityName) ~= "string" or activityName == "" or type(mapName) ~= "string" or mapName == "" then
        return false
    end

    if activityName == mapName or activityName:find(mapName, 1, true) or mapName:find(activityName, 1, true) then
        return true
    end

    local normalizedActivityName = NormalizeMeetingStoneDungeonName(activityName)
    local normalizedMapName = NormalizeMeetingStoneDungeonName(mapName)
    return normalizedMapName ~= ""
        and normalizedActivityName ~= ""
        and (normalizedActivityName == normalizedMapName
            or normalizedActivityName:find(normalizedMapName, 1, true)
            or normalizedMapName:find(normalizedActivityName, 1, true))
end

local function GetMeetingStoneChallengeMapIDForActivityInfo(activityInfo)
    if type(activityInfo) ~= "table" then
        return nil
    end

    local fullName = activityInfo.fullName
    local shortName = activityInfo.shortName
    for _, mapID in ipairs(GetMeetingStoneCandidateChallengeMapIDs()) do
        local mapName = GetMeetingStoneChallengeMapName(mapID)
        if DoesMeetingStoneActivityNameMatchMapName(fullName, mapName)
            or DoesMeetingStoneActivityNameMatchMapName(shortName, mapName)
        then
            return mapID
        end
    end

    return nil
end

local function GetMeetingStoneFallbackChallengeMapIDForGroup(groupID)
    groupID = tonumber(groupID)
    if not groupID then
        return nil
    end

    for index, fallbackGroupID in ipairs(MEETINGSTONE_FALLBACK_DUNGEON_GROUP_IDS or {}) do
        if tonumber(fallbackGroupID) == groupID then
            return tonumber((MEETINGSTONE_FALLBACK_CHALLENGE_MAP_IDS or {})[index])
        end
    end
    return nil
end

local function GetMeetingStoneFallbackActivityForChallengeMap(mapID)
    mapID = tonumber(mapID)
    if not mapID then
        return nil, nil
    end

    for index, challengeMapID in ipairs(MEETINGSTONE_FALLBACK_CHALLENGE_MAP_IDS or {}) do
        if tonumber(challengeMapID) == mapID then
            return tonumber((MEETINGSTONE_FALLBACK_DUNGEON_GROUP_IDS or {})[index]), tonumber((MEETINGSTONE_FALLBACK_DUNGEON_ACTIVITY_IDS or {})[index])
        end
    end
    return nil, nil
end

local function GetMeetingStoneSafeList(list)
    if type(list) == "table" then
        return list
    end
    return {}
end

local function IsMeetingStoneMythicPlusActivity(activityInfo)
    if not activityInfo or activityInfo.categoryID ~= 2 then
        return false
    end
    if activityInfo.isMythicPlusActivity == true then
        return true
    end

    local fullName = tostring(activityInfo.fullName or "")
    local shortName = tostring(activityInfo.shortName or "")
    return fullName:find("史诗钥石", 1, true)
        or fullName:find("Mythic Keystone", 1, true)
        or shortName:find("史诗钥石", 1, true)
        or shortName:find("Mythic Keystone", 1, true)
end

local function AddMeetingStoneSystemDungeonOption(options, seen, activityID, fallbackGroupID, baseFilter)
    activityID = tonumber(activityID)
    if not activityID or not C_LFGList or not C_LFGList.GetActivityInfoTable then
        return
    end

    local ok, activityInfo = pcall(C_LFGList.GetActivityInfoTable, activityID)
    if not ok or not IsMeetingStoneMythicPlusActivity(activityInfo) then
        return
    end

    local groupID = tonumber(activityInfo.groupFinderActivityGroupID) or tonumber(fallbackGroupID)
    if not groupID then
        return
    end

    local categoryID = tonumber(activityInfo.categoryID) or 2
    local key = string.format("%d-%d-%d", categoryID, groupID, activityID)
    if seen[key] then
        return
    end
    seen[key] = true

    local challengeMapID = GetMeetingStoneChallengeMapIDForActivityInfo(activityInfo)
        or GetMeetingStoneFallbackChallengeMapIDForGroup(groupID)
    options[#options + 1] = {
        mapID = challengeMapID or groupID,
        challengeMapID = challengeMapID,
        activityID = activityID,
        groupID = groupID,
        categoryID = categoryID,
        baseFilter = baseFilter,
        customID = 0,
        isSeasonShortcut = true,
        activityCode = string.format("%d-%d-%d-0", categoryID, groupID, activityID),
        activityName = activityInfo.fullName or activityInfo.shortName or tostring(groupID),
    }
end

local function CollectMeetingStoneSystemDungeonOptions(options, seen)
    if not (C_LFGList
        and C_LFGList.GetActivityInfoTable
        and C_LFGList.GetAvailableActivityGroups
        and C_LFGList.GetAvailableActivities)
    then
        return
    end

    local baseFilter = Enum and Enum.LFGListFilter and Enum.LFGListFilter.PvE or 4
    local categoryID = 2
    local okGroups, groups = pcall(C_LFGList.GetAvailableActivityGroups, categoryID)
    for _, groupID in ipairs(GetMeetingStoneSafeList(okGroups and groups or nil)) do
        local okActivities, activities = pcall(C_LFGList.GetAvailableActivities, categoryID, groupID)
        for _, activityID in ipairs(GetMeetingStoneSafeList(okActivities and activities or nil)) do
            AddMeetingStoneSystemDungeonOption(options, seen, activityID, groupID, baseFilter)
        end
    end

    local okActivities, activities = pcall(C_LFGList.GetAvailableActivities, categoryID)
    for _, activityID in ipairs(GetMeetingStoneSafeList(okActivities and activities or nil)) do
        AddMeetingStoneSystemDungeonOption(options, seen, activityID, nil, baseFilter)
    end
end

function MK:GetMeetingStoneActivityCodeForMap(mapID)
    if not mapID then
        return nil
    end

    local requestedMapID = tonumber(mapID)
    local fallbackGroupID, fallbackActivityID = GetMeetingStoneFallbackActivityForChallengeMap(requestedMapID)
    local options = self:GetMeetingStoneDungeonOptions()
    for _, option in ipairs(options) do
        local optionActivityID = tonumber(option.activityID)
        if optionActivityID and tonumber(option.challengeMapID) == requestedMapID then
            return option.activityCode, option.activityID, option.groupID, option
        end
    end

    for _, option in ipairs(options) do
        local optionActivityID = tonumber(option.activityID)
        if fallbackActivityID and optionActivityID == fallbackActivityID then
            return option.activityCode, option.activityID, option.groupID, option
        end
    end

    for _, option in ipairs(options) do
        local optionActivityID = tonumber(option.activityID)
        if optionActivityID and fallbackGroupID and tonumber(option.groupID) == fallbackGroupID then
            return option.activityCode, option.activityID, option.groupID, option
        end
    end

    for _, option in ipairs(options) do
        local optionActivityID = tonumber(option.activityID)
        if optionActivityID and not option.challengeMapID and tonumber(option.mapID) == requestedMapID then
            return option.activityCode, option.activityID, option.groupID, option
        end
    end

    return nil
end

function MK:GetMeetingStoneActivityInfoForMap(mapID)
    local activityCode, activityID, groupID, option = self:GetMeetingStoneActivityCodeForMap(mapID)
    if not activityID or not C_LFGList or not C_LFGList.GetActivityInfoTable then
        return activityCode, activityID, groupID, nil, option
    end

    local ok, activityInfo = pcall(C_LFGList.GetActivityInfoTable, activityID)
    if ok then
        return activityCode, activityID, groupID, activityInfo, option
    end

    return activityCode, activityID, groupID, nil, option
end

function MK:GetMeetingStoneDungeonOptions()
    local options = {}
    local seen = {}

    CollectMeetingStoneSystemDungeonOptions(options, seen)
    if #options > 0 then
        return options
    end

    local challengeMapIDs, dungeons, activities = GetMeetingStoneSeasonDungeonTables()
    for index, groupID in ipairs(dungeons) do
        local activityID = activities[index]
        if tonumber(groupID) and tonumber(activityID) then
            local activityInfo
            if C_LFGList and C_LFGList.GetActivityInfoTable then
                local ok, info = pcall(C_LFGList.GetActivityInfoTable, activityID)
                activityInfo = ok and info or nil
            end
            local dungeonName = activityInfo and (activityInfo.fullName or activityInfo.shortName) or tostring(groupID)
            local challengeMapID = GetMeetingStoneChallengeMapIDForActivityInfo(activityInfo) or tonumber(challengeMapIDs[index])
            options[#options + 1] = {
                mapID = challengeMapID or tonumber(groupID),
                challengeMapID = challengeMapID,
                activityID = tonumber(activityID),
                groupID = tonumber(groupID),
                categoryID = activityInfo and activityInfo.categoryID or 2,
                baseFilter = Enum and Enum.LFGListFilter and Enum.LFGListFilter.PvE or 4,
                customID = 0,
                activityCode = string.format("2-%d-%d-0", groupID, activityID),
                activityName = dungeonName,
            }
        end
    end

    return options
end

function MK:GetMeetingStoneDungeonOptionForActivity(activityID)
    activityID = tonumber(activityID)
    if not activityID then
        return nil
    end

    local options = self:GetMeetingStoneDungeonOptions()
    for _, option in ipairs(options) do
        if tonumber(option.activityID) == activityID then
            return option
        end
    end
    return nil
end

local function IsMeetingStoneSeasonDungeonActivity(owner, activityID, activityInfo)
    activityID = tonumber(activityID)
    if not activityID then
        return false
    end

    if owner and owner.GetMeetingStoneDungeonOptionForActivity and owner:GetMeetingStoneDungeonOptionForActivity(activityID) then
        return true
    end

    if IsMeetingStoneMythicPlusActivity(activityInfo) == true then
        return true
    end

    return IsMeetingStoneMythicPlusActivity(GetMeetingStoneActivityInfo(activityID)) == true
end

IsMeetingStoneActiveSeasonDungeonListing = function(owner)
    local activeInfo = GetMeetingStoneActiveEntryInfo()
    local activityID = GetMeetingStoneActiveEntryActivityID(activeInfo)
    if activeInfo and IsMeetingStoneSeasonDungeonActivity(owner, activityID, activeInfo) then
        return true
    end

    local shared = owner and owner.meetingStoneSharedLeaderDraft
    if type(shared) == "table"
        and shared.active ~= false
        and IsMeetingStoneSeasonDungeonActivity(owner, shared.activityID)
    then
        return true
    end

    return false
end

function MK:IsMeetingStoneActiveSeasonDungeonListing()
    return IsMeetingStoneActiveSeasonDungeonListing(self)
end

function MK:SaveMeetingStoneActiveListingDraft(draft)
    if not (self.db and self.db.profile) then
        return
    end

    local stored = CopyMeetingStoneListingDraftForStorage(draft or self.meetingStoneDraft)
    if not stored or not stored.activityID then
        return
    end

    local previous = self:GetMeetingStoneStoredActiveListingDraft(stored.activityID)
    if not IsMeetingStoneUsableText(stored.title) and previous and IsMeetingStoneUsableText(previous.title) then
        stored.title = previous.title
    end
    if not IsMeetingStoneUsableText(stored.summary) and previous and IsMeetingStoneUsableText(previous.summary) then
        stored.summary = previous.summary
    end

    self.db.profile.meetingStoneActiveListingDraft = stored
end

function MK:ClearMeetingStoneActiveListingDraft()
    if self.db and self.db.profile then
        self.db.profile.meetingStoneActiveListingDraft = nil
    end
end

function MK:GetMeetingStoneStoredActiveListingDraft(activityID)
    local stored = self.db and self.db.profile and self.db.profile.meetingStoneActiveListingDraft
    if type(stored) ~= "table" then
        return nil
    end

    if activityID and stored.activityID and tonumber(stored.activityID) ~= tonumber(activityID) then
        return nil
    end

    return stored
end

function MK:GetMeetingStoneSharedLeaderDraft(activityID)
    local shared = self.meetingStoneSharedLeaderDraft
    if type(shared) ~= "table" then
        return nil
    end

    if activityID and shared.activityID and tonumber(shared.activityID) ~= tonumber(activityID) then
        return nil
    end
    return shared
end

function MK:IsMeetingStoneActiveListingManagedByAssistant()
    local activeInfo = GetMeetingStoneActiveEntryInfo()
    local activityID = GetMeetingStoneActiveEntryActivityID(activeInfo)
    if not activeInfo or not activityID then
        return false
    end

    if self.meetingStonePendingPublish then
        return true
    end

    return self:GetMeetingStoneStoredActiveListingDraft(activityID) ~= nil
end

local function RestoreMeetingStoneDraftFromActiveEntryInternal(self, force, readOnly)
    if not self then
        return false
    end

    if not force and self.meetingStoneDraft and self.meetingStoneDraft.activityID then
        return false
    end

    local activeInfo = GetMeetingStoneActiveEntryInfo()
    local activityID = GetMeetingStoneActiveEntryActivityID(activeInfo)
    if not activeInfo or not activityID then
        return false
    end
    self.meetingStoneSharedLeaderListingRemovedAt = nil

    local activityInfo = GetMeetingStoneActivityInfo(activityID)
    local option = self:GetMeetingStoneDungeonOptionForActivity(activityID)
    local stored = self:GetMeetingStoneStoredActiveListingDraft(activityID)
    local shared = self:GetMeetingStoneSharedLeaderDraft(activityID)
    local fallback = stored or shared
    local groupID = option and option.groupID or activityInfo and activityInfo.groupFinderActivityGroupID
    local categoryID = option and option.categoryID or activityInfo and activityInfo.categoryID or 2
    local entryCreation = nil
    if readOnly ~= true then
        PushMeetingStoneTextDirtySuppression(self)
        entryCreation = CopyMeetingStoneActiveEntryInfoToNativeCreationFields()
        PopMeetingStoneTextDirtySuppression(self)
    end
    local nativeTitle = entryCreation and ReadMeetingStoneTextControl(entryCreation.Name) or nil
    local nativeSummary = entryCreation
        and entryCreation.Description
        and ReadMeetingStoneTextControl(entryCreation.Description.EditBox)
        or nil
    local title = activeInfo.name or activeInfo.title or activeInfo.Name
    if not IsMeetingStoneUsableText(title) and IsMeetingStoneUsableText(nativeTitle) then
        title = nativeTitle
    end
    if not IsMeetingStoneUsableText(title) and stored then
        title = stored.title
    end
    if not IsMeetingStoneUsableText(title) and shared then
        title = shared.title
    end
    local summary = activeInfo.comment or activeInfo.description or activeInfo.summary or activeInfo.Comment
    if not IsMeetingStoneUsableText(summary) and IsMeetingStoneUsableText(nativeSummary) then
        summary = nativeSummary
    end
    if not IsMeetingStoneUsableText(summary) and stored then
        summary = stored.summary
    end
    if not IsMeetingStoneUsableText(summary) and shared then
        summary = shared.summary
    end
    local itemLevel = activeInfo.requiredItemLevel or activeInfo.RequiredItemLevel
    if (not tonumber(itemLevel) or tonumber(itemLevel) <= 0) and fallback then
        itemLevel = fallback.itemLevel
    end
    local score = activeInfo.requiredDungeonScore or activeInfo.RequiredDungeonScore
    if (not tonumber(score) or tonumber(score) <= 0) and fallback then
        score = fallback.score
    end
    local playStyle = activeInfo.playstyle or activeInfo.generalPlaystyle or activeInfo.PlayStyle or fallback and fallback.playStyle or GetMeetingStoneDefaultPlayStyle()

    self.meetingStoneDraft = {
        mapID = option and (option.challengeMapID or option.mapID) or fallback and fallback.mapID,
        dungeonName = option and option.activityName or fallback and fallback.dungeonName or activityInfo and (activityInfo.fullName or activityInfo.shortName) or "",
        activityCode = option and option.activityCode or fallback and fallback.activityCode or (groupID and string.format("2-%d-%d-0", groupID, activityID) or nil),
        activityID = activityID,
        groupID = groupID or fallback and fallback.groupID,
        categoryID = categoryID or fallback and fallback.categoryID,
        baseFilter = option and option.baseFilter or fallback and fallback.baseFilter or Enum and Enum.LFGListFilter and Enum.LFGListFilter.PvE or 4,
        customID = option and option.customID or fallback and fallback.customID or 0,
        activityName = option and option.activityName or fallback and fallback.activityName or activityInfo and (activityInfo.fullName or activityInfo.shortName) or "",
        title = tostring(title or ""),
        summary = tostring(summary or ""),
        itemLevel = tostring(itemLevel or 0),
        score = tostring(score or 0),
        privateGroup = activeInfo.privateGroup == true or activeInfo.isPrivateGroup == true or fallback and fallback.privateGroup == true,
        crossFaction = activeInfo.isCrossFactionListing ~= false and (not fallback or fallback.crossFaction ~= false),
        playStyle = playStyle,
        source = readOnly == true and "system-readonly" or "system",
    }
    self.meetingStoneDraftID = (self.meetingStoneDraftID or 0) + 1
    if readOnly ~= true and self.ClearMeetingStoneDraftTextDirty then
        self:ClearMeetingStoneDraftTextDirty()
    end
    return true
end

function MK:RestoreMeetingStoneDraftFromActiveEntry(force)
    return RestoreMeetingStoneDraftFromActiveEntryInternal(self, force, false)
end

function MK:RestoreMeetingStoneReadOnlyDraftFromActiveEntry(force)
    return RestoreMeetingStoneDraftFromActiveEntryInternal(self, force, true)
end

function MK:HasMeetingStoneDraftTextDirty()
    return HasMeetingStoneActiveListing()
        and (self.meetingStoneDraftTextDirty == true or HasMeetingStoneManagedFormTextProtection(self))
end

function MK:ShouldPreserveMeetingStoneManagedFormText()
    return HasMeetingStoneActiveListing()
        and (self.meetingStoneDraftTextDirty == true or HasMeetingStoneManagedFormTextProtection(self))
end

function MK:RestoreMeetingStoneProtectedNativeText(field)
    local widgets = self.widgets
    local draft = self.meetingStoneDraft
    if type(widgets) ~= "table" or type(draft) ~= "table" then
        return false
    end

    local widget
    local text
    if field == "summary" then
        widget = widgets.meetingStoneSummaryBox
        text = draft.summary
    else
        widget = widgets.meetingStoneTitleBox
        text = draft.title
    end

    if not widget then
        return false
    end

    text = tostring(text or "")
    if GetEditBoxText(widget) == text then
        return true
    end

    SetMeetingStoneWidgetText(widget, text)
    return true
end

function MK:ClearMeetingStoneDraftTextDirty()
    self.meetingStoneDraftTextDirty = nil
    self.meetingStoneDraftTextDirtyAt = nil
end

function MK:SelectMeetingStoneDungeonOption(option)
    if not option or not option.mapID then
        return false
    end

    local draft = self:SyncMeetingStoneDraftFromWidgets() or self.meetingStoneDraft or {}
    draft.mapID = option.challengeMapID or option.mapID
    draft.dungeonName = option.activityName
    draft.activityCode = option.activityCode
    draft.activityID = option.activityID
    draft.groupID = option.groupID
    draft.categoryID = option.categoryID
    draft.baseFilter = option.baseFilter
    draft.customID = option.customID or 0
    draft.activityName = option.activityName
    draft.itemLevel = draft.itemLevel ~= nil and draft.itemLevel or "0"
    draft.score = draft.score ~= nil and draft.score or "0"
    draft.summary = draft.summary or ""
    draft.title = draft.title or ""
    draft.privateGroup = draft.privateGroup == true
    draft.crossFaction = draft.crossFaction ~= false
    draft.playStyle = draft.playStyle or GetMeetingStoneDefaultPlayStyle()

    self.meetingStoneDraft = draft
    self.meetingStoneDraftID = (self.meetingStoneDraftID or 0) + 1
    if self.widgets and self.widgets.meetingStoneUsesNativeTextControls then
        SyncMeetingStoneNativeEntryCreationState(draft, { generalPlaystyle = draft.playStyle }, false, GetMeetingStoneActiveEntryInfo() ~= nil)
    end
    self:RefreshMeetingStoneActionButtons()
    return true
end

function MK:SelectMeetingStonePlayStyleOption(option)
    if not option then
        return false
    end

    local draft = self:SyncMeetingStoneDraftFromWidgets() or self.meetingStoneDraft or {}
    draft.playStyle = GetMeetingStonePlayStyleValue(option)
    draft.itemLevel = draft.itemLevel ~= nil and draft.itemLevel or "0"
    draft.score = draft.score ~= nil and draft.score or "0"
    draft.summary = draft.summary or ""
    draft.title = draft.title or ""

    self.meetingStoneDraft = draft
    self.meetingStoneDraftID = (self.meetingStoneDraftID or 0) + 1
    if self.widgets and self.widgets.meetingStoneUsesNativeTextControls then
        SyncMeetingStoneNativeEntryCreationState(draft, { generalPlaystyle = draft.playStyle }, true, GetMeetingStoneActiveEntryInfo() ~= nil)
    end
    self:RefreshMeetingStoneActionButtons()
    return true
end

function MK:PrepareMeetingStoneListing(characterEntry)
    local character = characterEntry and characterEntry.data
    if type(character) ~= "table" or not character.mapID or not character.keyLevel then
        if self.Print then
            self:Print(TRANSLATE("当前行没有可用于创建招募的钥石数据。"))
        end
        return false
    end

    local activityCode, activityID, groupID, activityInfo, option = self:GetMeetingStoneActivityInfoForMap(character.mapID)
    if not activityID then
        if self.Print then
            self:Print(TRANSLATE("没有找到该钥石对应的集合石活动。"))
        end
        return false
    end

    local dungeonName = character.dungeonName
    if (not dungeonName or dungeonName == "") and C_ChallengeMode and C_ChallengeMode.GetMapUIInfo then
        dungeonName = C_ChallengeMode.GetMapUIInfo(character.mapID)
    end

    self:ClearMeetingStonePublishFailureMessage()

    self.meetingStoneDraft = {
        owner = characterEntry.owner,
        key = characterEntry.key,
        name = character.name or characterEntry.key,
        class = character.class,
        mapID = character.mapID,
        keyLevel = tonumber(character.keyLevel) or 0,
        dungeonName = dungeonName or TRANSLATE("未知地下城"),
        keystoneLink = character.keystoneLink,
        activityCode = activityCode,
        activityID = activityID,
        groupID = groupID,
        categoryID = option and option.categoryID or activityInfo and activityInfo.categoryID or 2,
        baseFilter = option and option.baseFilter or Enum and Enum.LFGListFilter and Enum.LFGListFilter.PvE or 4,
        customID = option and option.customID or 0,
        activityName = activityInfo and activityInfo.fullName or dungeonName,
        title = "",
        summary = "",
        itemLevel = GetMeetingStoneLeaderItemLevelText(),
        score = "0",
        suppressNativeTitleAutofill = true,
        privateGroup = false,
        crossFaction = true,
        playStyle = GetMeetingStoneDefaultPlayStyle(),
    }
    self.meetingStoneDraftID = (self.meetingStoneDraftID or 0) + 1

    if self.DebugLog then
        self:DebugLog(
            "集合石快捷入口：mapID=%s 副本=%s -> activityID=%s 活动=%s groupID=%s optionMapID=%s optionChallengeMapID=%s",
            tostring(character.mapID),
            tostring(dungeonName or character.dungeonName or ""),
            tostring(activityID),
            tostring(activityInfo and activityInfo.fullName or option and option.activityName or ""),
            tostring(groupID),
            tostring(option and option.mapID or ""),
            tostring(option and option.challengeMapID or "")
        )
    end

    return true
end

function MK:OpenMeetingStoneListingForCharacter(characterEntry)
    if not self:IsMeetingStoneManagementAvailable() then
        if self.Print then
            self:Print(TRANSLATE(MEETINGSTONE_MISSING_ADDON_MESSAGE_KEY))
        end
        return
    end

    if not CanManageMeetingStoneListing() then
        if self.Print then
            self:Print(TRANSLATE("只有队长可以发布活动。"))
        end
        return
    end

    if not self:PrepareMeetingStoneListing(characterEntry) then
        return
    end

    self:ShowMainWindow()
    self:SelectMainTab("meetingstone")
end

function MK:OpenMeetingStonePanelFromQueueStatusEye()
    if not (self.IsMeetingStoneActiveSeasonDungeonListing and self:IsMeetingStoneActiveSeasonDungeonListing()) then
        return false
    end

    if _G.InCombatLockdown and _G.InCombatLockdown() then
        if self.Print then
            self:Print(TRANSLATE("战斗中无法打开系统面板。"))
        end
        return true
    end

    if self.IsMainWindowShown and self:IsMainWindowShown() and self.activeTab == "meetingstone" then
        CloseMeetingStoneAssistantForNativePanel(self)
        return true
    end

    HideMeetingStoneNativeGroupFinderFrames()
    if CanManageMeetingStoneListing() then
        self:RestoreMeetingStoneDraftFromActiveEntry(true)
    elseif not self:RestoreMeetingStoneReadOnlyDraftFromActiveEntry(true) then
        self:RestoreMeetingStoneDraftFromSharedLeaderEntry()
    end

    if self.ShowMainWindow then
        self:ShowMainWindow()
    end
    if self.SelectMainTab then
        self:SelectMainTab("meetingstone")
    end
    if self.RefreshMainWindow then
        self:RefreshMainWindow("MeetingStoneQueueStatusEye")
    end

    return true
end

function MK:OpenMeetingStonePanelFromLeadershipChange(action)
    if not self:IsMeetingStoneManagementAvailable() then
        if self.Print then
            self:Print(TRANSLATE(MEETINGSTONE_MISSING_ADDON_MESSAGE_KEY))
        end
        return false
    end

    if _G.InCombatLockdown and _G.InCombatLockdown() then
        if self.Print then
            self:Print(TRANSLATE("战斗中无法打开系统面板。"))
        end
        return true
    end

    if HideMeetingStoneNativeGroupFinderFrames then
        HideMeetingStoneNativeGroupFinderFrames()
    end

    local restored = false
    if HasMeetingStoneActiveListing() then
        restored = self:RestoreMeetingStoneDraftFromActiveEntry(true) == true
    end
    if restored and self.SaveMeetingStoneActiveListingDraft then
        self:SaveMeetingStoneActiveListingDraft(self.meetingStoneDraft)
        if self.ClearMeetingStoneDraftTextDirty then
            self:ClearMeetingStoneDraftTextDirty()
        end
        if self.BroadcastMeetingStoneListingDraft then
            self:BroadcastMeetingStoneListingDraft()
        end
    end

    if self.ShowMainWindow then
        self:ShowMainWindow("meetingstone", true)
    end
    if self.SelectMainTab then
        self:SelectMainTab("meetingstone")
    end
    if self.RefreshMainWindow then
        self:RefreshMainWindow(action == "edit" and "MeetingStoneLeaderChangeEdit" or "MeetingStoneLeaderChangeList")
    end

    return true
end

function MK:SyncMeetingStoneDraftFromWidgets(preserveExistingText)
    local draft = self.meetingStoneDraft
    local widgets = self.widgets
    if self.mkBuildingMeetingStoneTab then
        return draft
    end
    if not widgets then
        return nil
    end
    if not draft then
        draft = {}
        self.meetingStoneDraft = draft
        self.meetingStoneDraftID = (self.meetingStoneDraftID or 0) + 1
    end

    local previousTitle = draft.title
    local previousSummary = draft.summary
    local title = GetEditBoxText(widgets.meetingStoneTitleBox)
    local summary = GetEditBoxText(widgets.meetingStoneSummaryBox)
    local trimmedTitle = TrimMeetingStoneText(title)
    local trimmedSummary = TrimMeetingStoneText(summary)
    if IsMeetingStoneUsableText(trimmedTitle) or (not preserveExistingText and trimmedTitle == "") then
        draft.title = trimmedTitle
    elseif not preserveExistingText and not IsMeetingStoneNativePlaceholderText(trimmedTitle) then
        draft.title = title
    end
    if IsMeetingStoneUsableText(trimmedSummary) or (not preserveExistingText and trimmedSummary == "") then
        draft.summary = trimmedSummary
    elseif not preserveExistingText and not IsMeetingStoneNativePlaceholderText(trimmedSummary) then
        draft.summary = summary
    end
    draft.itemLevel = GetEditBoxText(widgets.meetingStoneItemLevelBox)
    draft.score = GetEditBoxText(widgets.meetingStoneScoreBox)
    draft.playStyle = draft.playStyle or GetMeetingStoneDefaultPlayStyle()
    draft.privateGroup = widgets.meetingStonePrivateToggle and widgets.meetingStonePrivateToggle:GetChecked() or false
    if draft.title ~= previousTitle or draft.summary ~= previousSummary then
        MarkMeetingStoneDraftTextDirty(self)
    end
    return draft
end

local function IsPlayerInMeetingStoneManagedGroup()
    if IsInGroup then
        local okHome, inHome = pcall(IsInGroup, LE_PARTY_CATEGORY_HOME)
        local okInstance, inInstance = pcall(IsInGroup, LE_PARTY_CATEGORY_INSTANCE)
        local okAny, inAny = pcall(IsInGroup)
        if (okHome and inHome) or (okInstance and inInstance) or (okAny and inAny) then
            return true
        end
    end

    if GetNumGroupMembers then
        local ok, count = pcall(GetNumGroupMembers)
        if ok and tonumber(count) and tonumber(count) > 0 then
            return true
        end
    end
    if UnitExists then
        for index = 1, 4 do
            local ok, exists = pcall(UnitExists, "party" .. index)
            if ok and exists then
                return true
            end
        end
        for index = 1, 40 do
            local ok, exists = pcall(UnitExists, "raid" .. index)
            if ok and exists then
                return true
            end
        end
    end
    return false
end

local function IsPlayerMeetingStoneGroupLeader()
    if UnitIsGroupLeader then
        local okHome, homeLeader = pcall(UnitIsGroupLeader, "player", LE_PARTY_CATEGORY_HOME)
        local okInstance, instanceLeader = pcall(UnitIsGroupLeader, "player", LE_PARTY_CATEGORY_INSTANCE)
        local okAny, anyLeader = pcall(UnitIsGroupLeader, "player")
        if (okHome and homeLeader) or (okInstance and instanceLeader) or (okAny and anyLeader) then
            return true
        end
    end

    if IsGroupLeader then
        local ok, isLeader = pcall(IsGroupLeader)
        if ok and isLeader then
            return true
        end
    end
    return false
end

function CanManageMeetingStoneListing()
    if not IsPlayerInMeetingStoneManagedGroup() then
        return true
    end
    return IsPlayerMeetingStoneGroupLeader()
end

HasMeetingStoneActiveListing = function()
    if GetMeetingStoneActiveEntryInfo() ~= nil then
        return true
    end

    if C_LFGList and C_LFGList.HasActiveEntryInfo then
        local ok, hasEntry = pcall(C_LFGList.HasActiveEntryInfo)
        return ok and hasEntry == true
    end
    return false
end

local function CreateMeetingStoneInactiveListingDraft()
    return {
        active = false,
        sentAt = time and time() or nil,
    }
end

local function IsMeetingStoneLocalRemovalPending(owner)
    local removedAt = tonumber(owner and owner.meetingStoneListingRemovalPendingAt)
    if not removedAt then
        return false
    end

    local now = GetTime and GetTime() or 0
    if now <= 0 or (now - removedAt) < MEETINGSTONE_LOCAL_REMOVAL_PENDING_SECONDS then
        return true
    end

    owner.meetingStoneListingRemovalPendingAt = nil
    return false
end

function MK:GetSharableMeetingStoneListingDraft()
    if not CanManageMeetingStoneListing() then
        return nil
    end

    if IsMeetingStoneLocalRemovalPending(self) then
        return CreateMeetingStoneInactiveListingDraft()
    end

    if not HasMeetingStoneActiveListing() then
        return CreateMeetingStoneInactiveListingDraft()
    end

    local activeInfo = GetMeetingStoneActiveEntryInfo()
    local activityID = GetMeetingStoneActiveEntryActivityID(activeInfo)
    local draft = CopyMeetingStoneListingDraftForStorage(self:GetMeetingStoneStoredActiveListingDraft(activityID))
    if draft then
        FillMeetingStoneDraftTextFromWidgets(self, draft)
    end
    if not draft then
        local previousDraft = self.meetingStoneDraft
        local previousDraftID = self.meetingStoneDraftID
        self:RestoreMeetingStoneDraftFromActiveEntry(true)
        draft = CopyMeetingStoneListingDraftForStorage(self.meetingStoneDraft)
        if draft then
            FillMeetingStoneDraftTextFromWidgets(self, draft)
        end
        self.meetingStoneDraft = previousDraft
        self.meetingStoneDraftID = previousDraftID
    end
    if not draft or not draft.activityID then
        return nil
    end

    draft.active = true
    draft.sentAt = time and time() or nil
    return draft
end

function MK:MarkMeetingStoneListingRemovedLocally()
    self.meetingStoneListingRemovalPendingAt = GetTime and GetTime() or 0
    self.meetingStonePendingPublish = nil
    self:ClearMeetingStoneActiveListingDraft()

    if self.FadeOutMeetingStoneEmptyApplicantEye then
        self:FadeOutMeetingStoneEmptyApplicantEye()
    end

    if self.BroadcastMeetingStoneListingRemoval then
        self:BroadcastMeetingStoneListingRemoval()
    elseif self.BroadcastMeetingStoneListingDraft then
        self:BroadcastMeetingStoneListingDraft(CreateMeetingStoneInactiveListingDraft())
    end
end

function MK:ReceiveMeetingStoneListingDraft(sender, draft)
    if type(draft) ~= "table" then
        return false
    end

    if draft.active == false then
        local canManage = CanManageMeetingStoneListing()
        local hasNativeEntry = GetMeetingStoneActiveEntryInfo() ~= nil
        local changed = self.meetingStoneSharedLeaderDraft ~= nil
            or (not canManage and type(self.meetingStoneDraft) == "table" and self.meetingStoneDraft.activityID ~= nil)
        self.meetingStoneSharedLeaderDraft = nil
        if MeetingStoneBridge.QueueQueueStatusRoutingRefresh then
            MeetingStoneBridge.QueueQueueStatusRoutingRefresh(self)
        end
        if hasNativeEntry then
            self.meetingStoneSharedLeaderListingRemovedAt = nil
            if not canManage then
                self:RestoreMeetingStoneReadOnlyDraftFromActiveEntry(true)
            end
            if changed and not canManage and self.IsMainWindowShown and self:IsMainWindowShown() and self.activeTab == "meetingstone" then
                self:RefreshMainWindow("MeetingStoneNativeListing")
            end
            return changed
        end

        self.meetingStoneSharedLeaderListingRemovedAt = GetTime and GetTime() or time and time() or 0
        if not canManage and changed then
            self.meetingStoneDraft = {
                title = "",
                summary = "",
                itemLevel = "0",
                score = "0",
            }
            self.meetingStoneDraftID = (self.meetingStoneDraftID or 0) + 1
        end
        if changed and not canManage and self.IsMainWindowShown and self:IsMainWindowShown() and self.activeTab == "meetingstone" then
            self:RefreshMainWindow("MeetingStoneSharedListingRemoved")
        end
        return changed
    end

    local stored = CopyMeetingStoneListingDraftForStorage(draft)
    if not stored or not stored.activityID then
        return false
    end

    FillMeetingStoneDraftTextFromCandidate(stored, self:GetMeetingStoneSharedLeaderDraft(stored.activityID))

    stored.active = true
    stored.owner = tostring(sender or draft.sender or "")
    stored.receivedAt = GetTime and GetTime() or time and time() or 0
    self.meetingStoneSharedLeaderDraft = stored
    self.meetingStoneSharedLeaderListingRemovedAt = nil
    if MeetingStoneBridge.QueueQueueStatusRoutingRefresh then
        MeetingStoneBridge.QueueQueueStatusRoutingRefresh(self)
    end

    local canManage = CanManageMeetingStoneListing()
    if not canManage then
        local restoredNative = self:RestoreMeetingStoneReadOnlyDraftFromActiveEntry(true) == true
        if not restoredNative then
            self.meetingStoneDraft = CopyMeetingStoneListingDraftForStorage(stored)
            self.meetingStoneDraftID = (self.meetingStoneDraftID or 0) + 1
        end
    end

    if not canManage and self.IsMainWindowShown and self:IsMainWindowShown() and self.activeTab == "meetingstone" then
        self:RefreshMainWindow("MeetingStoneSharedListing")
    end
    return true
end

function MK:RestoreMeetingStoneDraftFromSharedLeaderEntry()
    local sharedDraft = self.meetingStoneSharedLeaderDraft
    local draft = CopyMeetingStoneListingDraftForStorage(sharedDraft)
    if not draft or not draft.activityID then
        return false
    end

    self.meetingStoneDraft = draft
    self.meetingStoneDraftID = (self.meetingStoneDraftID or 0) + 1
    return true
end

local function SetMeetingStoneControlEnabled(control, enabled, applyAlpha)
    if not control then
        return
    end

    if control.SetEnabled then
        control:SetEnabled(enabled)
    elseif enabled and control.Enable then
        control:Enable()
    elseif not enabled and control.Disable then
        control:Disable()
    end

    if control.EnableMouse then
        control:EnableMouse(enabled == true)
    end
    if control.SetMouseClickEnabled then
        pcall(control.SetMouseClickEnabled, control, enabled == true)
    end
    if control.SetMouseMotionEnabled then
        pcall(control.SetMouseMotionEnabled, control, enabled == true)
    end
    if not enabled and control.ClearFocus then
        pcall(control.ClearFocus, control)
    end

    if applyAlpha ~= false and control.SetAlpha then
        control:SetAlpha(enabled and 1 or 0.55)
    end

    if core.MeetingStoneButtonArt and core.MeetingStoneButtonArt.Update then
        core.MeetingStoneButtonArt.Update(control)
    end
end

local function SetMeetingStoneVisualEnabled(region, enabled)
    if region and region.SetAlpha then
        region:SetAlpha(enabled and 1 or 0.55)
    end
end

local function RefreshMeetingStoneDisabledPlaceholderOverlays(widgets, enabled)
    if not widgets then
        return
    end

    local showReadOnlyInfo = widgets.meetingStoneReadOnlyInfoFrame
        and widgets.meetingStoneReadOnlyInfoFrame.IsShown
        and widgets.meetingStoneReadOnlyInfoFrame:IsShown()
    local showDisabled = enabled == false and not showReadOnlyInfo
    local function refreshOverlay(overlay, control)
        if not overlay then
            return
        end
        local show = showDisabled and GetEditBoxText(control) == ""
        overlay:SetShown(show)
    end

    refreshOverlay(widgets.meetingStoneTitleDisabledPlaceholderOverlay, widgets.meetingStoneTitleBox)
    refreshOverlay(widgets.meetingStoneSummaryDisabledPlaceholderOverlay, widgets.meetingStoneSummaryBox)
end

local function SetMeetingStoneApplicantHeaderEnabled(header, enabled)
    if not header then
        return
    end

    SetMeetingStoneVisualEnabled(header, enabled)
    for _, child in ipairs({ header:GetChildren() }) do
        local mouseEnabled = enabled == true and child.mkMeetingStoneApplicantSortable == true
        if child.EnableMouse then
            child:EnableMouse(mouseEnabled)
        end
        if child.SetMouseClickEnabled then
            pcall(child.SetMouseClickEnabled, child, mouseEnabled)
        end
        if child.SetMouseMotionEnabled then
            pcall(child.SetMouseMotionEnabled, child, mouseEnabled)
        end
    end
end

local function SetMeetingStoneManagementFormVisualEnabled(widgets, enabled)
    if not widgets then
        return
    end

    for _, key in ipairs({
        "meetingStoneTitleHolder",
        "meetingStoneSummaryHolder",
        "meetingStonePublishButton",
        "meetingStoneDisbandButton",
    }) do
        SetMeetingStoneVisualEnabled(widgets[key], enabled)
    end
end

local function SetMeetingStoneManagementInteractivity(widgets, enabled)
    if not widgets then
        return
    end

    SetMeetingStoneManagementFormVisualEnabled(widgets, enabled)
    SetMeetingStoneVisualEnabled(widgets.meetingStoneApplicantRowsFrame, enabled)

    for _, key in ipairs({
        "meetingStoneDungeonDropdown",
        "meetingStonePlayStyleDropdown",
        "meetingStoneItemLevelBox",
        "meetingStoneScoreBox",
        "meetingStonePublishButton",
        "meetingStoneDisbandButton",
        "meetingStoneApplicantRefreshButton",
    }) do
        SetMeetingStoneControlEnabled(widgets[key], enabled)
    end

    SetMeetingStoneControlEnabled(widgets.meetingStoneTitleBox, enabled, false)
    SetMeetingStoneControlEnabled(widgets.meetingStoneSummaryBox, enabled, false)
    RefreshMeetingStoneDisabledPlaceholderOverlays(widgets, enabled)
    SetMeetingStoneApplicantHeaderEnabled(widgets.meetingStoneApplicantHeader, enabled)

    if not enabled then
        for _, row in ipairs(widgets.meetingStoneApplicantRows or {}) do
            if row.EnableMouse then
                row:EnableMouse(false)
            end
            if row.widgets then
                SetMeetingStoneControlEnabled(row.widgets.inviteButton, false)
                SetMeetingStoneControlEnabled(row.widgets.declineButton, false)
            end
        end
    end
end

function MK:RefreshMeetingStoneActionButtons(widgets)
    widgets = widgets or self.widgets
    if not widgets then
        return
    end

    local meetingStoneAvailable = self:IsMeetingStoneManagementAvailable()
    if not meetingStoneAvailable then
        SetMeetingStoneManagementInteractivity(widgets, false)
        if widgets.meetingStonePublishButton then
            widgets.meetingStonePublishButton:SetText(TRANSLATE("创建招募"))
        end
        return
    end

    SetMeetingStoneManagementInteractivity(widgets, true)
    local draft = self.meetingStoneDraft or {}
    local hasDungeon = draft.activityID ~= nil
    local hasTitle = IsMeetingStoneUsableText(GetEditBoxText(widgets.meetingStoneTitleBox))
    local canManage = CanManageMeetingStoneListing()
    local hasActiveEntry = HasMeetingStoneActiveListing()
    local canEditListing = canManage == true
    local showReadOnlyInfo = widgets.meetingStoneReadOnlyInfoFrame
        and widgets.meetingStoneReadOnlyInfoFrame.IsShown
        and widgets.meetingStoneReadOnlyInfoFrame:IsShown()

    SetMeetingStoneManagementFormVisualEnabled(widgets, canEditListing or showReadOnlyInfo == true)

    if widgets.meetingStoneDungeonDropdown then
        SetMeetingStoneControlEnabled(widgets.meetingStoneDungeonDropdown, canEditListing and not hasActiveEntry)
    end

    for _, key in ipairs({
        "meetingStonePlayStyleDropdown",
        "meetingStoneItemLevelBox",
        "meetingStoneScoreBox",
    }) do
        SetMeetingStoneControlEnabled(widgets[key], canEditListing)
    end
    SetMeetingStoneControlEnabled(widgets.meetingStoneTitleBox, canEditListing, false)
    SetMeetingStoneControlEnabled(widgets.meetingStoneSummaryBox, canEditListing, false)
    RefreshMeetingStoneDisabledPlaceholderOverlays(widgets, canEditListing)
    SetMeetingStoneApplicantHeaderEnabled(widgets.meetingStoneApplicantHeader, meetingStoneAvailable)

    if widgets.meetingStonePublishButton then
        widgets.meetingStonePublishButton:SetText(hasActiveEntry and TRANSLATE("更新招募") or TRANSLATE("创建招募"))
        SetMeetingStoneControlEnabled(widgets.meetingStonePublishButton, hasDungeon and hasTitle and canEditListing)
    end

    if widgets.meetingStoneDisbandButton then
        SetMeetingStoneControlEnabled(widgets.meetingStoneDisbandButton, hasActiveEntry and canEditListing)
    end

    if widgets.meetingStoneApplicantRefreshButton then
        SetMeetingStoneControlEnabled(widgets.meetingStoneApplicantRefreshButton, true)
    end
end

function MK:SetMeetingStonePublishFailureMessage(reason)
    self.meetingStonePublishFailureMessage = FormatMeetingStonePublishFailureMessage(reason)
    self.meetingStonePublishFailureMessageAt = GetTime and GetTime() or time and time() or nil
    return self.meetingStonePublishFailureMessage
end

function MK:GetMeetingStonePublishFailureMessage()
    return self.meetingStonePublishFailureMessage
end

function MK:ClearMeetingStonePublishFailureMessage()
    local hadMessage = self.meetingStonePublishFailureMessage ~= nil
    self.meetingStonePublishFailureMessage = nil
    self.meetingStonePublishFailureMessageAt = nil
    return hadMessage
end

function MK:FormatMeetingStonePublishFailureMessage(reason)
    return FormatMeetingStonePublishFailureMessage(reason)
end

function MK:PublishMeetingStoneListing()
    self:ClearMeetingStonePublishFailureMessage()

    if not self:IsMeetingStoneManagementAvailable() then
        return false, TRANSLATE(MEETINGSTONE_MISSING_ADDON_MESSAGE_KEY)
    end

    local draft = self:SyncMeetingStoneDraftFromWidgets()
    if not draft or not draft.activityID then
        return false, TRANSLATE("请选择副本名称")
    end

    if not CanManageMeetingStoneListing() then
        return false, TRANSLATE("只有队长可以发布活动。")
    end

    if not C_LFGList then
        return false, TRANSLATE("C_LFGList 招募接口不可用。")
    end

    local activeEntryInfo = GetMeetingStoneActiveEntryInfo()
    local handler = activeEntryInfo and C_LFGList.UpdateListing or nil
    if not handler then
        if activeEntryInfo then
            return false, TRANSLATE("C_LFGList 招募接口不可用。")
        end
    end

    local createData = BuildMeetingStoneCreateArguments(nil, draft, activeEntryInfo)
    local usesNativeTextControls = self.widgets and self.widgets.meetingStoneUsesNativeTextControls == true
    local preserveNativeText = usesNativeTextControls
        and IsMeetingStoneControlParentedTo(self.widgets.meetingStoneTitleBox, self.widgets.meetingStoneTitleHolder)
    local titleBeforeNativeSync = LimitMeetingStoneText(draft.title, MEETINGSTONE_CREATION_TITLE_MAX_LETTERS)
    local summaryBeforeNativeSync = LimitMeetingStoneText(draft.summary, MEETINGSTONE_CREATION_SUMMARY_MAX_LETTERS)
    local nativeStateOK, nativeStateReason = SyncMeetingStoneNativeEntryCreationState(draft, createData, preserveNativeText == true, activeEntryInfo ~= nil)
    if preserveNativeText then
        if IsMeetingStoneUsableText(titleBeforeNativeSync) and draft.title ~= titleBeforeNativeSync then
            draft.title = titleBeforeNativeSync
        end
        if not IsMeetingStoneUsableText(draft.summary) and IsMeetingStoneUsableText(summaryBeforeNativeSync) then
            draft.summary = summaryBeforeNativeSync
        end
    end
    local textOK, textReason = PrepareMeetingStoneNativeCreationFields(draft, activeEntryInfo, self.widgets)
    if nativeStateOK and textOK then
        ApplyMeetingStoneCreateTextFields(createData, draft)
        ApplyMeetingStoneNativeCreationOptions(GetMeetingStoneNativeEntryCreationFrame(), draft, createData)
    end
    local validStateOK, validStateReason = false, "未校验"
    if nativeStateOK and textOK then
        validStateOK, validStateReason = RefreshMeetingStoneNativeCreationValidation()
    end
    if self.DebugLog then
        self:DebugLog(
            "集合石同步：%s；%s；文本：%s；校验：%s",
            nativeStateOK and "成功" or "失败",
            tostring(nativeStateReason or ""),
            tostring(textReason or ""),
            validStateOK and "通过" or tostring(validStateReason or "失败")
        )
    end
    if not nativeStateOK then
        return false, tostring(nativeStateReason or TRANSLATE("活动发布失败。"))
    end
    if not textOK then
        return false, tostring(textReason or TRANSLATE("活动发布失败。"))
    end
    if not validStateOK then
        return false, tostring(validStateReason or TRANSLATE("活动发布失败。"))
    end

    if self.DebugLog then
        self:DebugLog("集合石发布参数：%s", GetMeetingStoneCreateDebugText(createData, draft))
    end

    if activeEntryInfo then
        local ok, listingUpdated = pcall(handler, createData)
        if not ok then
            return false, tostring(listingUpdated or TRANSLATE("活动发布失败。"))
        end
        if listingUpdated == false then
            return false, TRANSLATE("活动发布失败。")
        end
        self.meetingStoneListingRemovalPendingAt = nil
        SendMeetingStoneListingServerInfo(nil, draft.title, draft.summary)
        if C_LFGList.RefreshApplicants then
            C_LFGList.RefreshApplicants()
        end
        self:SaveMeetingStoneActiveListingDraft(draft)
        if self.ClearMeetingStoneDraftTextDirty then
            self:ClearMeetingStoneDraftTextDirty()
        end
        if self.BroadcastMeetingStoneListingDraft then
            self:BroadcastMeetingStoneListingDraft()
        end
        return true, "updated"
    end

    self.meetingStonePendingPublish = {
        activity = nil,
        title = draft.title or "",
        summary = draft.summary or "",
        draft = CopyMeetingStoneListingDraftForStorage(draft),
        createData = createData,
        serverInfoSent = true,
        startedAt = GetTime and GetTime() or 0,
        ticket = (self.meetingStonePublishTicket or 0) + 1,
    }
    self.meetingStonePublishTicket = self.meetingStonePendingPublish.ticket

    local nativeCreateOK, nativeCreateReason = ClickMeetingStoneNativeCreateButton(activeEntryInfo, draft)
    if not nativeCreateOK then
        self.meetingStonePendingPublish = nil
        return false, tostring(nativeCreateReason or TRANSLATE("活动发布失败。"))
    end

    if C_Timer and C_Timer.After then
        local ticket = self.meetingStonePendingPublish.ticket
        C_Timer.After(MEETINGSTONE_PUBLISH_CONFIRM_TIMEOUT, function()
            if self.meetingStonePendingPublish and self.meetingStonePendingPublish.ticket == ticket then
                if HasMeetingStoneActiveListing() then
                    self:ConfirmMeetingStonePublish()
                else
                    self:FailMeetingStonePublish(TRANSLATE("活动发布失败。"))
                end
            end
        end)
    end

    return true, "pending"
end

function MK:ConfirmMeetingStonePublish()
    local pending = self.meetingStonePendingPublish
    if not pending then
        return
    end

    self.meetingStonePendingPublish = nil
    self.meetingStoneListingRemovalPendingAt = nil
    self:ClearMeetingStonePublishFailureMessage()

    if not pending.serverInfoSent then
        SendMeetingStoneListingServerInfo(pending.activity, pending.title, pending.summary)
    end
    if C_LFGList and C_LFGList.RefreshApplicants then
        C_LFGList.RefreshApplicants()
    end
    self:SaveMeetingStoneActiveListingDraft(pending.draft or self.meetingStoneDraft)
    if self.ClearMeetingStoneDraftTextDirty then
        self:ClearMeetingStoneDraftTextDirty()
    end
    if self.BroadcastMeetingStoneListingDraft then
        self:BroadcastMeetingStoneListingDraft()
    end

    if self.PlayMeetingStoneCreateSuccessFeedback then
        self:PlayMeetingStoneCreateSuccessFeedback()
    end
    if self.Print then
        self:Print(TRANSLATE("集合石招募已发布。"))
    end
    self:RefreshMeetingStoneActionButtons()
    if self.RefreshMainWindow then
        self:RefreshMainWindow("MeetingStonePublishConfirmed")
    end
end

function MK:FailMeetingStonePublish(reason)
    local pending = self.meetingStonePendingPublish
    if not pending then
        return
    end

    if self.DebugLog and pending.createData then
        self:DebugLog("集合石失败参数：%s", GetMeetingStoneCreateDebugText(pending.createData, self.meetingStoneDraft))
    end

    self.meetingStonePendingPublish = nil
    if self.StopMeetingStoneCreateSuccessFeedback then
        self:StopMeetingStoneCreateSuccessFeedback()
    end
    local failureMessage = self:SetMeetingStonePublishFailureMessage(reason)
    if self.Print then
        self:Print(failureMessage)
    end
    self:RefreshMeetingStoneActionButtons()
    if self.RefreshMainWindow then
        self:RefreshMainWindow("MeetingStonePublishFailed")
    end
end

function MK:HandleMeetingStoneActiveEntryUpdate(_, created)
    if created and self.meetingStonePendingPublish then
        self:ConfirmMeetingStonePublish()
    elseif not HasMeetingStoneActiveListing() then
        if self.MarkMeetingStoneListingRemovedLocally then
            self:MarkMeetingStoneListingRemovedLocally()
        end
        self:RefreshMeetingStoneActionButtons()
        if self.RefreshMainWindow then
            self:RefreshMainWindow("MeetingStoneActiveEntryRemoved")
        end
    else
        self.meetingStoneListingRemovalPendingAt = nil
        DisableMeetingStoneManagedAutoAccept(self, GetMeetingStoneActiveEntryInfo())
        local preserveManagedFormText = self.ShouldPreserveMeetingStoneManagedFormText
            and self:ShouldPreserveMeetingStoneManagedFormText()
        if preserveManagedFormText then
            if self.RestoreMeetingStoneProtectedNativeText then
                self:RestoreMeetingStoneProtectedNativeText("title")
                self:RestoreMeetingStoneProtectedNativeText("summary")
            end
            self:UpdateMeetingStoneEmptyApplicantEye()
            if self.BroadcastMeetingStoneListingDraft then
                self:BroadcastMeetingStoneListingDraft()
            end
            if self.RefreshMeetingStoneApplicantsOnly then
                self:RefreshMeetingStoneApplicantsOnly("MeetingStoneActiveEntryUpdate")
            end
            return
        elseif self.HasMeetingStoneDraftTextDirty and self:HasMeetingStoneDraftTextDirty() then
            self:SyncMeetingStoneDraftFromWidgets()
        else
            self:RestoreMeetingStoneDraftFromActiveEntry(false)
        end
        self:UpdateMeetingStoneEmptyApplicantEye()
        if self.BroadcastMeetingStoneListingDraft then
            self:BroadcastMeetingStoneListingDraft()
        end
        if self.RefreshMainWindow then
            self:RefreshMainWindow("MeetingStoneActiveEntryUpdate")
        end
    end
end

function MK:HandleMeetingStoneEntryCreationFailed(event, ...)
    if self.DebugLog then
        self:DebugLog(
            "集合石创建失败事件：事件=%s；参数=%s；%s",
            tostring(event),
            FormatMeetingStoneEventArgs(...),
            GetMeetingStoneNativeCreationFailureDebugText()
        )
    end
    self:FailMeetingStonePublish(TRANSLATE("活动创建失败，请重试。"))
end

function MK:RefreshMeetingStoneApplicantsOnly(refreshSource, refreshDetail)
    local preserveManagedFormText = self.ShouldPreserveMeetingStoneManagedFormText
        and self:ShouldPreserveMeetingStoneManagedFormText()
    if preserveManagedFormText and self.RestoreMeetingStoneProtectedNativeText then
        self:RestoreMeetingStoneProtectedNativeText("title")
        self:RestoreMeetingStoneProtectedNativeText("summary")
    end
    if core.MeetingStoneApplicantList and core.MeetingStoneApplicantList.CacheCurrentApplicants then
        core.MeetingStoneApplicantList.CacheCurrentApplicants(self)
    end
    self:UpdateMeetingStoneEmptyApplicantEye()

    if not (self.IsMainWindowShown and self:IsMainWindowShown()) then
        return
    end
    if self.activeTab ~= "meetingstone" or not self.widgets then
        return
    end

    if core.MeetingStoneApplicantList and core.MeetingStoneApplicantList.Refresh then
        core.MeetingStoneApplicantList.Refresh(self, self.widgets, refreshSource)
    end
    if preserveManagedFormText and self.RestoreMeetingStoneProtectedNativeText then
        self:RestoreMeetingStoneProtectedNativeText("title")
        self:RestoreMeetingStoneProtectedNativeText("summary")
    end
    if self.RefreshMeetingStoneActionButtons then
        self:RefreshMeetingStoneActionButtons(self.widgets)
    end
    if self.GetActiveTabSnapshot then
        self.lastRenderedSnapshotByTab = self.lastRenderedSnapshotByTab or {}
        self.lastRenderedSnapshotByTab.meetingstone = self:GetActiveTabSnapshot()
    end
    if self.DebugLog then
        self:DebugLog(
            "集合石申请列表局部刷新：source=%s detail=%s",
            tostring(refreshSource or "unknown"),
            tostring(refreshDetail or "-")
        )
    end
end

function MK:HandleMeetingStoneApplicantListUpdated()
    self:RefreshMeetingStoneApplicantsOnly("MeetingStoneApplicantListUpdated")
end

function MK:HandleMeetingStoneApplicantUpdated()
    self:RefreshMeetingStoneApplicantsOnly("MeetingStoneApplicantUpdated")
end

function MK:HandleMeetingStoneEntryExpired()
    if self.MarkMeetingStoneListingRemovedLocally then
        self:MarkMeetingStoneListingRemovedLocally()
    end
    self:RefreshMeetingStoneActionButtons()
    if self.RefreshMainWindow then
        self:RefreshMainWindow("MeetingStoneEntryExpired")
    end
end


MeetingStoneListing.GetPlayStyleValue = GetMeetingStonePlayStyleValue
MeetingStoneListing.GetDefaultPlayStyle = GetMeetingStoneDefaultPlayStyle
MeetingStoneListing.IsPlayStyleValueEqual = IsMeetingStonePlayStyleValueEqual
MeetingStoneListing.GetPlayStyleText = GetMeetingStonePlayStyleText
MeetingStoneListing.TrimText = TrimMeetingStoneText
MeetingStoneListing.LimitText = LimitMeetingStoneText
MeetingStoneListing.IsTextFilled = IsMeetingStoneTextFilled
MeetingStoneListing.GetDraftNumber = GetMeetingStoneDraftNumber
MeetingStoneListing.CopyDraftForStorage = CopyMeetingStoneListingDraftForStorage
MeetingStoneListing.GetActiveEntryInfo = GetMeetingStoneActiveEntryInfo
MeetingStoneListing.GetActiveEntryActivityID = GetMeetingStoneActiveEntryActivityID
MeetingStoneListing.GetActivityInfo = GetMeetingStoneActivityInfo
MeetingStoneListing.GetCategoryInfo = GetMeetingStoneCategoryInfo
MeetingStoneListing.GetCrossFactionAllowed = GetMeetingStoneCrossFactionAllowed
MeetingStoneListing.BuildCreateArguments = BuildMeetingStoneCreateArguments
MeetingStoneListing.GetCreateDebugText = GetMeetingStoneCreateDebugText
MeetingStoneListing.SyncNativeEntryCreationState = SyncMeetingStoneNativeEntryCreationState
MeetingStoneListing.PrepareNativeCreationFields = PrepareMeetingStoneNativeCreationFields
MeetingStoneListing.SendServerInfo = SendMeetingStoneListingServerInfo
MeetingStoneListing.GetEditBoxText = GetEditBoxText
MeetingStoneListing.GetNumericEditBoxValue = GetNumericEditBoxValue
MeetingStoneListing.SetWidgetText = SetMeetingStoneWidgetText
MeetingStoneListing.IsManagementAvailable = IsMeetingStoneNativeLFGAvailable
MeetingStoneListing.CanManageListing = CanManageMeetingStoneListing
MeetingStoneListing.HasActiveListing = HasMeetingStoneActiveListing
MeetingStoneListing.IsApplicantOnlyRefreshSource = IsMeetingStoneApplicantOnlyRefreshSource
MeetingStoneListing.SetControlEnabled = SetMeetingStoneControlEnabled
MeetingStoneListing.SetManagementInteractivity = SetMeetingStoneManagementInteractivity
