local addonName, addonTable = ...

local AceSerializer = LibStub("AceSerializer-3.0")
local NetEaseSocket = LibStub("NetEaseSocket-2.0", true)

local COMM_PREFIX = "NERB"
local CONNECT_RETRY_SECONDS = 3
local CACHE_TTL_SECONDS = 24 * 60 * 60
local QUERY_DEBOUNCE_SECONDS = 0.5
local NATIVE_QUERY_RETRY_SECONDS = 1
local NATIVE_QUERY_RETRY_LIMIT = 30
local SERVER_QUERY_RESEND_SECONDS = 8
local SERVER_LOGIN_QUERY_DELAY_SECONDS = 5
local SERVER_LOGIN_SOURCE_FALLBACK = 0
local ACCOUNT_TYPE_CACHE_MAX_ENTRIES = 1000
local ACCOUNT_TYPE_FORCE_REFRESH_SECONDS = 10
local ACCOUNT_TYPE_QUERY_TIMEOUT_SECONDS = SERVER_QUERY_RESEND_SECONDS
local MEETINGSTONE_PROTOCOL_ADDON_NAME = "MeetingStone"
local MEETINGSTONE_PROTOCOL_VERSION_FALLBACK = "12.1.6"
local NATIVE_MEETINGSTONE_ENV_NAMES = {
    "MeetingStone",
    "MeetingStoneEX",
    "MeetingStoneEx",
}
local ADDON_SOURCE_HOSTS = {
    { name = "BigFoot", source = 1 },
    { name = "!!!163UI!!!", source = 2 },
    { name = "Duowan", source = 4 },
    { name = "ElvUI", source = 8 },
}
local ADDON_ICON_PATH = "Interface\\AddOns\\" .. addonName .. "\\Art\\Ui\\Icon\\"
local NEWBIE_ICON_TEXTURE = ADDON_ICON_PATH .. "Newbie.png"
local VETERAN_ICON_TEXTURE = ADDON_ICON_PATH .. "Veteran.png"
local LEAVER_ICON_TEXTURE = ADDON_ICON_PATH .. "isLeaver.png"
local STAR_ICON_TEXTURE = ADDON_ICON_PATH .. "Star.png"
local ACCOUNT_TYPE_ICON_SIZE = 18
local ACCOUNT_TYPE_GREEN_TEXT_COLOR = { 0.18, 1, 0.22, 1 }
local ACCOUNT_TYPE_GOLD_TEXT_COLOR = { 1, 0.82, 0, 1 }
local ACCOUNT_TYPE_RED_TEXT_COLOR = { 1, 0.08, 0.05, 1 }
local ACCOUNT_TYPE_PENDING_TEXT_COLOR = { 0.62, 0.75, 1, 1 }
local LEADER_ICON_TEXTURE = ADDON_ICON_PATH .. "Leader.png"

local MeetingStoneData = addonTable.MeetingStoneData or {}
addonTable.MeetingStoneData = MeetingStoneData

local function IsInCombatLockdown()
    return InCombatLockdown and InCombatLockdown()
end

local function IsMeetingStoneDataCombatPaused(self)
    return type(self) == "table" and (self.combatPaused == true or IsInCombatLockdown())
end

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

    local trimmed = value:match("^%s*(.-)%s*$")
    if not trimmed or trimmed == "" then
        return nil
    end

    return trimmed
end

local function NormalizeRealmName(realm)
    local normalizedRealm = TrimString(realm)
    if not normalizedRealm then
        return nil
    end

    normalizedRealm = normalizedRealm:gsub("%s+", "")
    return normalizedRealm ~= "" and normalizedRealm or nil
end

local function SplitLeaderName(name)
    local leader = TrimString(name)
    if not leader then
        return nil, nil
    end

    local shortName, realm = leader:match("^([^-]+)%-(.+)$")
    if shortName and realm then
        return TrimString(shortName), TrimString(realm)
    end

    return leader, nil
end

local function FormatLeaderName(name, realm)
    local shortName = TrimString(name)
    if not shortName then
        return nil
    end

    local resolvedRealm = TrimString(realm)
    if resolvedRealm then
        return shortName .. "-" .. resolvedRealm
    end

    return shortName
end

local function FormatLeaderNameWithRawRealm(name, realm)
    local shortName = TrimString(name)
    local rawRealm = TrimString(realm)
    if not shortName then
        return nil
    end
    if rawRealm then
        return shortName .. "-" .. rawRealm
    end
    return shortName
end

local function GetAddonMetadata(targetAddonName, fieldName)
    if C_AddOns and C_AddOns.GetAddOnMetadata then
        local ok, value = pcall(C_AddOns.GetAddOnMetadata, targetAddonName, fieldName)
        if ok and type(value) == "string" and value ~= "" then
            return value
        end
    end
    if GetAddOnMetadata then
        local ok, value = pcall(GetAddOnMetadata, targetAddonName, fieldName)
        if ok and type(value) == "string" and value ~= "" then
            return value
        end
    end
    return nil
end

local function GetMeetingStoneProtocolVersion()
    return GetAddonMetadata(MEETINGSTONE_PROTOCOL_ADDON_NAME, "Version")
        or MEETINGSTONE_PROTOCOL_VERSION_FALLBACK
end

local function IsAddonLoaded(addon)
    if C_AddOns and C_AddOns.IsAddOnLoaded then
        local ok, loaded = pcall(C_AddOns.IsAddOnLoaded, addon)
        return ok and loaded == true
    end
    if IsAddOnLoaded then
        local ok, loaded = pcall(IsAddOnLoaded, addon)
        return ok and loaded == true
    end
    return false
end

local function GetAddonSource()
    for _, host in ipairs(ADDON_SOURCE_HOSTS) do
        if IsAddonLoaded(host.name) then
            return host.source, false
        end
    end

    return SERVER_LOGIN_SOURCE_FALLBACK, true
end

local function GetServerLoginQueryData()
    local dataCache = _G.DataCache
    if dataCache and type(dataCache.GetQueryData) == "function" then
        local ok, queryData = pcall(dataCache.GetQueryData, dataCache)
        if ok and type(queryData) == "table" then
            return queryData
        end
    end

    return {}
end

local function GetServerRealm()
    local realm = TrimString(GetRealmName and GetRealmName())
    if realm then
        return realm
    end

    local realms = GetAutoCompleteRealms and GetAutoCompleteRealms()
    return realms and TrimString(realms[1]) or nil
end

local function GetAddonServer()
    local prefix = _G.NETEASE_SERVER_PREFIX or "S1"
    local faction = UnitFactionGroup and UnitFactionGroup("player") or "Neutral"
    return prefix .. tostring(faction or "Neutral")
end

local function GetNativeMeetingStoneEnv()
    local bridge = addonTable.MeetingStoneBridge
    if type(bridge) == "table" and type(bridge.GetEnv) == "function" then
        local ok, env = pcall(bridge.GetEnv)
        if ok and type(env) == "table" then
            return env
        end
    end

    local libStub = _G.LibStub
    if not libStub then
        return nil
    end

    local ok, envLib = pcall(libStub, "NetEaseEnv-1.0")
    if not ok or type(envLib) ~= "table" or type(envLib._NSList) ~= "table" then
        return nil
    end

    for _, envName in ipairs(NATIVE_MEETINGSTONE_ENV_NAMES) do
        local env = envLib._NSList[envName]
        if type(env) == "table" then
            return env
        end
    end

    return nil
end

local function GetSocketClientHandler(client)
    local socket = NetEaseSocket or LibStub("NetEaseSocket-2.0", true)
    local handlers = socket and socket.handlers
    return client and type(handlers) == "table" and handlers[client] or nil
end

local function IsSocketClientReady(client)
    if not client then
        return false
    end

    if client.IsReady then
        return client:IsReady() == true
    end

    local handler = GetSocketClientHandler(client)
    if handler and handler.IsReady then
        return handler:IsReady() == true
    end

    return false
end

local function GetSocketClientTarget(client)
    if not client then
        return nil
    end

    if type(client.target) == "string" and client.target ~= "" then
        return client.target
    end

    local handler = GetSocketClientHandler(client)
    if type(handler) == "table" then
        if type(handler.target) == "string" and handler.target ~= "" then
            return handler.target
        end
        if type(handler.connectTarget) == "string" and handler.connectTarget ~= "" then
            return handler.connectTarget
        end
    end

    if type(client.connectTarget) == "string" and client.connectTarget ~= "" then
        return client.connectTarget
    end

    return nil
end

local function GetSocketClientServerTarget(client)
    if not client then
        return nil
    end

    if type(client.target) == "string" and client.target ~= "" then
        return client.target
    end

    local handler = GetSocketClientHandler(client)
    if type(handler) == "table" and type(handler.target) == "string" and handler.target ~= "" then
        return handler.target
    end

    return nil
end

local function GetDebugTime()
    return GetTime and GetTime() or time()
end

local function GetServerLoginQueryDelayRemaining(self)
    if type(self.lastSLOGINAt) ~= "number" then
        return 0
    end

    local elapsed = GetDebugTime() - self.lastSLOGINAt
    if elapsed >= SERVER_LOGIN_QUERY_DELAY_SECONDS then
        return 0
    end

    return SERVER_LOGIN_QUERY_DELAY_SECONDS - elapsed
end

local function CountTableValues(values)
    if type(values) ~= "table" then
        return 0
    end

    local count = 0
    for _ in pairs(values) do
        count = count + 1
    end
    return count
end

local function IsActiveNewbieAccountTypeData(data)
    if type(data) ~= "table" or data.isNewbie ~= true then
        return false
    end

    local expireTime = tonumber(data.newbieExpireTime)
    return expireTime ~= nil and time() < expireTime
end

local function PreserveActiveNewbieAccountTypeData(existing, incoming)
    if type(incoming) ~= "table" then
        return incoming
    end
    if not IsActiveNewbieAccountTypeData(existing) or IsActiveNewbieAccountTypeData(incoming) then
        return incoming
    end

    local merged = {}
    for key, value in pairs(incoming) do
        merged[key] = value
    end
    merged.isNewbie = true
    merged.newbieExpireTime = existing.newbieExpireTime
    merged.newbiePreservedAt = time()
    return merged
end

local function FormatTableKeys(values, limit)
    if type(values) ~= "table" then
        return nil
    end

    local keys = {}
    for key in pairs(values) do
        keys[#keys + 1] = tostring(key)
    end
    table.sort(keys)

    limit = tonumber(limit) or #keys
    if #keys > limit then
        local remaining = #keys - limit
        for index = #keys, limit + 1, -1 do
            keys[index] = nil
        end
        keys[#keys + 1] = "+" .. tostring(remaining)
    end

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

local function EnsureAccountTypeQueryStates(self)
    if type(self.accountTypeQueryStates) ~= "table" then
        self.accountTypeQueryStates = {}
    end
    return self.accountTypeQueryStates
end

local function GetAccountTypeQueryState(self, leader)
    if type(leader) ~= "string" or leader == "" then
        return nil
    end

    local states = EnsureAccountTypeQueryStates(self)
    local state = states[leader]
    if type(state) ~= "table" then
        return nil
    end

    local now = GetDebugTime()
    if state.status == "sent" and type(state.sentAt) == "number" and (now - state.sentAt) >= ACCOUNT_TYPE_QUERY_TIMEOUT_SECONDS then
        state.status = "retry"
        state.retryAt = now
        state.failureReason = "timeout"
    elseif state.status == "pending" and type(state.queuedAt) == "number" and (now - state.queuedAt) >= ACCOUNT_TYPE_QUERY_TIMEOUT_SECONDS then
        state.status = "retry"
        state.retryAt = now
        state.failureReason = "timeout"
    end

    return state
end

local function IsAccountTypeQueryPending(state)
    return type(state) == "table" and (state.status == "pending" or state.status == "sent")
end

local EnsureState
local MarkAccountTypeQueryPending
local MarkAccountTypeQueryDone
local StoreNoResultAccountTypeData
local NormalizeLeaderName
local IsAccountTypeCacheForLeader

local function ScheduleAccountTypeQueryStateRefresh(self)
    if not (self and C_Timer and C_Timer.After) then
        return
    end
    if self.accountTypeQueryStateRefreshScheduled then
        return
    end

    self.accountTypeQueryStateRefreshScheduled = true
    C_Timer.After(ACCOUNT_TYPE_QUERY_TIMEOUT_SECONDS + 0.1, function()
        MeetingStoneData.accountTypeQueryStateRefreshScheduled = false
        if IsMeetingStoneDataCombatPaused(MeetingStoneData) then
            MeetingStoneData.combatDeferred = true
            return
        end

        if MeetingStoneData.owner
            and MeetingStoneData.owner.IsMainWindowShown
            and MeetingStoneData.owner:IsMainWindowShown()
            and MeetingStoneData.owner.RefreshMainWindow
        then
            MeetingStoneData.owner:RefreshMainWindow("MeetingStoneData", "AccountTypeQueryTimeout")
        end

        local shouldFlush = false
        local resolvedNoResult = false
        for leader in pairs(MeetingStoneData.accountTypeQueryStates or {}) do
            local state = GetAccountTypeQueryState(MeetingStoneData, leader)
            if state and state.status == "retry" then
                if state.sendMethod == "netease" then
                    resolvedNoResult = StoreNoResultAccountTypeData(MeetingStoneData, leader, state.failureReason or "timeout", "netease") or resolvedNoResult
                else
                    MarkAccountTypeQueryPending(MeetingStoneData, leader)
                    MeetingStoneData.pendingNames[leader] = true
                    shouldFlush = true
                end
            elseif IsAccountTypeQueryPending(state) then
                ScheduleAccountTypeQueryStateRefresh(MeetingStoneData)
            end
        end

        if resolvedNoResult
            and MeetingStoneData.owner
            and MeetingStoneData.owner.IsMainWindowShown
            and MeetingStoneData.owner:IsMainWindowShown()
            and MeetingStoneData.owner.RefreshMainWindow
        then
            MeetingStoneData.owner:RefreshMainWindow("MeetingStoneData", "AccountTypeNoResult")
        end

        if shouldFlush then
            if MeetingStoneData.ready then
                MeetingStoneData:ScheduleFlush()
            else
                MeetingStoneData:Connect()
            end
        end
    end)
end

MarkAccountTypeQueryPending = function(self, leader)
    if type(leader) ~= "string" or leader == "" then
        return nil
    end

    local states = EnsureAccountTypeQueryStates(self)
    local now = GetDebugTime()
    local state = states[leader]
    if type(state) ~= "table" then
        state = {}
        states[leader] = state
    elseif IsAccountTypeQueryPending(state) then
        return state
    end

    state.status = "pending"
    state.queuedAt = now
    state.queuedWallTime = time()
    state.sentAt = nil
    state.sentWallTime = nil
    state.retryAt = nil
    state.failureReason = nil
    ScheduleAccountTypeQueryStateRefresh(self)
    return state
end

local function MarkAccountTypeQuerySent(self, leader, sentAt, method)
    if type(leader) ~= "string" or leader == "" then
        return
    end

    local state = EnsureAccountTypeQueryStates(self)[leader]
    if type(state) ~= "table" then
        state = MarkAccountTypeQueryPending(self, leader)
    end

    state.status = "sent"
    state.sentAt = sentAt or GetDebugTime()
    state.sentWallTime = time()
    state.sendMethod = method
    ScheduleAccountTypeQueryStateRefresh(self)
end

MarkAccountTypeQueryDone = function(self, leader)
    if type(leader) ~= "string" or leader == "" then
        return
    end

    local state = EnsureAccountTypeQueryStates(self)[leader]
    if type(state) ~= "table" then
        state = {}
        self.accountTypeQueryStates[leader] = state
    end

    state.status = "done"
    state.doneAt = GetDebugTime()
    state.retryAt = nil
    state.failureReason = nil
    if type(self.pendingNames) == "table" then
        self.pendingNames[leader] = nil
    end
    if type(self.nativeMeetingStonePendingNames) == "table" then
        self.nativeMeetingStonePendingNames[leader] = nil
    end
end

StoreNoResultAccountTypeData = function(self, leader, reason, source)
    if type(self) ~= "table" or type(leader) ~= "string" or leader == "" then
        return false
    end

    EnsureState(self)
    local data = type(self.cache[leader]) == "table" and IsAccountTypeCacheForLeader(self.cache[leader], leader, leader) and self.cache[leader] or {}
    if IsActiveNewbieAccountTypeData(data) then
        data.noAccountTypeReason = reason
        data.lastNoResultAt = time()
        data.accountTypeLeaderName = NormalizeLeaderName(leader) or leader
        MarkAccountTypeQueryDone(self, leader)
        return false
    end
    data.level = -1
    data.starLevel = -1
    data.isNewbie = false
    data.newbieExpireTime = nil
    data.medalTime = time()
    data.source = source or "netease"
    data.sourceFallback = false
    data.noAccountTypeResult = true
    data.noAccountTypeReason = reason
    data.accountTypeLeaderName = NormalizeLeaderName(leader) or leader
    self.cache[leader] = data
    MarkAccountTypeQueryDone(self, leader)
    return true
end

local function MarkAccountTypeQueryRetry(self, leader, reason)
    if type(leader) ~= "string" or leader == "" then
        return
    end

    local state = EnsureAccountTypeQueryStates(self)[leader]
    if type(state) ~= "table" then
        state = {}
        self.accountTypeQueryStates[leader] = state
    end

    state.status = "retry"
    state.retryAt = GetDebugTime()
    state.failureReason = reason or "retry"
    self.pendingNames = self.pendingNames or {}
    self.pendingNames[leader] = true
    if self.ready then
        self:ScheduleFlush()
    else
        self:Connect()
    end
end

local function SplitQueryNames(queryNames)
    local names = {}
    if type(queryNames) ~= "string" or queryNames == "" then
        return names
    end

    for name in queryNames:gmatch("[^,]+") do
        local trimmed = TrimString(name)
        if trimmed then
            names[#names + 1] = trimmed
        end
    end

    return names
end

local function FormatTarget(target)
    local realm = GetServerRealm()
    if type(target) ~= "string" or target == "" or type(realm) ~= "string" or realm == "" then
        return nil
    end

    local fullName = target .. "-" .. realm
    return Ambiguate and Ambiguate(fullName, "none") or fullName
end

NormalizeLeaderName = function(name, realm)
    local shortName, embeddedRealm = SplitLeaderName(name)
    if not shortName then
        return nil
    end

    return FormatLeaderName(shortName, realm or embeddedRealm or GetServerRealm())
end

local function IsSameLeaderName(left, right)
    local leftName, leftRealm = SplitLeaderName(left)
    local rightName, rightRealm = SplitLeaderName(right)
    if not leftName or not rightName or leftName ~= rightName then
        return false
    end

    local normalizedLeftRealm = NormalizeRealmName(leftRealm)
    local normalizedRightRealm = NormalizeRealmName(rightRealm)
    if normalizedLeftRealm and normalizedRightRealm then
        return normalizedLeftRealm == normalizedRightRealm
    end

    return true
end

local function DoesLeaderListContainLeader(leaderNames, leader)
    if type(leaderNames) ~= "string" or leaderNames == "" or type(leader) ~= "string" or leader == "" then
        return false
    end

    for _, returnedLeader in ipairs(SplitQueryNames(leaderNames)) do
        if IsSameLeaderName(leader, returnedLeader) then
            return true
        end
    end
    return false
end

local function AddSetValue(values, value)
    if type(value) == "string" and value ~= "" then
        values[value] = true
    end
end

local function AddLeaderLookupKeys(keys, name, realm)
    local shortName, embeddedRealm = SplitLeaderName(name)
    if not shortName then
        return
    end

    local resolvedRealm = TrimString(realm) or embeddedRealm or GetServerRealm()
    if not resolvedRealm then
        AddSetValue(keys, TrimString(name))
        return
    end

    AddSetValue(keys, FormatLeaderName(shortName, resolvedRealm))
    AddSetValue(keys, FormatLeaderName(shortName, NormalizeRealmName(resolvedRealm)))
    AddSetValue(keys, FormatLeaderNameWithRawRealm(shortName, resolvedRealm))
end

local function TrackLeaderAlias(self, leader)
    local shortName = SplitLeaderName(leader)
    if not shortName then
        return
    end

    self.pendingLeaderAliases = self.pendingLeaderAliases or {}
    self.pendingLeaderAliases[shortName] = self.pendingLeaderAliases[shortName] or {}
    self.pendingLeaderAliases[shortName][leader] = true
end

local function GetTrackedLeaderAliases(self, shortName)
    return self.pendingLeaderAliases and self.pendingLeaderAliases[shortName] or nil
end

local function BuildLeaderCacheKeys(self, leaderName)
    local keys = {}
    local shortName, embeddedRealm = SplitLeaderName(leaderName)
    if not shortName then
        return keys
    end

    AddSetValue(keys, TrimString(leaderName))
    AddSetValue(keys, FormatLeaderName(shortName, embeddedRealm))
    AddSetValue(keys, FormatLeaderName(shortName, NormalizeRealmName(embeddedRealm)))

    local trackedAliases = GetTrackedLeaderAliases(self, shortName)
    if trackedAliases then
        for trackedLeader in pairs(trackedAliases) do
            AddSetValue(keys, trackedLeader)
            local trackedShortName, trackedRealm = SplitLeaderName(trackedLeader)
            AddSetValue(keys, FormatLeaderName(trackedShortName, NormalizeRealmName(trackedRealm)))
        end
        self.pendingLeaderAliases[shortName] = nil
    elseif not embeddedRealm then
        AddSetValue(keys, NormalizeLeaderName(shortName))
    end

    return keys
end

local function GetNativeMeetingStoneRegimentCache()
    local nativeEnv = GetNativeMeetingStoneEnv()
    local profile = (nativeEnv and nativeEnv.Profile) or _G.Profile
    local cache = profile
        and profile.gdb
        and profile.gdb.global
        and profile.gdb.global.LocomotiveData
    return type(cache) == "table" and cache or nil
end

local function GetNativeMeetingStoneRegimentData(leader, name)
    local keys = {}
    AddLeaderLookupKeys(keys, leader)
    AddLeaderLookupKeys(keys, name)

    local cache = GetNativeMeetingStoneRegimentCache()
    if cache then
        for cacheKey in pairs(keys) do
            local data = cache[cacheKey]
            if type(data) == "table" then
                return data
            end
        end
    end

    local nativeEnv = GetNativeMeetingStoneEnv()
    local nativeUtils = nativeEnv and nativeEnv.MSUtils
    local nativeSystemUtils = nativeUtils and nativeUtils.System
    if type(nativeSystemUtils) == "table" and type(nativeSystemUtils.getRegimentData) == "function" then
        for cacheKey in pairs(keys) do
            local ok, data = pcall(nativeSystemUtils.getRegimentData, cacheKey)
            if ok and type(data) == "table" then
                return data
            end
        end
    end

    if nativeEnv and type(nativeEnv.GetRegimentData) == "function" then
        for cacheKey in pairs(keys) do
            local ok, data = pcall(nativeEnv.GetRegimentData, cacheKey)
            if ok and type(data) == "table" then
                return data
            end
        end
    end

    if type(_G.GetRegimentData) == "function" then
        for cacheKey in pairs(keys) do
            local ok, data = pcall(_G.GetRegimentData, cacheKey)
            if ok and type(data) == "table" then
                return data
            end
        end
    end

    return nil
end

local function GetPersistentAccountTypeCache(self)
    local global = self
        and self.owner
        and self.owner.db
        and self.owner.db.global
    if type(global) ~= "table" then
        return nil
    end

    if type(global.meetingStoneAccountTypeCache) ~= "table" then
        global.meetingStoneAccountTypeCache = {}
    end
    return global.meetingStoneAccountTypeCache
end

local function AttachPersistentAccountTypeCache(self)
    local persistentCache = GetPersistentAccountTypeCache(self)
    if not persistentCache or self.cache == persistentCache then
        return
    end

    if type(self.cache) == "table" then
        for key, value in pairs(self.cache) do
            if type(value) == "table" and type(persistentCache[key]) ~= "table" then
                persistentCache[key] = value
            end
        end
    end
    self.cache = persistentCache
end

local function IsAccountTypeCacheNewEnough(data, minMedalTime)
    if type(minMedalTime) ~= "number" then
        return true
    end

    local medalTime = type(data) == "table" and tonumber(data.medalTime) or nil
    return medalTime ~= nil and medalTime >= minMedalTime
end

IsAccountTypeCacheForLeader = function(data, leader, name)
    if type(data) ~= "table" then
        return false
    end

    local cachedLeader = TrimString(data.accountTypeLeaderName)
    if cachedLeader then
        return IsSameLeaderName(leader, cachedLeader) or IsSameLeaderName(name, cachedLeader)
    end

    -- 旧版本写入的本插件缓存没有所属角色名，可能已经被单人查询回包污染。
    if data.medalTime ~= nil
        and (data.source ~= nil or data.sourceFallback ~= nil or data.noAccountTypeResult ~= nil or data.medalMap ~= nil)
    then
        return false
    end

    return true
end

local function GetCachedLeaderData(self, leader, name, skipNativeCache, minMedalTime)
    if not leader then
        return nil
    end

    local cache = type(self.cache) == "table" and self.cache or nil
    if cache then
        local data = cache[leader]
        if type(data) == "table" and IsAccountTypeCacheNewEnough(data, minMedalTime) and IsAccountTypeCacheForLeader(data, leader, name) then
            return data
        elseif type(data) == "table" and not IsAccountTypeCacheForLeader(data, leader, name) then
            cache[leader] = nil
        end

        local rawName = TrimString(name)
        data = rawName and rawName:find("-", 1, true) and cache[rawName] or nil
        if type(data) == "table" and IsAccountTypeCacheNewEnough(data, minMedalTime) and IsAccountTypeCacheForLeader(data, leader, rawName) then
            return data
        elseif type(data) == "table" and not IsAccountTypeCacheForLeader(data, leader, rawName) then
            cache[rawName] = nil
        end
    end

    if skipNativeCache then
        return nil
    end

    local nativeData = GetNativeMeetingStoneRegimentData(leader, name)
    return nativeData
end

local function GetCharacterNameAndRealm(characterEntry)
    local character = characterEntry and characterEntry.data
    if type(character) ~= "table" then
        return nil, nil
    end

    local name = character.name or characterEntry.key
    local realm = character.realm
    local shortName, embeddedRealm = SplitLeaderName(name)
    return shortName, TrimString(realm) or embeddedRealm
end

local function IsCacheFresh(data)
    if not (type(data) == "table" and type(data.medalTime) == "number" and (time() - data.medalTime) < CACHE_TTL_SECONDS) then
        return false
    end

    if data.isNewbie == true then
        local expireTime = tonumber(data.newbieExpireTime)
        if not expireTime or time() >= expireTime then
            return false
        end
    end

    local _, currentSourceFallback = GetAddonSource()
    return not (data.sourceFallback == true and currentSourceFallback ~= true)
end

local function IsAccountTypeForceRefreshActive(self)
    local expiresAt = self and self.forceAccountTypeRefreshUntil
    if type(expiresAt) == "number" and GetDebugTime() < expiresAt then
        return true
    end

    if self then
        self.forceAccountTypeRefreshUntil = nil
        self.forceAccountTypeRefreshStartedAt = nil
    end
    return false
end

local function GetMinimumNativeAccountTypeMedalTime(self, queryState, forceRefresh)
    local minMedalTime = nil
    if IsAccountTypeQueryPending(queryState) or (queryState and queryState.status == "retry") then
        minMedalTime = tonumber(queryState.queuedWallTime) or tonumber(queryState.sentWallTime)
    end
    if forceRefresh then
        local forceStartedAt = tonumber(self and self.forceAccountTypeRefreshStartedAt)
        if forceStartedAt and (not minMedalTime or forceStartedAt > minMedalTime) then
            minMedalTime = forceStartedAt
        end
    end
    return minMedalTime
end

EnsureState = function(self)
    AttachPersistentAccountTypeCache(self)
    if type(self.cache) ~= "table" then
        self.cache = {}
    end
    if type(self.pendingNames) ~= "table" then
        self.pendingNames = {}
    end
    if type(self.pendingLeaderAliases) ~= "table" then
        self.pendingLeaderAliases = {}
    end
    if type(self.nativeMeetingStonePendingNames) ~= "table" then
        self.nativeMeetingStonePendingNames = {}
    end
    EnsureAccountTypeQueryStates(self)
end

local function IsLeaderAwaitingAccountTypeResponse(self, leader)
    if type(leader) ~= "string" or leader == "" then
        return false
    end
    if self.pendingNames and self.pendingNames[leader] == true then
        return true
    end

    local lastSentAt = self.lastCQGLIBSentAt
    if type(lastSentAt) ~= "number" or (GetDebugTime() - lastSentAt) >= SERVER_QUERY_RESEND_SECONDS then
        return false
    end
    if type(self.lastSQGLIBAt) == "number" and self.lastSQGLIBAt >= lastSentAt then
        return false
    end

    for _, name in ipairs(SplitQueryNames(self.lastCQGLIBNames)) do
        if name == leader then
            return true
        end
    end
    return false
end

local function PruneAccountTypeCache(self)
    local cache = type(self.cache) == "table" and self.cache or nil
    if not cache then
        return
    end

    local entries = {}
    for key, data in pairs(cache) do
        entries[#entries + 1] = {
            key = key,
            medalTime = type(data) == "table" and tonumber(data.medalTime) or 0,
        }
    end
    if #entries <= ACCOUNT_TYPE_CACHE_MAX_ENTRIES then
        return
    end

    table.sort(entries, function(left, right)
        return (left.medalTime or 0) < (right.medalTime or 0)
    end)

    for index = 1, #entries - ACCOUNT_TYPE_CACHE_MAX_ENTRIES do
        cache[entries[index].key] = nil
    end
end

local function NormalizeLeaderLevel(value)
    local level = tonumber(value)
    if not level then
        return nil
    end

    level = math.floor(level)
    if level <= 0 then
        return nil
    end

    return level
end

local function NormalizeStarLevel(value)
    local level = tonumber(value)
    if not level then
        return nil
    end

    level = math.floor(level)
    if level <= 0 then
        return nil
    end

    return level
end

local function HasKnownAccountTypeData(data)
    if type(data) ~= "table" then
        return false
    end

    if type(data.medalTime) == "number" then
        return true
    end
    if data.isNewbie ~= nil then
        return true
    end
    if data.isLeaver ~= nil then
        return true
    end
    if NormalizeLeaderLevel(data.level) then
        return true
    end
    if NormalizeStarLevel(data.starLevel) then
        return true
    end

    return false
end

local function StoreAccountTypeData(self, leaderName, data, extraKey)
    if type(self) ~= "table" or type(data) ~= "table" then
        return false
    end

    local storedData = {}
    for key, value in pairs(data) do
        storedData[key] = value
    end
    storedData.accountTypeLeaderName = NormalizeLeaderName(leaderName) or TrimString(leaderName)

    local cacheKeys = BuildLeaderCacheKeys(self, leaderName)
    AddSetValue(cacheKeys, extraKey)
    local stored = false
    for cacheKey in pairs(cacheKeys) do
        local existing = self.cache[cacheKey]
        if not IsAccountTypeCacheForLeader(existing, leaderName, leaderName) then
            existing = nil
        end
        self.cache[cacheKey] = PreserveActiveNewbieAccountTypeData(existing, storedData)
        MarkAccountTypeQueryDone(self, cacheKey)
        stored = true
    end
    return stored
end

local function ResolvePendingNativeAccountTypeData(self)
    EnsureState(self)

    local changed = false
    local resolvedNames = {}
    for leader, state in pairs(self.accountTypeQueryStates or {}) do
        if IsAccountTypeQueryPending(state) or (state and state.status == "retry") then
            local data = GetNativeMeetingStoneRegimentData(leader)
            if HasKnownAccountTypeData(data) and StoreAccountTypeData(self, leader, data) then
                resolvedNames[#resolvedNames + 1] = leader
                changed = true
            end
        end
    end
    if changed then
        self.lastSQGLIBAt = GetDebugTime()
        self.lastSQGLIBCount = #resolvedNames
        self.lastSQGLIBChanged = true
        self.lastSQGLIBLeaderNames = table.concat(resolvedNames, ",")
    end
    return changed
end

local function AddAccountTypeBadge(badges, badge)
    if type(badges) ~= "table" or type(badge) ~= "table" or not badge.key then
        return
    end

    badges[#badges + 1] = badge
end

local function ApplyPrimaryAccountTypeBadge(info, badges)
    if type(info) ~= "table" or type(badges) ~= "table" or #badges == 0 then
        return
    end

    local primary = badges[1]
    info.status = primary.key
    info.texture = primary.texture
    info.text = primary.text
    info.compactText = primary.compactText
    info.textColor = primary.textColor
    info.width = primary.width
    info.height = primary.height
end

local function GetAccountTypeDebugText(accountTypeInfo)
    if type(accountTypeInfo) ~= "table" then
        return nil
    end

    if type(accountTypeInfo.badges) == "table" then
        local parts = {}
        for _, badge in ipairs(accountTypeInfo.badges) do
            if type(badge) == "table" and type(badge.text) == "string" and badge.text ~= "" then
                parts[#parts + 1] = badge.text
            end
        end
        if #parts > 0 then
            return table.concat(parts, "+")
        end
    end

    return accountTypeInfo.text or accountTypeInfo.status
end

local function GetNativeMeetingStoneLogic()
    local bridge = addonTable.MeetingStoneBridge
    if type(bridge) == "table" and type(bridge.GetRuntime) == "function" then
        local ok, _, logic = pcall(bridge.GetRuntime)
        if ok and type(logic) == "table" then
            return logic
        end
    end

    local nativeEnv = GetNativeMeetingStoneEnv()
    if nativeEnv and type(nativeEnv.Logic) == "table" then
        return nativeEnv.Logic
    end

    local libStub = _G.LibStub
    if libStub then
        local ok, aceAddon = pcall(libStub, "AceAddon-3.0")
        if ok and aceAddon and type(aceAddon.GetAddon) == "function" then
            local addonOk, nativeAddon = pcall(aceAddon.GetAddon, aceAddon, "MeetingStone", true)
            if addonOk and type(nativeAddon) == "table" and type(nativeAddon.GetModule) == "function" then
                local moduleOk, logic = pcall(nativeAddon.GetModule, nativeAddon, "Logic", true)
                if moduleOk and type(logic) == "table" then
                    return logic
                end
            end
        end
    end

    local logic = _G.Logic
    return type(logic) == "table" and logic or nil
end

local ScheduleNativeMeetingStoneRetry

local function IsNativeMeetingStoneLogicReady(logic)
    if type(logic) ~= "table" then
        return false
    end

    if logic.IsConnectServer == true then
        return true
    end

    if type(logic.IsReady) == "function" then
        local ok, ready = pcall(logic.IsReady, logic)
        if ok and ready == true then
            return true
        end
    end

    local socketTarget = GetSocketClientServerTarget(logic)
    return IsSocketClientReady(logic) == true and socketTarget ~= nil
end

local function CollectNativePendingNames(self)
    local names = {}
    if type(self) ~= "table" or type(self.nativeMeetingStonePendingNames) ~= "table" then
        return names
    end

    for leader in pairs(self.nativeMeetingStonePendingNames) do
        names[#names + 1] = leader
    end
    table.sort(names)
    return names
end

local function MarkNativeMeetingStoneQueriesSent(self, names)
    if type(self) ~= "table" or type(names) ~= "table" or #names == 0 then
        return
    end

    local sentAt = GetDebugTime()
    self.lastCQGLIBNames = table.concat(names, ",")
    self.lastCQGLIBSentAt = sentAt
    self.lastCQGLIBSendMethod = "netease"
    self.lastCQGLIBSendSucceeded = true
    for _, leader in ipairs(names) do
        MarkAccountTypeQuerySent(self, leader, sentAt, "netease")
    end
end

local function FlushNativeMeetingStoneQueries(self)
    EnsureState(self)
    if IsMeetingStoneDataCombatPaused(self) then
        self.combatDeferred = true
        return false
    end

    local logic = GetNativeMeetingStoneLogic()
    if not logic then
        if next(self.nativeMeetingStonePendingNames) then
            ScheduleNativeMeetingStoneRetry(self)
        end
        return false
    end

    local names = CollectNativePendingNames(self)
    if #names > 0 and type(logic.InsertServerCQGLIB) == "function" then
        for _, leader in ipairs(names) do
            pcall(logic.InsertServerCQGLIB, logic, leader)
        end
    end

    if not IsNativeMeetingStoneLogicReady(logic) then
        if next(self.nativeMeetingStonePendingNames) then
            ScheduleNativeMeetingStoneRetry(self)
        end
        return false
    end

    if #names == 0 then
        if type(logic.SendServerCQGLIB) == "function" then
            pcall(logic.SendServerCQGLIB, logic)
        end
        return true
    end

    local sent = false
    if type(logic.SendServerCQGLIB) == "function" then
        local ok = pcall(logic.SendServerCQGLIB, logic)
        sent = ok == true
    elseif type(logic.SendServer) == "function" then
        local ok = pcall(logic.SendServer, logic, "CQGLIB", table.concat(names, ","))
        sent = ok == true
    end

    if sent then
        for _, leader in ipairs(names) do
            self.nativeMeetingStonePendingNames[leader] = nil
        end
        MarkNativeMeetingStoneQueriesSent(self, names)
        ResolvePendingNativeAccountTypeData(self)
        return true
    end

    if next(self.nativeMeetingStonePendingNames) then
        ScheduleNativeMeetingStoneRetry(self)
    end
    return false
end

local function ScheduleNativeMeetingStoneFlush(self)
    if IsMeetingStoneDataCombatPaused(self) then
        self.combatDeferred = true
        return
    end

    if self.nativeMeetingStoneFlushScheduled then
        return
    end

    self.nativeMeetingStoneFlushScheduled = true
    if C_Timer and C_Timer.After then
        C_Timer.After(QUERY_DEBOUNCE_SECONDS, function()
            MeetingStoneData.nativeMeetingStoneFlushScheduled = false
            FlushNativeMeetingStoneQueries(MeetingStoneData)
        end)
    else
        self.nativeMeetingStoneFlushScheduled = false
        FlushNativeMeetingStoneQueries(self)
    end
end

local function RememberNativeMeetingStonePendingName(self, leader)
    EnsureState(self)

    local isNewName = self.nativeMeetingStonePendingNames[leader] ~= true
    self.nativeMeetingStonePendingNames[leader] = true
    if isNewName then
        self.nativeMeetingStoneRetryCount = 0
    end
    if IsMeetingStoneDataCombatPaused(self) then
        self.combatDeferred = true
        return
    end
    ScheduleNativeMeetingStoneRetry(self)
end

local function QueueNativeMeetingStoneQuery(self, leader)
    EnsureState(self)
    if IsMeetingStoneDataCombatPaused(self) then
        self.combatDeferred = true
        return false
    end

    local logic = GetNativeMeetingStoneLogic()
    if not (logic and type(logic.InsertServerCQGLIB) == "function") then
        if GetNativeMeetingStoneEnv() then
            RememberNativeMeetingStonePendingName(self, leader)
        end
        return false
    end

    local ok = pcall(logic.InsertServerCQGLIB, logic, leader)
    if not ok then
        RememberNativeMeetingStonePendingName(self, leader)
        return false
    end

    self.nativeMeetingStonePendingNames[leader] = true
    self.nativeMeetingStoneRetryCount = 0
    ScheduleNativeMeetingStoneFlush(self)
    return true
end

function MeetingStoneData:FlushNativeMeetingStonePendingQueries()
    EnsureState(self)
    if IsMeetingStoneDataCombatPaused(self) then
        self.combatDeferred = true
        return false
    end

    local names = {}
    for leader in pairs(self.nativeMeetingStonePendingNames) do
        names[#names + 1] = leader
    end

    if #names == 0 then
        return true
    end

    local logic = GetNativeMeetingStoneLogic()
    if not (logic and type(logic.InsertServerCQGLIB) == "function") then
        local _, sourceFallback = GetAddonSource()
        if sourceFallback == true then
            wipe(self.nativeMeetingStonePendingNames)
            self.nativeMeetingStoneRetryCount = 0
            self.nativeMeetingStoneRetryScheduled = false
            return false
        end
        ScheduleNativeMeetingStoneRetry(self)
        return false
    end

    local queued = false
    for _, leader in ipairs(names) do
        local ok = pcall(logic.InsertServerCQGLIB, logic, leader)
        if ok then
            self.nativeMeetingStonePendingNames[leader] = nil
            queued = true
        end
    end

    if queued then
        self.nativeMeetingStoneRetryCount = 0
        ScheduleNativeMeetingStoneFlush(self)
    end

    if next(self.nativeMeetingStonePendingNames) then
        ScheduleNativeMeetingStoneRetry(self)
    end

    return queued
end

ScheduleNativeMeetingStoneRetry = function(self)
    EnsureState(self)
    if IsMeetingStoneDataCombatPaused(self) then
        self.combatDeferred = true
        return
    end

    if self.nativeMeetingStoneRetryScheduled or not next(self.nativeMeetingStonePendingNames) then
        return
    end

    local retryCount = tonumber(self.nativeMeetingStoneRetryCount) or 0
    if retryCount >= NATIVE_QUERY_RETRY_LIMIT then
        return
    end

    if not (C_Timer and C_Timer.After) then
        return
    end

    self.nativeMeetingStoneRetryScheduled = true
    C_Timer.After(NATIVE_QUERY_RETRY_SECONDS, function()
        MeetingStoneData.nativeMeetingStoneRetryScheduled = false
        MeetingStoneData.nativeMeetingStoneRetryCount = (tonumber(MeetingStoneData.nativeMeetingStoneRetryCount) or 0) + 1
        MeetingStoneData:FlushNativeMeetingStonePendingQueries()
    end)
end

local function CreateSocketClient(self)
    local socket = NetEaseSocket or LibStub("NetEaseSocket-2.0", true)
    if not socket or (IsTrialAccount and IsTrialAccount()) then
        return nil
    end

    local client = {
        owner = self,
    }

    socket:Embed(client)

    function client:SERVER_CONNECTED()
        if self.owner and self.owner.HandleSocketServerConnected then
            self.owner:HandleSocketServerConnected()
        end
    end

    function client:SERVER_DISCONNECTED()
        if self.owner and self.owner.HandleSocketServerDisconnected then
            self.owner:HandleSocketServerDisconnected()
        end
    end

    function client:SVERSION(eventName, version, url, isSupport, changeLog)
        if self.owner and self.owner.RecordServerMessage then
            self.owner:RecordServerMessage("SVERSION")
        end
        if self.owner and self.owner.HandleServerVersion then
            self.owner:HandleServerVersion(version, url, isSupport, changeLog)
        end
    end

    function client:SQTB(eventName, isDeal)
        if self.owner and self.owner.RecordServerMessage then
            self.owner:RecordServerMessage("SQTB")
        end
        if self.owner and self.owner.HandleSQTB then
            self.owner:HandleSQTB(isDeal)
        end
    end

    function client:SQGLIB(eventName, maps)
        if self.owner and self.owner.RecordServerMessage then
            self.owner:RecordServerMessage("SQGLIB")
        end
        if self.owner and self.owner.HandleSQGLIB then
            self.owner:HandleSQGLIB(type(eventName) == "table" and eventName or maps)
        end
    end

    local ok = pcall(client.ListenSocket, client, COMM_PREFIX, GetAddonServer())
    if not ok then
        return nil
    end

    client:RegisterServer("SERVER_CONNECTED")
    client:RegisterServer("SERVER_DISCONNECTED")
    client:RegisterServer("SVERSION")
    client:RegisterServer("SQTB")
    client:RegisterServer("SQGLIB")

    return client
end

function MeetingStoneData:GetSocketClient()
    if self.socketClient then
        return self.socketClient
    end

    self.socketClient = CreateSocketClient(self)
    return self.socketClient
end

function MeetingStoneData:HandleSocketServerConnected()
    self.ready = true
    self.connecting = false
    self.target = GetSocketClientTarget(self.socketClient) or self.target
    self.lastSocketConnectedAt = GetDebugTime()
    self:RecordServerMessage("连接成功", self.target, "WHISPER")
    if self:IsCombatPaused() then
        self.loginDeferred = true
        self.combatDeferred = true
        return
    end

    self:SendLogin()
    self:ScheduleFlush(SERVER_LOGIN_QUERY_DELAY_SECONDS)
end

function MeetingStoneData:HandleSocketServerDisconnected()
    self.ready = false
    self.connecting = false
    self.lastSocketDisconnectedAt = GetDebugTime()

    EnsureState(self)
    if next(self.pendingNames) then
        if self:IsCombatPaused() then
            self.combatDeferred = true
        else
            self:Connect()
        end
    end
end

function MeetingStoneData:Initialize(owner)
    self.owner = owner
    EnsureState(self)

    if self.initialized then
        return
    end

    self.initialized = true
    self.connectKey = tostring((fastrandom or random)(0x100000, 0xFFFFFF))
    self:GetSocketClient()

    if owner and owner.RegisterComm then
        owner:RegisterComm(COMM_PREFIX, function(prefix, message, distribution, sender)
            MeetingStoneData:OnCommReceived(prefix, message, distribution, sender)
        end)
    end

    if owner and owner.RegisterMessage then
        owner:RegisterMessage("MEETINGSTONE_SQGLIB_UPDATED", function()
            ResolvePendingNativeAccountTypeData(MeetingStoneData)
            if MeetingStoneData.owner
                and MeetingStoneData.owner.IsMainWindowShown
                and MeetingStoneData.owner:IsMainWindowShown()
                and MeetingStoneData.owner.RefreshMainWindow
            then
                MeetingStoneData.owner:RefreshMainWindow("MeetingStoneData", "MEETINGSTONE_SQGLIB_UPDATED")
            end
        end)
    end

    if not self.nativeMeetingStoneEventFrame and CreateFrame then
        self.nativeMeetingStoneEventFrame = CreateFrame("Frame")
        self.nativeMeetingStoneEventFrame:RegisterEvent("ADDON_LOADED")
        self.nativeMeetingStoneEventFrame:SetScript("OnEvent", function(_, _, loadedAddonName)
            if loadedAddonName == "MeetingStone" or loadedAddonName == "MeetingStoneEX" then
                if C_Timer and C_Timer.After then
                    C_Timer.After(0, function()
                        MeetingStoneData:FlushNativeMeetingStonePendingQueries()
                    end)
                else
                    MeetingStoneData:FlushNativeMeetingStonePendingQueries()
                end
            end
        end)
    end
end

function MeetingStoneData:Start()
    EnsureState(self)
    if self:IsCombatPaused() then
        self.combatDeferred = true
        self:QueueCombatDeferredQueries()
        return
    end

    self:Connect()
    self:FlushNativeMeetingStonePendingQueries()
end

function MeetingStoneData:IsCombatPaused()
    return IsMeetingStoneDataCombatPaused(self)
end

function MeetingStoneData:QueueCombatDeferredQueries()
    EnsureState(self)
    for _, leader in ipairs(SplitQueryNames(self.lastCQGLIBNames)) do
        local queryState = GetAccountTypeQueryState(self, leader)
        if IsAccountTypeQueryPending(queryState) then
            self.pendingNames[leader] = true
        end
    end
end

function MeetingStoneData:SetCombatPaused(paused)
    EnsureState(self)

    local wasPaused = self.combatPaused == true
    self.combatPaused = paused == true

    if IsMeetingStoneDataCombatPaused(self) then
        self.combatDeferred = true
        self:QueueCombatDeferredQueries()
        return
    end

    if not wasPaused and not self.combatDeferred then
        return
    end

    self.combatDeferred = nil
    self.connecting = false

    if self.loginDeferred == true and self.ready == true then
        self.loginDeferred = nil
        self:SendLogin()
        self:ScheduleFlush(SERVER_LOGIN_QUERY_DELAY_SECONDS)
    else
        self.loginDeferred = nil
    end

    self:FlushNativeMeetingStonePendingQueries()

    if next(self.pendingNames) or not self.ready then
        self:Connect()
    end
    if next(self.pendingNames) then
        self:ScheduleFlush()
    end

    ScheduleAccountTypeQueryStateRefresh(self)
end

function MeetingStoneData:GetTarget()
    if self.target and self.target ~= "" then
        return self.target
    end
    self.connectTarget = self.connectTarget or FormatTarget(GetAddonServer())
    return self.connectTarget
end

function MeetingStoneData:Send(target, cmd, ...)
    if not self.owner or not self.owner.SendCommMessage or type(target) ~= "string" or target == "" then
        return false
    end

    local ok = pcall(self.owner.SendCommMessage, self.owner, COMM_PREFIX, AceSerializer:Serialize(cmd, ...), "WHISPER", target)
    return ok == true
end

function MeetingStoneData:RecordServerSend(cmd, sent, method, target)
    self.lastServerSendAt = GetDebugTime()
    self.lastServerSendCmd = cmd
    self.lastServerSendSucceeded = sent == true
    self.lastServerSendMethod = method
    self.lastServerSendTarget = target or self.target
end

function MeetingStoneData:RecordServerMessage(cmd, sender, distribution)
    self.lastServerMessageAt = GetDebugTime()
    self.lastServerMessageCmd = cmd
    self.lastServerMessageSender = sender or self.lastServerMessageSender
    self.lastServerMessageDistribution = distribution or self.lastServerMessageDistribution
end

function MeetingStoneData:SendServer(cmd, ...)
    if self:IsCombatPaused() then
        self.combatDeferred = true
        self:RecordServerSend(cmd, false, "combat_paused", self.target)
        return false, "combat_paused"
    end

    local socketClient = self:GetSocketClient()
    local socketTarget = GetSocketClientServerTarget(socketClient)
    if IsSocketClientReady(socketClient) and socketTarget then
        self.target = socketTarget
        local ok = socketClient.SendServer and pcall(socketClient.SendServer, socketClient, cmd, ...)
        local sent = ok == true
        self:RecordServerSend(cmd, sent, "socket", socketTarget)
        return sent, "socket"
    end

    if not self.ready or not self.target then
        self:RecordServerSend(cmd, false, "not_ready", self.target)
        return false, "not_ready"
    end

    local sent = self:Send(self.target, cmd, ...)
    self:RecordServerSend(cmd, sent, "acecomm", self.target)
    return sent, "acecomm"
end

function MeetingStoneData:Connect()
    EnsureState(self)

    if self:IsCombatPaused() then
        self.combatDeferred = true
        return
    end

    if self.ready or self.connecting then
        return
    end

    local socketClient = self:GetSocketClient()
    if socketClient and socketClient.ConnectServer then
        local socketTarget = GetSocketClientServerTarget(socketClient)
        if IsSocketClientReady(socketClient) and socketTarget then
            self.ready = true
            self.connecting = false
            self.target = socketTarget
            self:SendLogin()
            self:ScheduleFlush(SERVER_LOGIN_QUERY_DELAY_SECONDS)
            return
        end

        self.connecting = true
        local ok = pcall(socketClient.ConnectServer, socketClient, GetAddonServer())
        if not ok then
            self.connecting = false
            return
        end

        C_Timer.After(CONNECT_RETRY_SECONDS, function()
            if MeetingStoneData.ready then
                return
            end

            local delayedSocketTarget = GetSocketClientServerTarget(MeetingStoneData.socketClient)
            if IsSocketClientReady(MeetingStoneData.socketClient) and delayedSocketTarget then
                MeetingStoneData.ready = true
                MeetingStoneData.connecting = false
                MeetingStoneData.target = delayedSocketTarget
                if MeetingStoneData:IsCombatPaused() then
                    MeetingStoneData.loginDeferred = true
                    MeetingStoneData.combatDeferred = true
                    return
                end

                MeetingStoneData:SendLogin()
                MeetingStoneData:ScheduleFlush(SERVER_LOGIN_QUERY_DELAY_SECONDS)
                return
            end

            MeetingStoneData.connecting = false
            EnsureState(MeetingStoneData)
            if next(MeetingStoneData.pendingNames) then
                MeetingStoneData:Connect()
            end
        end)
        return
    end

    local target = self:GetTarget()
    if not target then
        return
    end

    self.connecting = true
    self:Send(target, "NETEASE_CONNECT", self.connectKey)

    C_Timer.After(CONNECT_RETRY_SECONDS, function()
        if MeetingStoneData.ready then
            return
        end

        MeetingStoneData.connecting = false
        EnsureState(MeetingStoneData)
        if next(MeetingStoneData.pendingNames) then
            MeetingStoneData:Connect()
        end
    end)
end

function MeetingStoneData:SendLogin()
    local battleTag = nil
    if BNGetInfo then
        battleTag = select(2, BNGetInfo())
    end

    local version = GetMeetingStoneProtocolVersion()
    local source, sourceFallback = GetAddonSource()
    local queryData = GetServerLoginQueryData()
    self.lastSLOGINAt = GetDebugTime()
    self.lastSLOGINVersion = version
    self.lastSLOGINSource = source
    self.lastSLOGINSourceFallback = sourceFallback == true
    self.lastSLOGINQueryDataType = type(queryData)
    self.lastSLOGINQueryDataCount = CountTableValues(queryData)
    self.lastSLOGINQueryDataKeys = FormatTableKeys(queryData, 8)
    local sent, method = self:SendServer("SLOGIN", version, UnitGUID and UnitGUID("player") or nil, source, battleTag, queryData)
    self.lastSLOGINSendSucceeded = sent == true
    self.lastSLOGINSendMethod = method
    self.lastSLOGINSendTarget = self.lastServerSendTarget
    local cqtbSent, cqtbMethod = self:SendServer("CQTB")
    self.lastCQTBQuerySendSucceeded = cqtbSent == true
    self.lastCQTBQuerySendMethod = cqtbMethod
end

function MeetingStoneData:HandleServerVersion(version, url, isSupport, changeLog)
    self.lastSVERSIONAt = GetDebugTime()
    self.lastSVERSIONVersion = version
    self.lastSVERSIONUrl = url
    self.lastSVERSIONSupported = isSupport == true
    self.lastSVERSIONChangeLog = changeLog
end

function MeetingStoneData:HandleSQTB(isDeal)
    self.lastSQTBAt = GetDebugTime()
    self.lastSQTBIsDeal = isDeal == true
end

function MeetingStoneData:OnCommReceived(prefix, message, distribution, sender)
    if prefix ~= COMM_PREFIX or type(message) ~= "string" then
        return
    end

    local ok, cmd, arg1, arg2, arg3, arg4 = AceSerializer:Deserialize(message)
    if not ok then
        return
    end

    self:RecordServerMessage(cmd, sender, distribution)

    if cmd == "NETEASE_CONNECT_SUCCESS" then
        if arg1 ~= self.connectKey then
            return
        end

        self.target = sender
        self.ready = true
        self.connecting = false
        if self:IsCombatPaused() then
            self.loginDeferred = true
            self.combatDeferred = true
            return
        end

        self:SendLogin()
        self:ScheduleFlush(SERVER_LOGIN_QUERY_DELAY_SECONDS)
        return
    end

    if cmd == "SVERSION" then
        self:HandleServerVersion(arg1, arg2, arg3, arg4)
        return
    end

    if cmd == "SQTB" then
        self:HandleSQTB(arg1)
        return
    end

    if cmd == "SQGLIB" then
        local maps = type(arg1) == "table" and arg1 or arg2
        self:HandleSQGLIB(maps)
    end
end

function MeetingStoneData:HandleSQGLIB(maps)
    if type(maps) ~= "table" then
        EnsureState(self)
        self.lastSQGLIBInvalidAt = GetDebugTime()
        self.lastSQGLIBInvalidType = type(maps)
        for _, queryName in ipairs(SplitQueryNames(self.lastCQGLIBNames)) do
            MarkAccountTypeQueryRetry(self, queryName, "invalid_response")
        end
        if self.owner and self.owner.IsMainWindowShown and self.owner:IsMainWindowShown() and self.owner.RefreshMainWindow then
            self.owner:RefreshMainWindow("MeetingStoneData", "SQGLIBInvalid")
        end
        return
    end

    EnsureState(self)

    local changed = false
    local queryStateChanged = false
    local receivedCount = 0
    local receivedLeaderNames = {}
    local lastQueryNames = SplitQueryNames(self.lastCQGLIBNames)
    local singleQueryName = #lastQueryNames == 1 and lastQueryNames[1] or nil
    local resolvedQueryNames = {}
    local mismatchedQueryNames = {}
    local mismatchedQueryNameSet = {}
    for leaderName, data in pairs(maps) do
        receivedCount = receivedCount + 1
        if type(leaderName) == "string" and type(data) == "table" then
            receivedLeaderNames[#receivedLeaderNames + 1] = leaderName
            local newbieFlag = data.n or data["n"] or 0
            local remainCount = data.r or data["r"] or 0
            local cacheKeys = BuildLeaderCacheKeys(self, leaderName)
            if singleQueryName then
                if IsSameLeaderName(singleQueryName, leaderName) then
                    AddSetValue(cacheKeys, singleQueryName)
                else
                    mismatchedQueryNames[#mismatchedQueryNames + 1] = singleQueryName .. "->" .. leaderName
                    mismatchedQueryNameSet[singleQueryName] = true
                end
            end
            for cacheKey in pairs(cacheKeys) do
                self.pendingNames[cacheKey] = nil
                self.nativeMeetingStonePendingNames[cacheKey] = nil
            end
            local regimentData = nil
            for cacheKey in pairs(cacheKeys) do
                if type(self.cache[cacheKey]) == "table" and IsAccountTypeCacheForLeader(self.cache[cacheKey], leaderName, leaderName) then
                    regimentData = self.cache[cacheKey]
                    break
                end
            end
            regimentData = regimentData or {}
            local keepExistingNewbie = IsActiveNewbieAccountTypeData(regimentData) and newbieFlag ~= 1

            regimentData.level = data.l or data["l"]
            regimentData.starLevel = data.s or data["s"]
            regimentData.roomID = data.c or data["c"]
            regimentData.bgID = data.b or data["b"] or 0
            regimentData.source = self.lastSLOGINSource
            regimentData.sourceFallback = self.lastSLOGINSourceFallback == true
            regimentData.medalMap = {
                acc_exp = data.ae,
                next_star_level_threshold = data.ne,
                medal = data.m,
            }
            regimentData.medalTime = time()
            regimentData.accountTypeLeaderName = NormalizeLeaderName(leaderName) or leaderName
            if newbieFlag == 1 then
                regimentData.isNewbie = true
                regimentData.newbieExpireTime = time() + remainCount * 30 * 60
            elseif keepExistingNewbie then
                regimentData.isNewbie = true
                regimentData.newbiePreservedAt = time()
            else
                regimentData.isNewbie = false
                regimentData.newbieExpireTime = nil
            end

            for cacheKey in pairs(cacheKeys) do
                self.cache[cacheKey] = regimentData
                resolvedQueryNames[cacheKey] = true
                MarkAccountTypeQueryDone(self, cacheKey)
                queryStateChanged = true
                changed = true
            end
        end
    end

    for _, queryName in ipairs(lastQueryNames) do
        if not resolvedQueryNames[queryName] then
            MarkAccountTypeQueryRetry(self, queryName, mismatchedQueryNameSet[queryName] and "mismatched_response" or "missing_response")
            queryStateChanged = true
        end
    end

    self.lastSQGLIBAt = GetDebugTime()
    self.lastSQGLIBCount = receivedCount
    self.lastSQGLIBChanged = changed
    self.lastSQGLIBLeaderNames = table.concat(receivedLeaderNames, ",")
    self.lastSQGLIBMismatchedNames = #mismatchedQueryNames > 0 and table.concat(mismatchedQueryNames, ",") or nil
    PruneAccountTypeCache(self)

    if (changed or queryStateChanged) and self.owner and self.owner.IsMainWindowShown and self.owner:IsMainWindowShown() and self.owner.RefreshMainWindow then
        self.owner:RefreshMainWindow("MeetingStoneData", "SQGLIB")
    end
end

function MeetingStoneData:ScheduleFlush(delaySeconds)
    EnsureState(self)
    if self:IsCombatPaused() then
        self.combatDeferred = true
        return
    end

    if self.flushScheduled then
        return
    end

    delaySeconds = tonumber(delaySeconds) or QUERY_DEBOUNCE_SECONDS
    if delaySeconds < QUERY_DEBOUNCE_SECONDS then
        delaySeconds = QUERY_DEBOUNCE_SECONDS
    end

    self.flushScheduled = true
    C_Timer.After(delaySeconds, function()
        MeetingStoneData.flushScheduled = false
        if MeetingStoneData:IsCombatPaused() then
            MeetingStoneData.combatDeferred = true
            return
        end

        MeetingStoneData:FlushQueries()
    end)
end

function MeetingStoneData:QueryLeader(name, realm)
    EnsureState(self)

    local leader = NormalizeLeaderName(name, realm)
    if not leader then
        return
    end

    local forceRefresh = IsAccountTypeForceRefreshActive(self)
    local queryState = GetAccountTypeQueryState(self, leader)
    local minNativeMedalTime = GetMinimumNativeAccountTypeMedalTime(self, queryState, forceRefresh)
    local cached = GetCachedLeaderData(self, leader, name, forceRefresh and not minNativeMedalTime, minNativeMedalTime)
    if not forceRefresh and IsCacheFresh(cached) then
        return
    end

    TrackLeaderAlias(self, leader)
    if self:IsCombatPaused() then
        MarkAccountTypeQueryPending(self, leader)
        self.pendingNames[leader] = true
        self.combatDeferred = true
        return
    end

    if IsLeaderAwaitingAccountTypeResponse(self, leader) then
        MarkAccountTypeQueryPending(self, leader)
        return
    end
    MarkAccountTypeQueryPending(self, leader)

    if QueueNativeMeetingStoneQuery(self, leader) then
        self.lastCQGLIBNames = leader
        self.lastCQGLIBSentAt = nil
        self.lastCQGLIBSendMethod = "netease"
        return
    end

    self.pendingNames[leader] = true
    if self.ready then
        self:ScheduleFlush()
    else
        self:Connect()
    end
end

function MeetingStoneData:QueryCharacters(characterEntries)
    if type(characterEntries) ~= "table" then
        return
    end

    for _, characterEntry in ipairs(characterEntries) do
        local name, realm = GetCharacterNameAndRealm(characterEntry)
        self:QueryLeader(name, realm)
    end
end

function MeetingStoneData:FlushQueries()
    EnsureState(self)

    if self:IsCombatPaused() then
        self.combatDeferred = true
        return
    end

    if not self.ready then
        self:Connect()
        return
    end

    local names = {}
    for name in pairs(self.pendingNames) do
        names[#names + 1] = name
    end

    if #names == 0 then
        return
    end

    local loginDelayRemaining = GetServerLoginQueryDelayRemaining(self)
    if loginDelayRemaining > 0 then
        self:ScheduleFlush(loginDelayRemaining)
        return
    end

    table.sort(names)
    local queryNames = table.concat(names, ",")
    local sent, method = self:SendServer("CQGLIB", queryNames)
    self.lastCQGLIBNames = queryNames
    self.lastCQGLIBSentAt = GetDebugTime()
    self.lastCQGLIBSendMethod = method
    self.lastCQGLIBSendSucceeded = sent == true
    if sent then
        for _, name in ipairs(names) do
            MarkAccountTypeQuerySent(self, name, self.lastCQGLIBSentAt, method)
        end
        wipe(self.pendingNames)
    end
end

function MeetingStoneData:ClearAccountTypeCache()
    EnsureState(self)

    local clearedCount = CountTableValues(self.cache)
    if type(self.cache) == "table" then
        wipe(self.cache)
    else
        self.cache = {}
    end

    if type(self.pendingNames) == "table" then
        wipe(self.pendingNames)
    end
    if type(self.pendingLeaderAliases) == "table" then
        wipe(self.pendingLeaderAliases)
    end
    if type(self.nativeMeetingStonePendingNames) == "table" then
        wipe(self.nativeMeetingStonePendingNames)
    end
    if type(self.accountTypeQueryStates) == "table" then
        wipe(self.accountTypeQueryStates)
    end

    self.lastCQGLIBNames = nil
    self.lastCQGLIBSentAt = nil
    self.lastCQGLIBSendMethod = nil
    self.lastCQGLIBSendSucceeded = nil
    self.lastSQGLIBAt = nil
    self.lastSQGLIBCount = nil
    self.lastSQGLIBChanged = nil
    self.lastSQGLIBLeaderNames = nil
    self.lastSQGLIBMismatchedNames = nil
    self.forceAccountTypeRefreshUntil = GetDebugTime() + ACCOUNT_TYPE_FORCE_REFRESH_SECONDS
    self.forceAccountTypeRefreshStartedAt = time()

    return clearedCount
end

function MeetingStoneData:GetAccountTypeInfo(name, realm)
    EnsureState(self)

    local leader = NormalizeLeaderName(name, realm)
    local queryState = GetAccountTypeQueryState(self, leader)
    local forceRefresh = IsAccountTypeForceRefreshActive(self)
    local minNativeMedalTime = GetMinimumNativeAccountTypeMedalTime(self, queryState, forceRefresh)
    local data = GetCachedLeaderData(self, leader, name, forceRefresh and not minNativeMedalTime, minNativeMedalTime)
    local hasKnownData = HasKnownAccountTypeData(data)
    local isQueryPending = IsAccountTypeQueryPending(queryState) or (queryState and queryState.status == "retry")
    if hasKnownData and leader then
        MarkAccountTypeQueryDone(self, leader)
    end
    data = hasKnownData and data or {}
    local now = time()
    local newbieExpireTime = tonumber(data.newbieExpireTime)
    local newbieExpired = data.isNewbie == true and (not newbieExpireTime or now >= newbieExpireTime)
    local isNewbie = data.isNewbie == true and newbieExpireTime ~= nil and now < newbieExpireTime
    local leaderLevel = NormalizeLeaderLevel(data.level)
    local starLevel = NormalizeStarLevel(data.starLevel)
    local badges = {}
    local info = {
        level = leaderLevel,
        starLevel = starLevel,
        isNewbie = isNewbie,
        newbieExpired = newbieExpired == true,
        queryPending = isQueryPending == true,
        badges = badges,
    }

    if not leader then
        info.status = "unknown"
        info.text = ""
        info.compactText = ""
        info.textColor = ACCOUNT_TYPE_PENDING_TEXT_COLOR
    elseif not hasKnownData and isQueryPending then
        info.status = "pending"
        info.text = "···"
        info.compactText = "···"
        info.textColor = ACCOUNT_TYPE_PENDING_TEXT_COLOR
    elseif data.isLeaver == true then
        AddAccountTypeBadge(badges, {
            key = "leaver",
            texture = LEAVER_ICON_TEXTURE,
            text = "逃兵",
            compactText = "逃兵",
            textColor = ACCOUNT_TYPE_RED_TEXT_COLOR,
            width = ACCOUNT_TYPE_ICON_SIZE,
            height = ACCOUNT_TYPE_ICON_SIZE,
        })
        ApplyPrimaryAccountTypeBadge(info, badges)
    else
        if isNewbie then
            AddAccountTypeBadge(badges, {
                key = "newbie",
                texture = NEWBIE_ICON_TEXTURE,
                text = "大秘新兵",
                compactText = "新兵",
                textColor = ACCOUNT_TYPE_GREEN_TEXT_COLOR,
                width = ACCOUNT_TYPE_ICON_SIZE,
                height = ACCOUNT_TYPE_ICON_SIZE,
            })
        elseif leaderLevel then
            AddAccountTypeBadge(badges, {
                key = "leader",
                texture = LEADER_ICON_TEXTURE,
                text = "火车头 Lv" .. tostring(leaderLevel),
                compactText = "Lv" .. tostring(leaderLevel),
                textColor = ACCOUNT_TYPE_GOLD_TEXT_COLOR,
                width = ACCOUNT_TYPE_ICON_SIZE,
                height = ACCOUNT_TYPE_ICON_SIZE,
                level = leaderLevel,
            })
        elseif starLevel then
            AddAccountTypeBadge(badges, {
                key = "star",
                texture = STAR_ICON_TEXTURE,
                text = "星团长 Lv" .. tostring(starLevel),
                compactText = "Lv" .. tostring(starLevel),
                textColor = ACCOUNT_TYPE_GOLD_TEXT_COLOR,
                width = ACCOUNT_TYPE_ICON_SIZE,
                height = ACCOUNT_TYPE_ICON_SIZE,
                level = starLevel,
            })
        else
            AddAccountTypeBadge(badges, {
                key = "veteran",
                texture = VETERAN_ICON_TEXTURE,
                text = "老兵",
                compactText = "老兵",
                textColor = ACCOUNT_TYPE_GOLD_TEXT_COLOR,
                width = ACCOUNT_TYPE_ICON_SIZE,
                height = ACCOUNT_TYPE_ICON_SIZE,
            })
        end
        ApplyPrimaryAccountTypeBadge(info, badges)
    end

    info.width = ACCOUNT_TYPE_ICON_SIZE
    info.height = ACCOUNT_TYPE_ICON_SIZE
    return info
end

function MeetingStoneData:DebugQueryLeader(name, realm)
    EnsureState(self)

    local leader = NormalizeLeaderName(name, realm)
    if leader then
        TrackLeaderAlias(self, leader)
        local now = GetDebugTime()
        local lastSentAt = self.lastCQGLIBSentAt
        local lastNames = self.lastCQGLIBNames
        local hasCurrentSQGLIB = type(self.lastSQGLIBAt) == "number"
            and type(lastSentAt) == "number"
            and self.lastSQGLIBAt >= lastSentAt
        local recentlySentSameQuery = lastNames == leader
            and type(lastSentAt) == "number"
            and (now - lastSentAt) < SERVER_QUERY_RESEND_SECONDS
            and not hasCurrentSQGLIB

        if not recentlySentSameQuery then
            self:QueryLeader(name, realm)
        end
    end

    return self:GetDebugInfo(name, realm)
end

function MeetingStoneData:GetDebugInfo(name, realm)
    EnsureState(self)

    local leader = NormalizeLeaderName(name, realm)
    local queryState = GetAccountTypeQueryState(self, leader)
    local forceRefresh = IsAccountTypeForceRefreshActive(self)
    local minNativeMedalTime = GetMinimumNativeAccountTypeMedalTime(self, queryState, forceRefresh)
    local data = GetCachedLeaderData(self, leader, name, forceRefresh and not minNativeMedalTime, minNativeMedalTime)
    local accountTypeInfo = self:GetAccountTypeInfo(name, realm)
    queryState = GetAccountTypeQueryState(self, leader)
    local socketClient = self:GetSocketClient()
    local socketTarget = GetSocketClientTarget(socketClient)

    return {
        leader = leader,
        socketAvailable = socketClient ~= nil,
        socketReady = IsSocketClientReady(socketClient) == true,
        ready = self.ready == true,
        connecting = self.connecting == true,
        target = self.target or socketTarget or self.connectTarget,
        pending = (leader and self.pendingNames[leader] == true) or IsAccountTypeQueryPending(queryState),
        queryStatus = queryState and queryState.status,
        queryFailureReason = queryState and queryState.failureReason,
        pendingCount = CountTableValues(self.pendingNames),
        nativePendingCount = CountTableValues(self.nativeMeetingStonePendingNames),
        lastSocketConnectedAt = self.lastSocketConnectedAt,
        lastSocketDisconnectedAt = self.lastSocketDisconnectedAt,
        lastServerSendAt = self.lastServerSendAt,
        lastServerSendCmd = self.lastServerSendCmd,
        lastServerSendSucceeded = self.lastServerSendSucceeded == true,
        lastServerSendMethod = self.lastServerSendMethod,
        lastServerSendTarget = self.lastServerSendTarget,
        lastServerMessageAt = self.lastServerMessageAt,
        lastServerMessageCmd = self.lastServerMessageCmd,
        lastServerMessageSender = self.lastServerMessageSender,
        lastServerMessageDistribution = self.lastServerMessageDistribution,
        lastSLOGINAt = self.lastSLOGINAt,
        lastSLOGINVersion = self.lastSLOGINVersion,
        lastSLOGINSource = self.lastSLOGINSource,
        lastSLOGINSourceFallback = self.lastSLOGINSourceFallback == true,
        lastSLOGINQueryDataType = self.lastSLOGINQueryDataType,
        lastSLOGINQueryDataCount = self.lastSLOGINQueryDataCount,
        lastSLOGINQueryDataKeys = self.lastSLOGINQueryDataKeys,
        lastSLOGINSendSucceeded = self.lastSLOGINSendSucceeded == true,
        lastSLOGINSendMethod = self.lastSLOGINSendMethod,
        lastSLOGINSendTarget = self.lastSLOGINSendTarget,
        lastCQTBQuerySendSucceeded = self.lastCQTBQuerySendSucceeded == true,
        lastCQTBQuerySendMethod = self.lastCQTBQuerySendMethod,
        lastSVERSIONAt = self.lastSVERSIONAt,
        lastSVERSIONVersion = self.lastSVERSIONVersion,
        lastSVERSIONSupported = self.lastSVERSIONSupported,
        lastSQTBAt = self.lastSQTBAt,
        lastSQTBIsDeal = self.lastSQTBIsDeal,
        lastCQGLIBSentAt = self.lastCQGLIBSentAt,
        lastCQGLIBNames = self.lastCQGLIBNames,
        lastCQGLIBSendMethod = self.lastCQGLIBSendMethod,
        lastCQGLIBSendSucceeded = self.lastCQGLIBSendSucceeded == true,
        lastSQGLIBAt = self.lastSQGLIBAt,
        lastSQGLIBCount = self.lastSQGLIBCount,
        lastSQGLIBChanged = self.lastSQGLIBChanged == true,
        lastSQGLIBLeaderNames = self.lastSQGLIBLeaderNames,
        lastSQGLIBMismatchedNames = self.lastSQGLIBMismatchedNames,
        lastSQGLIBMatchesQuery = DoesLeaderListContainLeader(self.lastSQGLIBLeaderNames, leader),
        lastSQGLIBInvalidAt = self.lastSQGLIBInvalidAt,
        lastSQGLIBInvalidType = self.lastSQGLIBInvalidType,
        cached = type(data) == "table",
        cacheFresh = IsCacheFresh(data) == true,
        cachedLeaderName = data and data.accountTypeLeaderName,
        level = data and data.level,
        starLevel = data and data.starLevel,
        cacheSource = data and data.source,
        cacheSourceFallback = data and data.sourceFallback == true,
        isNewbie = data and data.isNewbie == true,
        accountStatus = GetAccountTypeDebugText(accountTypeInfo),
    }
end
