--[[## Version: 12.1.04 ## Author: Updated for Midnight Pre-Patch (12.0) by Osiris the Kiwi / Discord: osirisnz / Original authors: mkp_tv / Hayato2846 / Thainii
	FriendGroups - FULL ORIGINAL CODE + SECURE ARCHITECTURE
    - Uses the exact original logic for groups, menus, and sorting.
    - Adds "Lazy Loading" to prevent VisitHouse() taint.
    - Adds Safety Shield to the House List.
]] --

local FGL = {}

-- [[ GUARD CLAUSE: STOP IF NOT CN ]] --
if GetLocale() == "zhCN" then
-- ============================================================================
-- [[ SETTINGS MENU HEADERS ]]
-- ============================================================================
FGL["SETTINGS_SIZE"]       = "列表大小"
FGL["SETTINGS_FILTER"]     = "过滤器"
FGL["SETTINGS_APPEARANCE"] = "外观"
FGL["SETTINGS_BEHAVIOR"]   = "行为"
FGL["SETTINGS_AUTOMATION"] = "自动化"
FGL["SETTINGS_RESET"]      = "|cffff0000重置为默认|r"

-- ============================================================================
-- [[ SETTINGS: SIZE ]]
-- ============================================================================
FGL["SET_SIZE_SMALL"]      = "小 (默认)"
FGL["SET_SIZE_MEDIUM"]     = "中"
FGL["SET_SIZE_LARGE"]      = "大"

-- ============================================================================
-- [[ SETTINGS: FILTERS ]]
-- ============================================================================
FGL["SET_HIDE_OFFLINE"]    = "隐藏离线"
FGL["SET_HIDE_AFK"]        = "隐藏暂离 (AFK)"
FGL["SET_MOBILE_AFK"]      = "将移动端标记为暂离"
FGL["SET_HIDE_EMPTY"]      = "隐藏空分组"
FGL["SET_INGAME_ONLY"]     = "仅显示游戏内好友"
FGL["SET_RETAIL_ONLY"]     = "仅显示正式服好友"

-- ============================================================================
-- [[ SETTINGS: APPEARANCE ]]
-- ============================================================================
FGL["SET_SHOW_FLAGS"]      = "显示服务器旗帜"
FGL["SET_SHOW_REALM"]      = "显示服务器名称"
FGL["SET_CLASS_COLOR"]     = "使用职业颜色"
FGL["SET_FACTION_ICONS"]   = "显示阵营图标"
FGL["SET_GRAY_FACTION"]    = "灰显对立阵营"
FGL["SET_SHOW_BTAG"]       = "仅显示战网昵称"
FGL["SET_HIDE_MAX_LEVEL"]  = "隐藏满级等级"

-- ============================================================================
-- [[ SETTINGS: BEHAVIOR ]]
-- ============================================================================
FGL["SET_FAV_GROUP"]       = "启用特别关注分组"
FGL["SET_COLLAPSE"]        = "自动折叠分组"

-- ============================================================================
-- [[ SETTINGS: AUTOMATION ]]
-- ============================================================================
FGL["SET_AUTO_ACCEPT"]     = "自动接受组队邀请"
FGL["SET_AUTO_PARTY_SYNC"] = "自动接受小队同步"
FGL["MSG_AUTO_INVITE"]     = "|cFF33FF99FriendGroups|r: %s 邀请你加入队伍。自动接受 |cff00ff00已启用|r"
FGL["MSG_AUTO_SYNC"]       = "|cFF33FF99FriendGroups|r: %s 邀请你进行小队同步。自动接受 |cff00ff00已启用|r"
FGL["MSG_AUTO_ACCEPT_FAILED"] = "|cFF33FF99FriendGroups|r: 暴雪在此区域阻止了自动接受。请手动点击按钮。"

-- Spirit Behavior Sub-Menu
FGL["SET_SPIRIT_HEADER"]   = "灵魂行为"
FGL["SET_SPIRIT_NONE"]     = "无"
FGL["SET_SPIRIT_RES"]      = "自动接受复活"
FGL["SET_SPIRIT_RELEASE"]  = "自动释放灵魂"

FGL["MSG_AUTO_RES"]        = "|cFF33FF99FriendGroups|r: %s 正在复活你。自动接受 |cff00ff00已启用|r"
FGL["MSG_AUTO_RELEASE"]    = "|cFF33FF99FriendGroups|r: 你已经死亡。自动释放灵魂 |cff00ff00已启用|r"
FGL["MSG_AUTO_RELEASE_FAILED"] = "|cFF33FF99FriendGroups|r: 暴雪在此区域阻止了自动释放灵魂。请点击释放按钮。"

-- ============================================================================
-- [[ CONTEXT MENUS ]]
-- ============================================================================
-- Group Header Right-Click
FGL["MENU_RENAME"]         = "重命名分组"
FGL["MENU_REMOVE"]         = "删除分组"
FGL["MENU_INVITE"]         = "邀请分组"
FGL["MENU_MAX_40"]         = " (最多 40)"

-- Friend Button Right-Click
FGL["DROP_TITLE"]          = "[好友分组]"
FGL["DROP_COPY_NAME"]      = "复制 名字-服务器"
FGL["DROP_COPY_BTAG"]      = "复制 战网昵称"
FGL["DROP_CREATE"]         = "新建分组"
FGL["DROP_ADD"]            = "添加到分组"
FGL["DROP_REMOVE"]         = "移出分组"
FGL["DROP_CANCEL"]         = "取消"

-- ============================================================================
-- [[ POPUPS & SYSTEM ]]
-- ============================================================================
FGL["POPUP_ENTER_NAME"]    = "输入新分组名称"
FGL["POPUP_COPY"]          = "按 Ctrl+C 复制:"

FGL["SEARCH_PLACEHOLDER"]  = "好友分组 搜索"
FGL["SEARCH_TOOLTIP"]      = "好友分组: 搜索任何人！名字、服务器、职业甚至备注"

FGL["MSG_WELCOME"]         = ""
FGL["MSG_RESET"]           = "|cFF33FF99[好友分组]|r: 设置已重置为默认。"
FGL["MSG_BUG_WARNING"]     = "|cFF33FF99[好友分组]|r: 检测到 Bnet API 错误。好友列表为空是魔兽客户端的 Bug 导致的。请尝试重启游戏。(无法保证修复)"

-- ============================================================================
-- [[ SPECIAL GROUP NAMES ]]
-- ============================================================================
FGL["GROUP_FAVORITES"]     = "[特别关注]"
FGL["GROUP_NONE"]          = "[2ui]"
FGL["GROUP_EMPTY"]         = "好友列表为空"
FGL["STATUS_MOBILE"]       = "移动端"

-- ============================================================================
-- [[ HOUSING / SAFE MODE ]]
-- ============================================================================
FGL["RELOAD_BTN_TEXT"]      = "重载 [好友分组]"
FGL["RELOAD_TOOLTIP_TITLE"] = "重载 [好友分组]"
FGL["RELOAD_TOOLTIP_DESC"]  = "重载界面以恢复 [好友分组]。"

FGL["SHIELD_MSG"]           = "|cffFF0000[好友分组] 已激活|r\n\n由于暴雪安全框架限制，\n你需要重载界面才能查看房屋。"
FGL["SHIELD_BTN_TEXT"]      = "重载以查看房屋"
FGL["SAFE_MODE_WARNING"]    = "|cffFF0000房屋:|r [好友分组] 已禁用以查看房屋。重载以启用。"

-- ============================================================================
-- 12.0.8
-- ============================================================================
FGL["SETTINGS_CONTACTS"]   = "联系人管理"
FGL["SET_CONTACT_CAP"]     = "显示联系人上限追踪"
FGL["SET_OFFLINE_TRACKER"] = "启用离线追踪分组"
FGL["GROUP_OFFLINE_1"]     = "[离线 1 个月]"
FGL["GROUP_OFFLINE_2"]     = "[离线 2 个月]"
FGL["GROUP_OFFLINE_3"]     = "[离线 3 个月以上]"
FGL["TEXT_CONTACTS"]       = "%d/600"

-- ============================================================================
-- 12.1.01
-- ============================================================================

FGL["SET_GUILDMATES"]      = "启用自动公会队伍"
FGL["GROUP_GUILDMATES"]    = "[公会]"
FGL["FORMAT_GUILD_TAG"]    = "%s <%s>"
FGL["TEXT_BNET_CONTACTS"]       = "%d/%d"
FGL["TOOLTIP_CONTACT_TITLE"]    = "联系人上限"
FGL["TOOLTIP_CONTACT_DESC"]     = "Battle.net 上限为所有游戏共享。\nWoW 好友为此角色独有。"
FGL["TOOLTIP_CONTACT_BNET"]     = "Battle.net 好友："
FGL["TOOLTIP_CONTACT_INVITES"]  = "待处理的邀请："
FGL["TOOLTIP_CONTACT_INV_DESC"] = "（这会消耗 BNet 栏位！）"
FGL["TOOLTIP_CONTACT_WOW"]      = "WoW 角色好友："
FGL["TOOLTIP_CONTACT_TOTAL"]    = "追踪的独立联系人总数：%d"

-- ============================================================================
-- 12.1.02
-- ============================================================================

FGL["DEBUG_DB_SIZE"]    = "数据库配置文件：%d (私人战网缓存)"
FGL["DEBUG_HEADER"]     = "|cFF33FF99[FriendGroups 诊断数据]|r"
FGL["DEBUG_HELP"]       = "|cFF33FF99FriendGroups：|r 输入 /fg debug 查看性能数据。"
FGL["DEBUG_MEM_USAGE"]  = "当前插件内存：%.2f MB"
FGL["FORMAT_AKA_DISPLAY"] = " |cFF949694aka|r %s"
FGL["MSG_PURGE_CACHE"]      = "|cFF33FF99FriendGroups：|r 小号和公会缓存已成功清除。"
FGL["SET_KNOWN_ALTS"]      = "显示已知小号面板"
FGL["SETTINGS_ALT_CHARS"]  = "小号角色"
FGL["SETTINGS_PURGE_CACHE"] = "清除小号缓存"
FGL["TIME_DAYS_AGO"]       = "%d 天前"
FGL["TIME_HOURS_AGO"]      = "%d 小时前"
FGL["TIME_JUST_NOW"]       = "刚刚"
FGL["TIME_MINUTES_AGO"]    = "%d 分钟前"
FGL["TOOLTIP_ALTS_FORMAT"] = "%s %s %s 等级 %d"
FGL["TOOLTIP_ALTS_GUILD_SUFFIX"] = " |cFFFFD100<%s>|r"
FGL["TOOLTIP_ALTS_PUBLIC_TITLE_FORMAT"] = "%s 的已知小号"
FGL["TOOLTIP_ALTS_SEEN"]   = "最后出现在 %s - %s"
FGL["TOOLTIP_ALTS_TITLE_FORMAT"] = "%s 的已知小号"
FGL["TOOLTIP_ALTS_TITLE_FORMAT_MAX"] = "%s 的已知小号（最近10个）"
FGL["TOOLTIP_GUILD_GROUP_DESC_1"]   = "此分组将自动由在线的公会成员填充。"
FGL["TOOLTIP_GUILD_GROUP_DESC_2"]   = "要手动将好友加入此分组，请在其好友备注中输入 %s。"
FGL["TOOLTIP_GUILD_GROUP_TITLE"]    = "自动公会分组"
FGL["UNKNOWN"]             = "未知"
FGL["UNKNOWN_ZONE"]        = "未知区域"
-- [[ GUARD CLAUSE: STOP IF NOT TW ]] --
elseif GetLocale() == "zhTW" then

-- ============================================================================
-- [[ SETTINGS MENU HEADERS ]]
-- ============================================================================
FGL["SETTINGS_SIZE"]       = "列表大小"
FGL["SETTINGS_FILTER"]     = "過濾"
FGL["SETTINGS_APPEARANCE"] = "外觀"
FGL["SETTINGS_BEHAVIOR"]   = "行為"
FGL["SETTINGS_AUTOMATION"] = "自動化"
FGL["SETTINGS_RESET"]      = "|cffff0000重置為預設值|r"

-- ============================================================================
-- [[ SETTINGS: SIZE ]]
-- ============================================================================
FGL["SET_SIZE_SMALL"]      = "小 (預設)"
FGL["SET_SIZE_MEDIUM"]     = "中"
FGL["SET_SIZE_LARGE"]      = "大"

-- ============================================================================
-- [[ SETTINGS: FILTERS ]]
-- ============================================================================
FGL["SET_HIDE_OFFLINE"]    = "隱藏離線"
FGL["SET_HIDE_AFK"]        = "隱藏暫離 (AFK)"
FGL["SET_MOBILE_AFK"]      = "標記手機為暫離"
FGL["SET_HIDE_EMPTY"]      = "隱藏空群組"
FGL["SET_INGAME_ONLY"]     = "僅顯示遊戲內好友"
FGL["SET_RETAIL_ONLY"]     = "僅顯示正式服好友"

-- ============================================================================
-- [[ SETTINGS: APPEARANCE ]]
-- ============================================================================
FGL["SET_SHOW_FLAGS"]      = "顯示伺服器旗幟"
FGL["SET_SHOW_REALM"]      = "顯示伺服器名稱"
FGL["SET_CLASS_COLOR"]     = "使用職業顏色"
FGL["SET_FACTION_ICONS"]   = "顯示陣營圖示"
FGL["SET_GRAY_FACTION"]    = "灰顯對立陣營"
FGL["SET_SHOW_BTAG"]       = "僅顯示 BattleTag"
FGL["SET_HIDE_MAX_LEVEL"]  = "隱藏滿級等級"

-- ============================================================================
-- [[ SETTINGS: BEHAVIOR ]]
-- ============================================================================
FGL["SET_FAV_GROUP"]       = "啟用最愛群組"
FGL["SET_COLLAPSE"]        = "自動折疊群組"

-- ============================================================================
-- [[ SETTINGS: AUTOMATION ]]
-- ============================================================================
FGL["SET_AUTO_ACCEPT"]     = "自動接受組隊邀請"
FGL["SET_AUTO_PARTY_SYNC"] = "自動接受隊伍同步"
FGL["MSG_AUTO_INVITE"]     = "|cFF33FF99FriendGroups|r: %s 邀請你加入隊伍。自動接受 |cff00ff00已啟用|r"
FGL["MSG_AUTO_SYNC"]       = "|cFF33FF99FriendGroups|r: %s 邀請你進行隊伍同步。自動接受 |cff00ff00已啟用|r"
FGL["MSG_AUTO_ACCEPT_FAILED"] = "|cFF33FF99FriendGroups|r: 暴雪在此區域阻止了自動接受。請手動點擊按鈕。"

-- Spirit Behavior Sub-Menu
FGL["SET_SPIRIT_HEADER"]   = "靈魂行為"
FGL["SET_SPIRIT_NONE"]     = "無"
FGL["SET_SPIRIT_RES"]      = "自動接受復活"
FGL["SET_SPIRIT_RELEASE"]  = "自動釋放靈魂"

FGL["MSG_AUTO_RES"]        = "|cFF33FF99FriendGroups|r: %s 正在復活你。自動接受 |cff00ff00已啟用|r"
FGL["MSG_AUTO_RELEASE"]    = "|cFF33FF99FriendGroups|r: 你已經死亡。自動釋放靈魂 |cff00ff00已啟用|r"
FGL["MSG_AUTO_RELEASE_FAILED"] = "|cFF33FF99FriendGroups|r: 暴雪在此區域阻止了自動釋放靈魂。請點擊釋放按鈕。"

-- ============================================================================
-- [[ CONTEXT MENUS ]]
-- ============================================================================
-- Group Header Right-Click
FGL["MENU_RENAME"]         = "重新命名群組"
FGL["MENU_REMOVE"]         = "移除群組"
FGL["MENU_INVITE"]         = "邀請群組"
FGL["MENU_MAX_40"]         = " (上限 40)"

-- Friend Button Right-Click
FGL["DROP_TITLE"]          = "FriendGroups"
FGL["DROP_COPY_NAME"]      = "複製 名字-伺服器"
FGL["DROP_COPY_BTAG"]      = "複製 BattleTag"
FGL["DROP_CREATE"]         = "建立新群組"
FGL["DROP_ADD"]            = "加入群組"
FGL["DROP_REMOVE"]         = "移出群組"
FGL["DROP_CANCEL"]         = "取消"

-- ============================================================================
-- [[ POPUPS & SYSTEM ]]
-- ============================================================================
FGL["POPUP_ENTER_NAME"]    = "輸入新群組名稱"
FGL["POPUP_COPY"]          = "按 Ctrl+C 複製:"

FGL["SEARCH_PLACEHOLDER"]  = "搜尋 FriendGroups"
FGL["SEARCH_TOOLTIP"]      = "FriendGroups: 搜尋任何人！名字、伺服器、職業甚至備註"

FGL["MSG_WELCOME"]         = ""
FGL["MSG_RESET"]           = "|cFF33FF99FriendGroups|r: 設定已重置為預設值。"
FGL["MSG_BUG_WARNING"]     = "|cFF33FF99FriendGroups|r: 偵測到 Bnet API 錯誤。好友列表是空的可能是魔獸客戶端錯誤導致。請嘗試重新啟動遊戲。(無法保證修復)"

-- ============================================================================
-- [[ SPECIAL GROUP NAMES ]]
-- ============================================================================
FGL["GROUP_FAVORITES"]     = "[最愛]"
FGL["GROUP_NONE"]          = "[2ui]"
FGL["GROUP_EMPTY"]         = "好友列表是空的"
FGL["STATUS_MOBILE"]       = "手機"

-- ============================================================================
-- [[ HOUSING / SAFE MODE ]]
-- ============================================================================
FGL["RELOAD_BTN_TEXT"]      = "重載 FriendGroups"
FGL["RELOAD_TOOLTIP_TITLE"] = "重載 FriendGroups"
FGL["RELOAD_TOOLTIP_DESC"]  = "重載介面以還原 FriendGroups。"

FGL["SHIELD_MSG"]           = "|cffFF0000FriendGroups 已啟用|r\n\n由於暴雪安全框架限制，\n你需要重載介面才能檢視房屋。"
FGL["SHIELD_BTN_TEXT"]      = "重載以檢視房屋"
FGL["SAFE_MODE_WARNING"]    = "|cffFF0000房屋:|r FriendGroups 已停用以檢視房屋。重載以啟用。"

-- ============================================================================
-- 12.0.8
-- ============================================================================
FGL["SETTINGS_CONTACTS"]   = "聯絡人管理"
FGL["SET_CONTACT_CAP"]     = "顯示聯絡人上限追蹤"
FGL["SET_OFFLINE_TRACKER"] = "啟用離線追蹤群組"
FGL["GROUP_OFFLINE_1"]     = "[離線 1 個月]"
FGL["GROUP_OFFLINE_2"]     = "[離線 2 個月]"
FGL["GROUP_OFFLINE_3"]     = "[離線 3 個月以上]"
FGL["TEXT_CONTACTS"]       = "%d/600"

-- ============================================================================
-- 12.1.01
-- ============================================================================

FGL["SET_GUILDMATES"]      = "啟用自動公會隊伍"
FGL["GROUP_GUILDMATES"]    = "[公會]"
FGL["FORMAT_GUILD_TAG"]    = "%s <%s>"
FGL["TEXT_BNET_CONTACTS"]       = "%d/%d"
FGL["TOOLTIP_CONTACT_TITLE"]    = "聯絡人上限"
FGL["TOOLTIP_CONTACT_DESC"]     = "Battle.net 上限為所有遊戲共用。\nWoW 好友為此角色獨有。"
FGL["TOOLTIP_CONTACT_BNET"]     = "Battle.net 好友："
FGL["TOOLTIP_CONTACT_INVITES"]  = "待處理的邀請："
FGL["TOOLTIP_CONTACT_INV_DESC"] = "（這會消耗 BNet 空位！）"
FGL["TOOLTIP_CONTACT_WOW"]      = "WoW 角色好友："
FGL["TOOLTIP_CONTACT_TOTAL"]    = "追蹤的獨立聯絡人總數：%d"

-- ============================================================================
-- 12.1.02
-- ============================================================================

FGL["DEBUG_DB_SIZE"]    = "資料庫設定檔：%d（私人 BNet 快取）"
FGL["DEBUG_HEADER"]     = "|cFF33FF99[FriendGroups 診斷遙測]|r"
FGL["DEBUG_HELP"]       = "|cFF33FF99FriendGroups：|r 輸入 /fg debug 以查看效能遙測。"
FGL["DEBUG_MEM_USAGE"]  = "目前插件記憶體：%.2f MB"
FGL["FORMAT_AKA_DISPLAY"] = " |cFF949694aka|r %s"
FGL["MSG_PURGE_CACHE"]      = "|cFF33FF99FriendGroups：|r 分身與公會快取已成功清除。"
FGL["SET_KNOWN_ALTS"]      = "顯示已知分身面板"
FGL["SETTINGS_ALT_CHARS"]  = "分身角色"
FGL["SETTINGS_PURGE_CACHE"] = "清除分身快取"
FGL["TIME_DAYS_AGO"]       = "%d 天前"
FGL["TIME_HOURS_AGO"]      = "%d 小時前"
FGL["TIME_JUST_NOW"]       = "剛剛"
FGL["TIME_MINUTES_AGO"]    = "%d 分鐘前"
FGL["TOOLTIP_ALTS_FORMAT"] = "%s %s %s 等級 %d"
FGL["TOOLTIP_ALTS_GUILD_SUFFIX"] = " |cFFFFD100<%s>|r"
FGL["TOOLTIP_ALTS_PUBLIC_TITLE_FORMAT"] = "%s 的已知分身"
FGL["TOOLTIP_ALTS_SEEN"]   = "最後出現在 %s - %s"
FGL["TOOLTIP_ALTS_TITLE_FORMAT"] = "%s 的已知分身"
FGL["TOOLTIP_ALTS_TITLE_FORMAT_MAX"] = "%s 的已知分身（最近 10 個）"
FGL["TOOLTIP_GUILD_GROUP_DESC_1"]   = "此群組會由你線上的公會成員自動填入。"
FGL["TOOLTIP_GUILD_GROUP_DESC_2"]   = "若要手動將好友加入此群組，請在他們的好友備註中輸入 %s。"
FGL["TOOLTIP_GUILD_GROUP_TITLE"]    = "自動公會群組"
FGL["UNKNOWN"]             = "未知"
FGL["UNKNOWN_ZONE"]        = "未知區域"

-- ============================================================================
-- [[ SETTINGS MENU HEADERS ]]
-- ============================================================================
FGL["SETTINGS_SIZE"]       = "Contact List Size"
FGL["SETTINGS_FILTER"]     = "Filter"
FGL["SETTINGS_APPEARANCE"] = "Appearance"
FGL["SETTINGS_BEHAVIOR"]   = "Group Behavior"
FGL["SETTINGS_AUTOMATION"] = "Automation"
FGL["SETTINGS_RESET"]      = "|cffff0000Reset to Default|r"

-- ============================================================================
-- [[ SETTINGS: SIZE ]]
-- ============================================================================
FGL["SET_SIZE_SMALL"]      = "Small (WoW Default)"
FGL["SET_SIZE_MEDIUM"]     = "Medium"
FGL["SET_SIZE_LARGE"]      = "Large"

-- ============================================================================
-- [[ SETTINGS: FILTERS ]]
-- ============================================================================
FGL["SET_HIDE_OFFLINE"]    = "Hide All Offline"
FGL["SET_HIDE_AFK"]        = "Hide All AFK"
FGL["SET_MOBILE_AFK"]      = "Mark Mobile As AFK"
FGL["SET_HIDE_EMPTY"]      = "Hide Empty Groups"
FGL["SET_INGAME_ONLY"]     = "Show Only In-Game Friends"
FGL["SET_RETAIL_ONLY"]     = "Show Only Retail Friends"

-- ============================================================================
-- [[ SETTINGS: APPEARANCE ]]
-- ============================================================================
FGL["SET_SHOW_FLAGS"]      = "Show Realm Flags"
FGL["SET_SHOW_REALM"]      = "Show Realm Names"
FGL["SET_CLASS_COLOR"]     = "Use Class Color Names"
FGL["SET_FACTION_ICONS"]   = "Show Faction Icons"
FGL["SET_GRAY_FACTION"]    = "Dim Opposing Faction"
FGL["SET_SHOW_BTAG"]       = "Show Only BattleTag"
FGL["SET_HIDE_MAX_LEVEL"]  = "Hide Max Level"

-- ============================================================================
-- [[ SETTINGS: BEHAVIOR ]]
-- ============================================================================
FGL["SET_FAV_GROUP"]       = "Enable Favorite Friends Group"
FGL["SET_COLLAPSE"]        = "Auto Collapse Groups"

-- ============================================================================
-- [[ SETTINGS: AUTOMATION ]]
-- ============================================================================
FGL["SET_AUTO_ACCEPT"]     = "Auto Accept Group Invite"
FGL["SET_AUTO_PARTY_SYNC"] = "Auto Accept Party Sync"
FGL["MSG_AUTO_INVITE"]     = "|cFF33FF99FriendGroups|r: %s invites you to a group. Auto Accept |cff00ff00ENABLED|r"
FGL["MSG_AUTO_SYNC"]       = "|cFF33FF99FriendGroups|r: %s invites you to Party Sync. Auto Accept |cff00ff00ENABLED|r"
FGL["MSG_AUTO_ACCEPT_FAILED"] = "|cFF33FF99FriendGroups|r: Blizzard prevented auto-accept in this zone. Please click the button manually."

-- Spirit Behavior Sub-Menu
FGL["SET_SPIRIT_HEADER"]   = "Spirit Behavior"
FGL["SET_SPIRIT_NONE"]     = "None"
FGL["SET_SPIRIT_RES"]      = "Auto Accept Resurrection"
FGL["SET_SPIRIT_RELEASE"]  = "Auto Release Spirit"

FGL["MSG_AUTO_RES"]        = "|cFF33FF99FriendGroups|r: %s is resurrecting you. Auto Accept |cff00ff00ENABLED|r"
FGL["MSG_AUTO_RELEASE"]    = "|cFF33FF99FriendGroups|r: You have died. Auto Release |cff00ff00ENABLED|r"
FGL["MSG_AUTO_RELEASE_FAILED"] = "|cFF33FF99FriendGroups|r: Blizzard prevented auto-release in this zone. Please click the release button."

-- ============================================================================
-- [[ CONTEXT MENUS ]]
-- ============================================================================
-- Group Header Right-Click
FGL["MENU_RENAME"]         = "Rename Group"
FGL["MENU_REMOVE"]         = "Remove Group"
FGL["MENU_INVITE"]         = "Invite Group to Party"
FGL["MENU_MAX_40"]         = " (Max 40)"

-- Friend Button Right-Click
FGL["DROP_TITLE"]          = "FriendGroups"
FGL["DROP_COPY_NAME"]      = "Copy Name-Realm"
FGL["DROP_COPY_BTAG"]      = "Copy BattleTag"
FGL["DROP_CREATE"]         = "Create New Group"
FGL["DROP_ADD"]            = "Add to Group"
FGL["DROP_REMOVE"]         = "Remove from Group"
FGL["DROP_CANCEL"]         = "Cancel"

-- ============================================================================
-- [[ POPUPS & SYSTEM ]]
-- ============================================================================
FGL["POPUP_ENTER_NAME"]    = "Enter new group name"
FGL["POPUP_COPY"]          = "Press Ctrl+C to copy:"

FGL["SEARCH_PLACEHOLDER"]  = "FriendGroups Search"
FGL["SEARCH_TOOLTIP"]      = "FriendGroups: Search for anyone! Name, Realm, Class and even Notes"

FGL["MSG_WELCOME"]         = ""
FGL["MSG_RESET"]           = "|cFF33FF99FriendGroups|r: Settings reset to default."
FGL["MSG_BUG_WARNING"]     = "|cFF33FF99FriendGroups|r: Bnet API Bug detected. Your empty Friends List is caused by a WoW Client Bug. Please try to restart your game. (no guaranteed fix)"

-- ============================================================================
-- [[ SPECIAL GROUP NAMES ]]
-- ============================================================================
FGL["GROUP_FAVORITES"]     = "[Favorites]"
FGL["GROUP_NONE"]          = "[2ui]"
FGL["GROUP_EMPTY"]         = "Friends List is empty"
FGL["STATUS_MOBILE"]       = "Mobile"

-- ============================================================================
-- [[ HOUSING / SAFE MODE ]]
-- ============================================================================
FGL["RELOAD_BTN_TEXT"]      = "Reload FriendGroups"
FGL["RELOAD_TOOLTIP_TITLE"] = "Reload FriendGroups"
FGL["RELOAD_TOOLTIP_DESC"]  = "Reloads the UI to restore FriendGroups."

FGL["SHIELD_MSG"]           = "|cffFF0000FriendGroups is Active|r\n\nDue to Blizzard Secure Frame restrictions,\nyou must reload to View Houses."
FGL["SHIELD_BTN_TEXT"]      = "Reload to View Houses"
FGL["SAFE_MODE_WARNING"]    = "|cffFF0000VIEW HOUSES:|r FriendGroups disabled to View Houses. Reload to enable."

-- ============================================================================
-- 12.0.8
-- ============================================================================
FGL["SETTINGS_CONTACTS"]   = "Contact Management"
FGL["SET_CONTACT_CAP"]     = "Show Contact Cap Tracker"
FGL["SET_OFFLINE_TRACKER"] = "Enable Offline Tracker Groups"
FGL["GROUP_OFFLINE_1"]     = "[Offline 1 Month]"
FGL["GROUP_OFFLINE_2"]     = "[Offline 2 Months]"
FGL["GROUP_OFFLINE_3"]     = "[Offline 3+ Months]"
FGL["TEXT_CONTACTS"]       = "%d/600"

-- ============================================================================
-- 12.1.01
-- ============================================================================

FGL["SET_GUILDMATES"]      = "Enable Auto Guild Groups"
FGL["GROUP_GUILDMATES"]    = "[Guild]"
FGL["FORMAT_GUILD_TAG"]    = "%s <%s>"
FGL["TEXT_BNET_CONTACTS"]       = "%d/%d"
FGL["TOOLTIP_CONTACT_TITLE"]    = "Contact Limits"
FGL["TOOLTIP_CONTACT_DESC"]     = "Battle.net limits are shared across all games.\nWoW friends are unique to this character."
FGL["TOOLTIP_CONTACT_BNET"]     = "Battle.net Friends:"
FGL["TOOLTIP_CONTACT_INVITES"]  = "Pending Invites:"
FGL["TOOLTIP_CONTACT_INV_DESC"] = "(These consume BNet slots!)"
FGL["TOOLTIP_CONTACT_WOW"]      = "WoW Character Friends:"
FGL["TOOLTIP_CONTACT_TOTAL"]    = "Total unique contacts tracked: %d"

-- ============================================================================
-- 12.1.02
-- ============================================================================

FGL["DEBUG_DB_SIZE"]    = "Database Profiles: %d (Private BNet Cache)"
FGL["DEBUG_HEADER"]     = "|cFF33FF99[FriendGroups Diagnostic Telemetry]|r"
FGL["DEBUG_HELP"]       = "|cFF33FF99FriendGroups:|r Type /fg debug to view performance telemetry."
FGL["DEBUG_MEM_USAGE"]  = "Current Addon Memory: %.2f MB"
FGL["FORMAT_AKA_DISPLAY"] = " |cFF949694aka|r %s"
FGL["MSG_PURGE_CACHE"]      = "|cFF33FF99FriendGroups|r: Alt and Guild caches have been successfully purged."
FGL["SET_KNOWN_ALTS"]      = "Show Known Alt Characters Panel"
FGL["SETTINGS_ALT_CHARS"]  = "Alt Characters"
FGL["SETTINGS_PURGE_CACHE"] = "Purge Alt Cache"
FGL["TIME_DAYS_AGO"]       = "%d days ago"
FGL["TIME_HOURS_AGO"]      = "%d hours ago"
FGL["TIME_JUST_NOW"]       = "Just now"
FGL["TIME_MINUTES_AGO"]    = "%d minutes ago"
FGL["TOOLTIP_ALTS_FORMAT"] = "%s %s %s Level %d"
FGL["TOOLTIP_ALTS_GUILD_SUFFIX"] = " |cFFFFD100<%s>|r"
FGL["TOOLTIP_ALTS_PUBLIC_TITLE_FORMAT"] = "%s's Known Alt Characters"
FGL["TOOLTIP_ALTS_SEEN"]   = "Last seen in %s - %s"
FGL["TOOLTIP_ALTS_TITLE_FORMAT"] = "%s's Known Alt Characters"
FGL["TOOLTIP_ALTS_TITLE_FORMAT_MAX"] = "%s's Known Alt Characters (Recent 10)"
FGL["TOOLTIP_GUILD_GROUP_DESC_1"]   = "This group is automatically populated by your online guildmates."
FGL["TOOLTIP_GUILD_GROUP_DESC_2"]   = "To manually add a friend to this group, type %s anywhere in their friend note."
FGL["TOOLTIP_GUILD_GROUP_TITLE"]    = "Auto Guild Group"
FGL["UNKNOWN"]             = "Unknown"
FGL["UNKNOWN_ZONE"]        = "Unknown Zone"
end

-- [[ REALM FLAGS DATABASE ]] --
-- Categorized by RegionID: 1=US/OC, 2=KR, 3=EU/RU, 4=TW, 5=CN
-- Keys must have NO SPACES AND NO PUNCTUATION.

-- [[ REGION 1: AMERICAS & OCEANIA ]] --
FriendGroups_RealmData = {
    -- [[ BRAZIL (FlagBR.tga) ]] --
    ["Azralon"] = {icon = "FlagBR.tga", region = "Brazil"},
    ["Gallywix"] = {icon = "FlagBR.tga", region = "Brazil"},
    ["Goldrinn"] = {icon = "FlagBR.tga", region = "Brazil"},
    ["Nemesis"] = {icon = "FlagBR.tga", region = "Brazil"},
    ["TolBarad"] = {icon = "FlagBR.tga", region = "Brazil"},

    -- [[ LATIN AMERICA (FlagMX.tga) ]] --
    ["Drakkari"] = {icon = "FlagMX.tga", region = "Latin America"},
    ["QuelThalas"] = {icon = "FlagMX.tga", region = "Latin America"},
    ["Ragnaros"] = {icon = "FlagMX.tga", region = "Latin America"},
    ["Loatheb"] = {icon = "FlagMX.tga", region = "Latin America"},

    -- [[ OCEANIA (FlagAU.tga) ]] --
    ["AmanThul"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Barthilas"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Caelestrasz"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["DathRemar"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Dreadmaul"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Frostmourne"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Gundrak"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["JubeiThos"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Khazgoroth"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Nagrand"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Saurfang"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Thaurissan"] = {icon = "FlagAU.tga", region = "Oceania"},
    ["Arugal"] = {icon = "FlagAU.tga", region = "Oceania"}, 
    ["Yojamba"] = {icon = "FlagAU.tga", region = "Oceania"}, 
    ["Remulos"] = {icon = "FlagAU.tga", region = "Oceania"}, 
    ["Felstriker"] = {icon = "FlagAU.tga", region = "Oceania"}, 
    ["Shadowstrike"] = {icon = "FlagAU.tga", region = "Oceania"}, 
    ["Penance"] = {icon = "FlagAU.tga", region = "Oceania"}, 
    ["Maladath"] = {icon = "FlagAU.tga", region = "Oceania"}, 

    -- [[ US / NORTH AMERICA (FlagUS.tga) ]] --
    ["Aegwynn"] = {icon = "FlagUS.tga", region = "US"},
    ["AeriePeak"] = {icon = "FlagUS.tga", region = "US"},
    ["Agamaggan"] = {icon = "FlagUS.tga", region = "US"},
    ["Aggramar"] = {icon = "FlagUS.tga", region = "US"},
    ["Akama"] = {icon = "FlagUS.tga", region = "US"},
    ["Alexstrasza"] = {icon = "FlagUS.tga", region = "US"},
    ["Alleria"] = {icon = "FlagUS.tga", region = "US"},
    ["AltarofStorms"] = {icon = "FlagUS.tga", region = "US"},
    ["AlteracMountains"] = {icon = "FlagUS.tga", region = "US"},
    ["Andorhal"] = {icon = "FlagUS.tga", region = "US"},
    ["Anetheron"] = {icon = "FlagUS.tga", region = "US"},
    ["Antonidas"] = {icon = "FlagUS.tga", region = "US"},
    ["Anubarak"] = {icon = "FlagUS.tga", region = "US"},
    ["Anvilmar"] = {icon = "FlagUS.tga", region = "US"},
    ["Arathor"] = {icon = "FlagUS.tga", region = "US"},
    ["Archimonde"] = {icon = "FlagUS.tga", region = "US"},
    ["Area52"] = {icon = "FlagUS.tga", region = "US"},
    ["ArgentDawn"] = {icon = "FlagUS.tga", region = "US"},
    ["Arthas"] = {icon = "FlagUS.tga", region = "US"},
    ["Arygos"] = {icon = "FlagUS.tga", region = "US"},
    ["Auchindoun"] = {icon = "FlagUS.tga", region = "US"},
    ["Azgalor"] = {icon = "FlagUS.tga", region = "US"},
    ["AzjolNerub"] = {icon = "FlagUS.tga", region = "US"},
    ["Azshara"] = {icon = "FlagUS.tga", region = "US"},
    ["Azuremyst"] = {icon = "FlagUS.tga", region = "US"},
    ["Baelgun"] = {icon = "FlagUS.tga", region = "US"},
    ["Balnazzar"] = {icon = "FlagUS.tga", region = "US"},
    ["BlackDragonflight"] = {icon = "FlagUS.tga", region = "US"},
    ["Blackhand"] = {icon = "FlagUS.tga", region = "US"},
    ["Blackrock"] = {icon = "FlagUS.tga", region = "US"},
    ["BlackwaterRaiders"] = {icon = "FlagUS.tga", region = "US"},
    ["BlackwingLair"] = {icon = "FlagUS.tga", region = "US"},
    ["BladesEdge"] = {icon = "FlagUS.tga", region = "US"},
    ["Bladefist"] = {icon = "FlagUS.tga", region = "US"},
    ["BleedingHollow"] = {icon = "FlagUS.tga", region = "US"},
    ["BloodFurnace"] = {icon = "FlagUS.tga", region = "US"},
    ["Bloodhoof"] = {icon = "FlagUS.tga", region = "US"},
    ["Bloodscalp"] = {icon = "FlagUS.tga", region = "US"},
    ["Bonechewer"] = {icon = "FlagUS.tga", region = "US"},
    ["BoreanTundra"] = {icon = "FlagUS.tga", region = "US"},
    ["Boulderfist"] = {icon = "FlagUS.tga", region = "US"},
    ["Bronzebeard"] = {icon = "FlagUS.tga", region = "US"},
    ["BurningBlade"] = {icon = "FlagUS.tga", region = "US"},
    ["BurningLegion"] = {icon = "FlagUS.tga", region = "US"},
    ["Cairne"] = {icon = "FlagUS.tga", region = "US"},
    ["CenarionCircle"] = {icon = "FlagUS.tga", region = "US"},
    ["Cenarius"] = {icon = "FlagUS.tga", region = "US"},
    ["Chogall"] = {icon = "FlagUS.tga", region = "US"},
    ["Chromaggus"] = {icon = "FlagUS.tga", region = "US"},
    ["Coilfang"] = {icon = "FlagUS.tga", region = "US"},
    ["Crushridge"] = {icon = "FlagUS.tga", region = "US"},
    ["Daggerspine"] = {icon = "FlagUS.tga", region = "US"},
    ["Dalaran"] = {icon = "FlagUS.tga", region = "US"},
    ["Dalvengyr"] = {icon = "FlagUS.tga", region = "US"},
    ["DarkIron"] = {icon = "FlagUS.tga", region = "US"},
    ["Darkspear"] = {icon = "FlagUS.tga", region = "US"},
    ["Darrowmere"] = {icon = "FlagUS.tga", region = "US"},
    ["Dawnbringer"] = {icon = "FlagUS.tga", region = "US"},
    ["Deathwing"] = {icon = "FlagUS.tga", region = "US"},
    ["DemonSoul"] = {icon = "FlagUS.tga", region = "US"},
    ["Dentarg"] = {icon = "FlagUS.tga", region = "US"},
    ["Destromath"] = {icon = "FlagUS.tga", region = "US"},
    ["Dethecus"] = {icon = "FlagUS.tga", region = "US"},
    ["Detheroc"] = {icon = "FlagUS.tga", region = "US"},
    ["Doomhammer"] = {icon = "FlagUS.tga", region = "US"},
    ["Draenor"] = {icon = "FlagUS.tga", region = "US"},
    ["Dragonblight"] = {icon = "FlagUS.tga", region = "US"},
    ["Dragonmaw"] = {icon = "FlagUS.tga", region = "US"},
    ["Drakthul"] = {icon = "FlagUS.tga", region = "US"},
	["Drenden"] = {icon = "FlagUS.tga", region = "US"},
    ["Dunemaul"] = {icon = "FlagUS.tga", region = "US"},
    ["Durotan"] = {icon = "FlagUS.tga", region = "US"},
    ["Duskwood"] = {icon = "FlagUS.tga", region = "US"},
    ["EarthenRing"] = {icon = "FlagUS.tga", region = "US"},
    ["EchoIsles"] = {icon = "FlagUS.tga", region = "US"},
    ["Eitrigg"] = {icon = "FlagUS.tga", region = "US"},
    ["EldreThalas"] = {icon = "FlagUS.tga", region = "US"},
    ["Elune"] = {icon = "FlagUS.tga", region = "US"},
    ["EmeraldDream"] = {icon = "FlagUS.tga", region = "US"},
    ["Eonar"] = {icon = "FlagUS.tga", region = "US"},
    ["Eredar"] = {icon = "FlagUS.tga", region = "US"},
    ["Executus"] = {icon = "FlagUS.tga", region = "US"},
    ["Exodar"] = {icon = "FlagUS.tga", region = "US"},
    ["Farstriders"] = {icon = "FlagUS.tga", region = "US"},
    ["Feathermoon"] = {icon = "FlagUS.tga", region = "US"},
    ["Fenris"] = {icon = "FlagUS.tga", region = "US"},
    ["Firetree"] = {icon = "FlagUS.tga", region = "US"},
    ["Fizzcrank"] = {icon = "FlagUS.tga", region = "US"},
    ["Frostmane"] = {icon = "FlagUS.tga", region = "US"},
    ["Frostwolf"] = {icon = "FlagUS.tga", region = "US"},
    ["Galakrond"] = {icon = "FlagUS.tga", region = "US"},
    ["Garithos"] = {icon = "FlagUS.tga", region = "US"},
    ["Garona"] = {icon = "FlagUS.tga", region = "US"},
    ["Garrosh"] = {icon = "FlagUS.tga", region = "US"},
    ["Ghostlands"] = {icon = "FlagUS.tga", region = "US"},
    ["Gilneas"] = {icon = "FlagUS.tga", region = "US"},
    ["Gnomeregan"] = {icon = "FlagUS.tga", region = "US"},
    ["Gorefiend"] = {icon = "FlagUS.tga", region = "US"},
    ["Gorgonnash"] = {icon = "FlagUS.tga", region = "US"},
    ["Greymane"] = {icon = "FlagUS.tga", region = "US"},
    ["GrizzlyHills"] = {icon = "FlagUS.tga", region = "US"},
    ["Guldan"] = {icon = "FlagUS.tga", region = "US"},
    ["Gurubashi"] = {icon = "FlagUS.tga", region = "US"},
    ["Hakkar"] = {icon = "FlagUS.tga", region = "US"},
    ["Haomarush"] = {icon = "FlagUS.tga", region = "US"},
    ["Hellscream"] = {icon = "FlagUS.tga", region = "US"},
    ["Hydraxis"] = {icon = "FlagUS.tga", region = "US"},
    ["Hyjal"] = {icon = "FlagUS.tga", region = "US"},
    ["Icecrown"] = {icon = "FlagUS.tga", region = "US"},
    ["Illidan"] = {icon = "FlagUS.tga", region = "US"},
    ["Jaedenar"] = {icon = "FlagUS.tga", region = "US"},
    ["Kaelthas"] = {icon = "FlagUS.tga", region = "US"},
    ["Kalecgos"] = {icon = "FlagUS.tga", region = "US"},
    ["Kargath"] = {icon = "FlagUS.tga", region = "US"},
    ["KelThuzad"] = {icon = "FlagUS.tga", region = "US"},
    ["Khadgar"] = {icon = "FlagUS.tga", region = "US"},
    ["KhazModan"] = {icon = "FlagUS.tga", region = "US"},
    ["Kiljaeden"] = {icon = "FlagUS.tga", region = "US"},
    ["Kilrogg"] = {icon = "FlagUS.tga", region = "US"},
    ["KirinTor"] = {icon = "FlagUS.tga", region = "US"},
    ["Korgath"] = {icon = "FlagUS.tga", region = "US"},
    ["Korialstrasz"] = {icon = "FlagUS.tga", region = "US"},
    ["KulTiras"] = {icon = "FlagUS.tga", region = "US"},
    ["LaughingSkull"] = {icon = "FlagUS.tga", region = "US"},
    ["Lethon"] = {icon = "FlagUS.tga", region = "US"},
    ["Lightbringer"] = {icon = "FlagUS.tga", region = "US"},
    ["LightningsBlade"] = {icon = "FlagUS.tga", region = "US"},
    ["Lightninghoof"] = {icon = "FlagUS.tga", region = "US"},
    ["Llane"] = {icon = "FlagUS.tga", region = "US"},
    ["Lothar"] = {icon = "FlagUS.tga", region = "US"},
    ["Madoran"] = {icon = "FlagUS.tga", region = "US"},
    ["Maelstrom"] = {icon = "FlagUS.tga", region = "US"},
    ["Magtheridon"] = {icon = "FlagUS.tga", region = "US"},
    ["Maiev"] = {icon = "FlagUS.tga", region = "US"},
    ["MalGanis"] = {icon = "FlagUS.tga", region = "US"},
    ["Malfurion"] = {icon = "FlagUS.tga", region = "US"},
    ["Malorne"] = {icon = "FlagUS.tga", region = "US"},
    ["Malygos"] = {icon = "FlagUS.tga", region = "US"},
    ["Mannoroth"] = {icon = "FlagUS.tga", region = "US"},
    ["Medivh"] = {icon = "FlagUS.tga", region = "US"},
    ["Misha"] = {icon = "FlagUS.tga", region = "US"},
    ["MokNathal"] = {icon = "FlagUS.tga", region = "US"},
    ["MoonGuard"] = {icon = "FlagUS.tga", region = "US"},
    ["Moonrunner"] = {icon = "FlagUS.tga", region = "US"},
    ["Mugthol"] = {icon = "FlagUS.tga", region = "US"},
    ["Muradin"] = {icon = "FlagUS.tga", region = "US"},
    ["Nathrezim"] = {icon = "FlagUS.tga", region = "US"},
    ["Nazgrel"] = {icon = "FlagUS.tga", region = "US"},
    ["Nazjatar"] = {icon = "FlagUS.tga", region = "US"},
    ["Nerzhul"] = {icon = "FlagUS.tga", region = "US"},
    ["Nesingwary"] = {icon = "FlagUS.tga", region = "US"},
    ["Nordrassil"] = {icon = "FlagUS.tga", region = "US"},
    ["Norgannon"] = {icon = "FlagUS.tga", region = "US"},
    ["Onyxia"] = {icon = "FlagUS.tga", region = "US"},
    ["Perenolde"] = {icon = "FlagUS.tga", region = "US"},
    ["Proudmoore"] = {icon = "FlagUS.tga", region = "US"},
    ["Queldorei"] = {icon = "FlagUS.tga", region = "US"},
    ["Ravencrest"] = {icon = "FlagUS.tga", region = "US"},
    ["Ravenholdt"] = {icon = "FlagUS.tga", region = "US"},
    ["Rexxar"] = {icon = "FlagUS.tga", region = "US"},
    ["Rivendare"] = {icon = "FlagUS.tga", region = "US"},
    ["Runetotem"] = {icon = "FlagUS.tga", region = "US"},
    ["Sargeras"] = {icon = "FlagUS.tga", region = "US"},
    ["ScarletCrusade"] = {icon = "FlagUS.tga", region = "US"},
    ["Scilla"] = {icon = "FlagUS.tga", region = "US"},
    ["Senjin"] = {icon = "FlagUS.tga", region = "US"},
    ["Sentinels"] = {icon = "FlagUS.tga", region = "US"},
    ["ShadowCouncil"] = {icon = "FlagUS.tga", region = "US"},
    ["Shadowmoon"] = {icon = "FlagUS.tga", region = "US"},
    ["Shadowsong"] = {icon = "FlagUS.tga", region = "US"},
    ["Shandris"] = {icon = "FlagUS.tga", region = "US"},
    ["ShatteredHalls"] = {icon = "FlagUS.tga", region = "US"},
    ["ShatteredHand"] = {icon = "FlagUS.tga", region = "US"},
    ["Shuhalo"] = {icon = "FlagUS.tga", region = "US"},
    ["SilverHand"] = {icon = "FlagUS.tga", region = "US"},
    ["Silvermoon"] = {icon = "FlagUS.tga", region = "US"},
    ["SistersofElune"] = {icon = "FlagUS.tga", region = "US"},
    ["Skullcrusher"] = {icon = "FlagUS.tga", region = "US"},
    ["Skywall"] = {icon = "FlagUS.tga", region = "US"},
    ["Smolderthorn"] = {icon = "FlagUS.tga", region = "US"},
    ["Spinebreaker"] = {icon = "FlagUS.tga", region = "US"},
    ["Spirestone"] = {icon = "FlagUS.tga", region = "US"},
    ["Staghelm"] = {icon = "FlagUS.tga", region = "US"},
    ["SteamwheedleCartel"] = {icon = "FlagUS.tga", region = "US"},
    ["Stonemaul"] = {icon = "FlagUS.tga", region = "US"},
    ["Stormrage"] = {icon = "FlagUS.tga", region = "US"},
    ["Stormreaver"] = {icon = "FlagUS.tga", region = "US"},
    ["Stormscale"] = {icon = "FlagUS.tga", region = "US"},
    ["Suramar"] = {icon = "FlagUS.tga", region = "US"},
    ["Tanaris"] = {icon = "FlagUS.tga", region = "US"},
    ["Terenas"] = {icon = "FlagUS.tga", region = "US"},
    ["Terokkar"] = {icon = "FlagUS.tga", region = "US"},
    ["TheForgottenCoast"] = {icon = "FlagUS.tga", region = "US"},
    ["TheScryers"] = {icon = "FlagUS.tga", region = "US"},
    ["TheUnderbog"] = {icon = "FlagUS.tga", region = "US"},
    ["TheVentureCo"] = {icon = "FlagUS.tga", region = "US"},
    ["ThoriumBrotherhood"] = {icon = "FlagUS.tga", region = "US"},
    ["Thrall"] = {icon = "FlagUS.tga", region = "US"},
    ["Thunderhorn"] = {icon = "FlagUS.tga", region = "US"},
    ["Thunderlord"] = {icon = "FlagUS.tga", region = "US"},
    ["Tichondrius"] = {icon = "FlagUS.tga", region = "US"},
    ["Tortheldrin"] = {icon = "FlagUS.tga", region = "US"},
    ["Trollbane"] = {icon = "FlagUS.tga", region = "US"},
    ["Turalyon"] = {icon = "FlagUS.tga", region = "US"},
    ["TwistingNether"] = {icon = "FlagUS.tga", region = "US"},
    ["Uldaman"] = {icon = "FlagUS.tga", region = "US"}, 
    ["Uldum"] = {icon = "FlagUS.tga", region = "US"},
    ["Undermine"] = {icon = "FlagUS.tga", region = "US"},
    ["Ursin"] = {icon = "FlagUS.tga", region = "US"},
    ["Uther"] = {icon = "FlagUS.tga", region = "US"},
    ["Vashj"] = {icon = "FlagUS.tga", region = "US"},
    ["Veknilash"] = {icon = "FlagUS.tga", region = "US"},
    ["Velen"] = {icon = "FlagUS.tga", region = "US"},
    ["Warsong"] = {icon = "FlagUS.tga", region = "US"},
    ["Whisperwind"] = {icon = "FlagUS.tga", region = "US"},
    ["Wildhammer"] = {icon = "FlagUS.tga", region = "US"},
    ["Windrunner"] = {icon = "FlagUS.tga", region = "US"},
    ["Winterhoof"] = {icon = "FlagUS.tga", region = "US"},
    ["WyrmrestAccord"] = {icon = "FlagUS.tga", region = "US"},
    ["Ysera"] = {icon = "FlagUS.tga", region = "US"},
    ["Ysondre"] = {icon = "FlagUS.tga", region = "US"},
    ["Zangarmarsh"] = {icon = "FlagUS.tga", region = "US"},
    ["Zuljin"] = {icon = "FlagUS.tga", region = "US"},
    ["Zuluhed"] = {icon = "FlagUS.tga", region = "US"},

    -- [[ US CLASSIC / SEASONAL / ERA ]] --
    ["Ashkandi"] = {icon = "FlagUS.tga", region = "US"},
    ["Atiesh"] = {icon = "FlagUS.tga", region = "US"},
    ["Benediction"] = {icon = "FlagUS.tga", region = "US"},
    ["BloodsailBuccaneers"] = {icon = "FlagUS.tga", region = "US"},
    ["Faerlina"] = {icon = "FlagUS.tga", region = "US"},
    ["Grobbulus"] = {icon = "FlagUS.tga", region = "US"},
    ["Mankrik"] = {icon = "FlagUS.tga", region = "US"},
    ["OldBlanchy"] = {icon = "FlagUS.tga", region = "US"},
    ["Westfall"] = {icon = "FlagUS.tga", region = "US"},
    ["Whitemane"] = {icon = "FlagUS.tga", region = "US"},
    ["ChaosBolt"] = {icon = "FlagUS.tga", region = "US"},
    ["CrusaderStrike"] = {icon = "FlagUS.tga", region = "US"},
    ["DefiasPillager"] = {icon = "FlagUS.tga", region = "US"},
    ["Doomhowl"] = {icon = "FlagUS.tga", region = "US"},
    ["Dreamscythe"] = {icon = "FlagUS.tga", region = "US"},
    ["LavaLash"] = {icon = "FlagUS.tga", region = "US"},
    ["LivingFlame"] = {icon = "FlagUS.tga", region = "US"},
    ["LoneWolf"] = {icon = "FlagUS.tga", region = "US"},
    ["Nightslayer"] = {icon = "FlagUS.tga", region = "US"},
    ["Pagle"] = {icon = "FlagUS.tga", region = "US"},
    ["SkullRock"] = {icon = "FlagUS.tga", region = "US"},
    ["WildGrowth"] = {icon = "FlagUS.tga", region = "US"},
    ["DeviateDelight"] = {icon = "FlagUS.tga", region = "US"},
    ["Mutanus"] = {icon = "FlagUS.tga", region = "US"},
    ["JomGabbar"] = {icon = "FlagUS.tga", region = "US"},
    ["BarmanShanker"] = {icon = "FlagUS.tga", region = "US"},
    ["Nightfall"] = {icon = "FlagUS.tga", region = "US"},
    ["ObsidianEdge"] = {icon = "FlagUS.tga", region = "US"},
}

-- [[ REGION 3: EUROPE & RUSSIA ]] --
FriendGroups_RealmDataEU = {
    -- [[ GERMAN (FlagDE.tga) ]] --
    ["Aegwynn"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Alexstrasza"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Alleria"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Ambossar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["AmanThul"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Antonidas"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Anubarak"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Area52"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Arthas"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Arygos"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Azshara"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Baelgun"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Blackhand"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Blackmoore"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Blackrock"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Blutkessel"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Dalvengyr"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DasKonsortium"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DasSyndikat"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DerMithrilorden"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DerRatvonDalaran"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Destromath"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Dethecus"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DieAldor"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DieArguswacht"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DieewigeWacht"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DieNachtwache"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DieSilberneHand"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DieTodeskrallen"] = {icon = "FlagDE.tga", region = "Germany"},
    ["DunMorogh"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Echsenkessel"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Eredar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Everlook"] = {icon = "FlagDE.tga", region = "Germany"},
    ["FestungderStürme"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Forscherliga"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Frostmourne"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Frostwolf"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Garrosh"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Gilneas"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Gorgonnash"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Guldan"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Kargath"] = {icon = "FlagDE.tga", region = "Germany"},
    ["KelThuzad"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Khazgoroth"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Kiljaeden"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Kragjin"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Lothar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Madmortem"] = {icon = "FlagDE.tga", region = "Germany"},
    ["MalGanis"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Malfurion"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Malorne"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Malygos"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Mannoroth"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Mugthol"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Nathrezim"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Nazjatar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Nerathor"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Nethersturm"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Norgannon"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Nozdormu"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Onyxia"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Perenolde"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Proudmoore"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Rajaxx"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Rexxar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Senjin"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Shattrath"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Shendralar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["SteamwheedleCartel"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Taerar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Teldrassil"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Terrordar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Theradras"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Thrall"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Tichondrius"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Tirion"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Todeswache"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Ulduar"] = {icon = "FlagDE.tga", region = "Germany"},
    ["UnGoro"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Veklor"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Venoxis"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Wrathbringer"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Ysera"] = {icon = "FlagDE.tga", region = "Germany"},
    ["ZirkeldesCenarius"] = {icon = "FlagDE.tga", region = "Germany"},
    ["Zuluhed"] = {icon = "FlagDE.tga", region = "Germany"},

    -- [[ FRENCH (FlagFR.tga) ]] --
    ["Arakarahm"] = {icon = "FlagFR.tga", region = "France"},
    ["Arathi"] = {icon = "FlagFR.tga", region = "France"},
    ["Archimonde"] = {icon = "FlagFR.tga", region = "France"},
    ["Auberdine"] = {icon = "FlagFR.tga", region = "France"},
    ["Chantséternels"] = {icon = "FlagFR.tga", region = "France"},
    ["Chogall"] = {icon = "FlagFR.tga", region = "France"},
    ["Chromaggus"] = {icon = "FlagFR.tga", region = "France"},
    ["ConfrérieduThorium"] = {icon = "FlagFR.tga", region = "France"},
    ["ConseildesOmbres"] = {icon = "FlagFR.tga", region = "France"},
    ["CulteDeLaRiveNoire"] = {icon = "FlagFR.tga", region = "France"},
    ["Dalaran"] = {icon = "FlagFR.tga", region = "France"},
    ["DrekThar"] = {icon = "FlagFR.tga", region = "France"},
    ["Eitrigg"] = {icon = "FlagFR.tga", region = "France"},
    ["EldreThalas"] = {icon = "FlagFR.tga", region = "France"},
    ["Elune"] = {icon = "FlagFR.tga", region = "France"},
    ["Garona"] = {icon = "FlagFR.tga", region = "France"},
    ["Hyjal"] = {icon = "FlagFR.tga", region = "France"},
    ["Illidan"] = {icon = "FlagFR.tga", region = "France"},
    ["Kaelthas"] = {icon = "FlagFR.tga", region = "France"},
    ["KhazModan"] = {icon = "FlagFR.tga", region = "France"},
    ["KirinTor"] = {icon = "FlagFR.tga", region = "France"},
    ["Krasus"] = {icon = "FlagFR.tga", region = "France"},
    ["LaCroisadeécarlate"] = {icon = "FlagFR.tga", region = "France"},
    ["LesClairvoyants"] = {icon = "FlagFR.tga", region = "France"},
    ["LesSentinelles"] = {icon = "FlagFR.tga", region = "France"},
    ["MarécagedeZangar"] = {icon = "FlagFR.tga", region = "France"},
    ["Medivh"] = {icon = "FlagFR.tga", region = "France"},
    ["Naxxramas"] = {icon = "FlagFR.tga", region = "France"},
    ["Nerzhul"] = {icon = "FlagFR.tga", region = "France"},
    ["Rashgarroth"] = {icon = "FlagFR.tga", region = "France"},
    ["Sargeras"] = {icon = "FlagFR.tga", region = "France"},
    ["Sinstralis"] = {icon = "FlagFR.tga", region = "France"},
    ["Suramar"] = {icon = "FlagFR.tga", region = "France"},
    ["Templenoir"] = {icon = "FlagFR.tga", region = "France"},
    ["ThrokFeroth"] = {icon = "FlagFR.tga", region = "France"},
    ["Uldaman"] = {icon = "FlagFR.tga", region = "France"}, 
    ["Varimathras"] = {icon = "FlagFR.tga", region = "France"},
    ["Voljin"] = {icon = "FlagFR.tga", region = "France"},
    ["Ysondre"] = {icon = "FlagFR.tga", region = "France"},

    -- [[ SPANISH (FlagES.tga) ]] --
    ["CThun"] = {icon = "FlagES.tga", region = "Spain"},
    ["ColinasPardas"] = {icon = "FlagES.tga", region = "Spain"},
    ["DunModr"] = {icon = "FlagES.tga", region = "Spain"},
    ["Exodar"] = {icon = "FlagES.tga", region = "Spain"},
    ["LosErrantes"] = {icon = "FlagES.tga", region = "Spain"},
    ["Mandokir"] = {icon = "FlagES.tga", region = "Spain"},
    ["Minahonda"] = {icon = "FlagES.tga", region = "Spain"},
    ["Sanguino"] = {icon = "FlagES.tga", region = "Spain"},
    ["Shendralar"] = {icon = "FlagES.tga", region = "Spain"},
    ["Tyrande"] = {icon = "FlagES.tga", region = "Spain"},
    ["Uldum"] = {icon = "FlagES.tga", region = "Spain"},
    ["Zuljin"] = {icon = "FlagES.tga", region = "Spain"},

    -- [[ ITALIAN (FlagIT.tga) ]] --
    ["Nemesis"] = {icon = "FlagIT.tga", region = "Italy"},
    ["PozzodellEternità"] = {icon = "FlagIT.tga", region = "Italy"},

    -- [[ PORTUGUESE (FlagPT.tga) ]] --
    ["Aggra"] = {icon = "FlagPT.tga", region = "Portugal"},
    ["GrimBatol"] = {icon = "FlagPT.tga", region = "Portugal"}, 

    -- [[ RUSSIAN (FlagRU.tga) ]] --
    ["Ashenvale"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Azuregos"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Blackscar"] = {icon = "FlagRU.tga", region = "Russia"},
    ["BootyBay"] = {icon = "FlagRU.tga", region = "Russia"},
    ["BoreanTundra"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Chromie"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Deathguard"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Deepholm"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Eversong"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Flamegor"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Fordragon"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Galakrond"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Goldrinn"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Gordunni"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Grommash"] = {icon = "FlagRU.tga", region = "Russia"},
    ["HowlingFjord"] = {icon = "FlagRU.tga", region = "Russia"},
    ["LichKing"] = {icon = "FlagRU.tga", region = "Russia"},
    ["MoltenCore"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Razuvious"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Soulflayer"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Theradras"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Thermaplugg"] = {icon = "FlagRU.tga", region = "Russia"},
    ["Wyrmthalak"] = {icon = "FlagRU.tga", region = "Russia"},

    -- [[ ENGLISH / UK (FlagGB.tga) ]] --
    ["Agamaggan"] = {icon = "FlagGB.tga", region = "EU"},
    ["Aggramar"] = {icon = "FlagGB.tga", region = "EU"},
    ["AhnQiraj"] = {icon = "FlagGB.tga", region = "EU"},
    ["AlAkir"] = {icon = "FlagGB.tga", region = "EU"},
    ["Alonsus"] = {icon = "FlagGB.tga", region = "EU"},
    ["Anachronos"] = {icon = "FlagGB.tga", region = "EU"},
    ["Arathor"] = {icon = "FlagGB.tga", region = "EU"},
    ["ArgentDawn"] = {icon = "FlagGB.tga", region = "EU"},
    ["Aszune"] = {icon = "FlagGB.tga", region = "EU"},
    ["Auchindoun"] = {icon = "FlagGB.tga", region = "EU"},
    ["AzjolNerub"] = {icon = "FlagGB.tga", region = "EU"},
    ["Azuremyst"] = {icon = "FlagGB.tga", region = "EU"},
    ["Balnazzar"] = {icon = "FlagGB.tga", region = "EU"},
    ["BladesEdge"] = {icon = "FlagGB.tga", region = "EU"},
    ["Bladefist"] = {icon = "FlagGB.tga", region = "EU"},
    ["Bloodfeather"] = {icon = "FlagGB.tga", region = "EU"},
    ["Bloodhoof"] = {icon = "FlagGB.tga", region = "EU"},
    ["Bloodscalp"] = {icon = "FlagGB.tga", region = "EU"},
    ["Boulderfist"] = {icon = "FlagGB.tga", region = "EU"},
    ["Bronzebeard"] = {icon = "FlagGB.tga", region = "EU"},
    ["BronzeDragonflight"] = {icon = "FlagGB.tga", region = "EU"},
    ["BurningBlade"] = {icon = "FlagGB.tga", region = "EU"},
    ["BurningLegion"] = {icon = "FlagGB.tga", region = "EU"},
    ["BurningSteppes"] = {icon = "FlagGB.tga", region = "EU"},
    ["ChamberofAspects"] = {icon = "FlagGB.tga", region = "EU"},
    ["Chromaggus"] = {icon = "FlagGB.tga", region = "EU"},
    ["Crushridge"] = {icon = "FlagGB.tga", region = "EU"},
    ["Daggerspine"] = {icon = "FlagGB.tga", region = "EU"},
    ["DarkmoonFaire"] = {icon = "FlagGB.tga", region = "EU"},
    ["Darksorrow"] = {icon = "FlagGB.tga", region = "EU"},
    ["Darkspear"] = {icon = "FlagGB.tga", region = "EU"},
    ["Deathwing"] = {icon = "FlagGB.tga", region = "EU"},
    ["DefiasBrotherhood"] = {icon = "FlagGB.tga", region = "EU"},
    ["Dentarg"] = {icon = "FlagGB.tga", region = "EU"},
    ["Doomhammer"] = {icon = "FlagGB.tga", region = "EU"},
    ["Draenor"] = {icon = "FlagGB.tga", region = "EU"},
    ["Dragonblight"] = {icon = "FlagGB.tga", region = "EU"},
    ["Dragonmaw"] = {icon = "FlagGB.tga", region = "EU"},
    ["Drakthul"] = {icon = "FlagGB.tga", region = "EU"},
    ["Dunemaul"] = {icon = "FlagGB.tga", region = "EU"},
    ["EarthenRing"] = {icon = "FlagGB.tga", region = "EU"},
    ["EmeraldDream"] = {icon = "FlagGB.tga", region = "EU"},
    ["Emeriss"] = {icon = "FlagGB.tga", region = "EU"},
    ["Eonar"] = {icon = "FlagGB.tga", region = "EU"},
    ["Executus"] = {icon = "FlagGB.tga", region = "EU"},
    ["Frostmane"] = {icon = "FlagGB.tga", region = "EU"},
    ["Frostwhisper"] = {icon = "FlagGB.tga", region = "EU"},
    ["Genjuros"] = {icon = "FlagGB.tga", region = "EU"},
    ["Ghostlands"] = {icon = "FlagGB.tga", region = "EU"},
    ["GrimBatol"] = {icon = "FlagGB.tga", region = "EU"},
    ["Hakkar"] = {icon = "FlagGB.tga", region = "EU"},
    ["Haomarush"] = {icon = "FlagGB.tga", region = "EU"},
    ["Hellfire"] = {icon = "FlagGB.tga", region = "EU"},
    ["Hellscream"] = {icon = "FlagGB.tga", region = "EU"},
    ["Jaedenar"] = {icon = "FlagGB.tga", region = "EU"},
    ["Karazhan"] = {icon = "FlagGB.tga", region = "EU"},
    ["Kazzak"] = {icon = "FlagGB.tga", region = "EU"},
    ["Khadgar"] = {icon = "FlagGB.tga", region = "EU"},
    ["Kilrogg"] = {icon = "FlagGB.tga", region = "EU"},
    ["Korgall"] = {icon = "FlagGB.tga", region = "EU"},
    ["KulTiras"] = {icon = "FlagGB.tga", region = "EU"},
    ["LaughingSkull"] = {icon = "FlagGB.tga", region = "EU"},
    ["Lightbringer"] = {icon = "FlagGB.tga", region = "EU"},
    ["LightningsBlade"] = {icon = "FlagGB.tga", region = "EU"},
    ["Magtheridon"] = {icon = "FlagGB.tga", region = "EU"},
    ["Mazrigos"] = {icon = "FlagGB.tga", region = "EU"},
    ["Moonglade"] = {icon = "FlagGB.tga", region = "EU"},
    ["Nagrand"] = {icon = "FlagGB.tga", region = "EU"},
    ["Neptulon"] = {icon = "FlagGB.tga", region = "EU"},
    ["Nordrassil"] = {icon = "FlagGB.tga", region = "EU"},
    ["Outland"] = {icon = "FlagGB.tga", region = "EU"},
    ["QuelThalas"] = {icon = "FlagGB.tga", region = "EU"},
    ["Ragnaros"] = {icon = "FlagGB.tga", region = "EU"},
    ["Ravencrest"] = {icon = "FlagGB.tga", region = "EU"},
    ["Ravenholdt"] = {icon = "FlagGB.tga", region = "EU"},
    ["Runetotem"] = {icon = "FlagGB.tga", region = "EU"},
    ["Saurfang"] = {icon = "FlagGB.tga", region = "EU"},
    ["ScarshieldLegion"] = {icon = "FlagGB.tga", region = "EU"},
    ["Shadowsong"] = {icon = "FlagGB.tga", region = "EU"},
    ["ShatteredHalls"] = {icon = "FlagGB.tga", region = "EU"},
    ["ShatteredHand"] = {icon = "FlagGB.tga", region = "EU"},
    ["Silvermoon"] = {icon = "FlagGB.tga", region = "EU"},
    ["Skullcrusher"] = {icon = "FlagGB.tga", region = "EU"},
    ["Spinebreaker"] = {icon = "FlagGB.tga", region = "EU"},
    ["Sporeggar"] = {icon = "FlagGB.tga", region = "EU"},
    ["Stormrage"] = {icon = "FlagGB.tga", region = "EU"},
    ["Stormreaver"] = {icon = "FlagGB.tga", region = "EU"},
    ["Stormscale"] = {icon = "FlagGB.tga", region = "EU"},
    ["Sunstrider"] = {icon = "FlagGB.tga", region = "EU"},
    ["Sylvanas"] = {icon = "FlagGB.tga", region = "EU"},
    ["Talnivarr"] = {icon = "FlagGB.tga", region = "EU"},
    ["TarrenMill"] = {icon = "FlagGB.tga", region = "EU"},
    ["Terenas"] = {icon = "FlagGB.tga", region = "EU"},
    ["Terokkar"] = {icon = "FlagGB.tga", region = "EU"},
    ["TheMaelstrom"] = {icon = "FlagGB.tga", region = "EU"},
    ["TheShatar"] = {icon = "FlagGB.tga", region = "EU"},
    ["TheVentureCo"] = {icon = "FlagGB.tga", region = "EU"},
    ["Thunderhorn"] = {icon = "FlagGB.tga", region = "EU"},
    ["Trollbane"] = {icon = "FlagGB.tga", region = "EU"},
    ["Turalyon"] = {icon = "FlagGB.tga", region = "EU"},
    ["TwilightsHammer"] = {icon = "FlagGB.tga", region = "EU"},
    ["TwistingNether"] = {icon = "FlagGB.tga", region = "EU"},
    ["Vashj"] = {icon = "FlagGB.tga", region = "EU"},
    ["Veknilash"] = {icon = "FlagGB.tga", region = "EU"},
    ["Wildhammer"] = {icon = "FlagGB.tga", region = "EU"},
    ["Xavius"] = {icon = "FlagGB.tga", region = "EU"},
    ["Zenedar"] = {icon = "FlagGB.tga", region = "EU"},

    -- [[ CLASSIC / ERA EU ]] --
    ["Firemaw"] = {icon = "FlagGB.tga", region = "EU"},
    ["Gehennas"] = {icon = "FlagGB.tga", region = "EU"},
    ["Golemagg"] = {icon = "FlagGB.tga", region = "EU"},
    ["MirageRaceway"] = {icon = "FlagGB.tga", region = "EU"},
    ["Mograine"] = {icon = "FlagGB.tga", region = "EU"},
    ["NekRosh"] = {icon = "FlagGB.tga", region = "EU"},
    ["PyrewoodVillage"] = {icon = "FlagGB.tga", region = "EU"},
    ["Soulseeker"] = {icon = "FlagGB.tga", region = "EU"},
    ["Spineshatter"] = {icon = "FlagGB.tga", region = "EU"},
    ["Stitches"] = {icon = "FlagGB.tga", region = "EU"},
    ["Thunderstrike"] = {icon = "FlagGB.tga", region = "EU"},
    ["WildGrowth"] = {icon = "FlagGB.tga", region = "EU"},
    ["ZandalarTribe"] = {icon = "FlagGB.tga", region = "EU"},
    ["CrusaderStrike"] = {icon = "FlagGB.tga", region = "EU"},
    ["LivingFlame"] = {icon = "FlagGB.tga", region = "EU"},
    ["LoneWolf"] = {icon = "FlagGB.tga", region = "EU"},
    ["ChaosBolt"] = {icon = "FlagGB.tga", region = "EU"},
    ["LavaLash"] = {icon = "FlagGB.tga", region = "EU"},
}

-- [[ REGIONS 2, 4, 5: ASIA & CHINA ]] --
FriendGroups_RealmDataAsia = {
    -- China (RegionID: 5)
    ["AbyssalMaw"] = {icon = "FlagCN.tga", region = "China"},
    ["AeriePeakCN"] = {icon = "FlagCN.tga", region = "China"},
    ["Akilzon"] = {icon = "FlagCN.tga", region = "China"},
    ["Algalon"] = {icon = "FlagCN.tga", region = "China"},
    ["Chronos"] = {icon = "FlagCN.tga", region = "China"},
    ["Goldshire"] = {icon = "FlagCN.tga", region = "China"},
    ["LichKing"] = {icon = "FlagCN.tga", region = "China"},
    ["Onyxia"] = {icon = "FlagCN.tga", region = "China"},
    ["SilverHand"] = {icon = "FlagCN.tga", region = "China"},
    ["SilvermoonCN"] = {icon = "FlagCN.tga", region = "China"},
    ["TitanReforged"] = {icon = "FlagCN.tga", region = "China"},

    -- Korea (RegionID: 2)
    ["Azshara"] = {icon = "FlagKR.tga", region = "Korea"},
    ["BurningLegion"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Cenarius"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Deathwing"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Durotan"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Frostmourne"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Hellscream"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Hyjal"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Ragnaros"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Windrunner"] = {icon = "FlagKR.tga", region = "Korea"},
    ["Zuljin"] = {icon = "FlagKR.tga", region = "Korea"},

    -- Taiwan (RegionID: 4)
    ["Arthas"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Arygos"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["BleedingHollow"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["ChillwindPoint"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["CrystalpineStinger"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["DemonFallCanyon"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Dragonmaw"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Frostmane"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Global"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Icecrown"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["KrolBlade"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["LightsHope"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Menethil"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Nightsong"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["OldBlanchy"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["OrderoftheCloudSerpent"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Queldorei"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Shadowmoon"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Skywall"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Spirestone"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Stormscale"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["WorldTree"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Whisperwind"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["Wrathbringer"] = {icon = "FlagTW.tga", region = "Taiwan"},
    ["ZealotBlade"] = {icon = "FlagTW.tga", region = "Taiwan"}
}

--[[
	FriendGroups - FULL ORIGINAL CODE + SECURE ARCHITECTURE
    - Uses the exact original logic for groups, menus, and sorting.
    - Adds "Lazy Loading" to prevent VisitHouse() taint.
    - Adds Safety Shield to the House List.
]] --

-- [[ STATE MANAGEMENT ]] --
local FriendGroups_Loaded = false 

-- ============================================================================
-- [[ DIRTY ROSTER ENGINE (IDLE GC CHURN PREVENTER) ]]
-- ============================================================================
local FriendGroups_RosterDirty = true
local fgRosterEventFrame = CreateFrame("Frame")
fgRosterEventFrame:RegisterEvent("BN_FRIEND_INFO_CHANGED")
fgRosterEventFrame:RegisterEvent("BN_FRIEND_LIST_SIZE_CHANGED")
fgRosterEventFrame:RegisterEvent("BN_FRIEND_INVITE_ADDED")
fgRosterEventFrame:RegisterEvent("BN_FRIEND_INVITE_REMOVED")
fgRosterEventFrame:RegisterEvent("FRIENDLIST_UPDATE")
fgRosterEventFrame:RegisterEvent("PLAYER_FLAGS_CHANGED")
fgRosterEventFrame:SetScript("OnEvent", function(self, event, ...)
    FriendGroups_RosterDirty = true
end)

-- ============================================================================
-- [[ MEMORY OPTIMIZATION UTILITIES ]]
-- ============================================================================
local FriendGroups_RealmStringCache = {}

function FriendGroups_CleanRealmName(realmName)
    if type(realmName) ~= "string" or realmName == "" then return "" end
    if FriendGroups_RealmStringCache[realmName] then
        return FriendGroups_RealmStringCache[realmName]
    end
    local cleanRealm = realmName:gsub("[%s%p]+", "")
    FriendGroups_RealmStringCache[realmName] = cleanRealm
    return cleanRealm
end

local FriendGroups_DPElementPool = {}

local function GetDPElement()
    local t = table.remove(FriendGroups_DPElementPool) or {}
    wipe(t)
    return t
end

-- [[ FORWARD DECLARATIONS ]] --
local EnableFriendGroups
local FriendGroups_FriendsListUpdate
local FriendGroups_FriendsListUpdateFriendButton
local FriendGroups_FriendsListButtonTemplateClick
local SetupGroupedView
local FriendGroups_SaveToAltCache

local ADDON_CHAT_PREFIX = "|cffFFE400[|r|cff3AFF00" .. "FriendGroups" .. "|r|cffFFE400]|r"
local function Print(...)
    local str = ADDON_CHAT_PREFIX .. "|cff00F7FF "
    local count = select("#", ...)
    for i = 1, count do
        str = str .. tostring(select(i, ...))
        if i < count then
            str = str .. " "
        end
    end
    DEFAULT_CHAT_FRAME:AddMessage(str .. "|r")
end

local playerRealmID = GetRealmID();
local playerFactionGroup = UnitFactionGroup("player");
local INVITE_RESTRICTION_NONE = 9
local groupsTotal = {}
local groupsSorted = {}
local groupsCount = {}
local expansionMaxLevel = {}
local searchBoxInit = false
local currentExpansionMaxLevel, FriendGroups_Menu, FriendGroupsFrame, searchOpened
local searchValue = ""
local FriendGroups_SearchBox

-- Data for the Custom Menu
local FriendGroups_ClickedData = {}

local groupMenuItems = {
    { text = "",         notCheckable = true, isTitle = true },
    {
        text = FGL["MENU_RENAME"],
        notCheckable = true,
        func = function(self, groupName)
            StaticPopup_Show("FRIENDGROUPS_RENAME", nil, nil, groupName)
        end
    },
    {
        text = FGL["MENU_REMOVE"],
        notCheckable = true,
        func = function(self, groupName)
            FriendGroups_InviteOrGroup(groupName, false)
        end
    },
    {
        text = FGL["MENU_INVITE"],
        notCheckable = true,
        func = function(self, groupName)
            FriendGroups_InviteOrGroup(groupName, true)
        end
    },
}

local settingsMenuItems = {
    -- SECTION 0: SIZE
    { text = FGL["SETTINGS_SIZE"], notCheckable = true, isTitle = true },
    {
        text = FGL["SET_SIZE_SMALL"],
        checked = function() return (ShiGuangDB.FriendGroupsextra_height or 0) == 0 end,
        func = function()
            ShiGuangDB.FriendGroupsextra_height = 0
            FriendGroups_UpdateSize()
        end
    },
    {
        text = FGL["SET_SIZE_MEDIUM"],
        checked = function() return (ShiGuangDB.FriendGroupsextra_height or 0) == 190 end,
        func = function()
            ShiGuangDB.FriendGroupsextra_height = 190
            FriendGroups_UpdateSize()
        end
    },
    {
        text = FGL["SET_SIZE_LARGE"],
        checked = function() return (ShiGuangDB.FriendGroupsextra_height or 0) == 380 end,
        func = function()
            ShiGuangDB.FriendGroupsextra_height = 380
            FriendGroups_UpdateSize()
        end
    },

    -- SECTION 1: FILTERS
    { text = FGL["SETTINGS_FILTER"],    notCheckable = true, isTitle = true },
	{
        text = FGL["SET_HIDE_OFFLINE"],
        keepShownOnClick = true, 
        checked = function() return ShiGuangDB.FriendGroupshide_offline end,
        func = function()
            ShiGuangDB.FriendGroupshide_offline = not ShiGuangDB.FriendGroupshide_offline
            if ShiGuangDB.FriendGroupshide_offline then
                ShiGuangDB.FriendGroupsoffline_tracker = false
            end
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_HIDE_AFK"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupshide_afk end,
        func = function()
            ShiGuangDB.FriendGroupshide_afk = not ShiGuangDB.FriendGroupshide_afk
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_MOBILE_AFK"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsshow_mobile_afk end,
        func = function()
            ShiGuangDB.FriendGroupsshow_mobile_afk = not ShiGuangDB.FriendGroupsshow_mobile_afk
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_HIDE_EMPTY"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupshide_empty_groups end,
        func = function()
            ShiGuangDB.FriendGroupshide_empty_groups = not ShiGuangDB.FriendGroupshide_empty_groups
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_INGAME_ONLY"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsingame_only end,
        func = function()
            ShiGuangDB.FriendGroupsingame_only = not ShiGuangDB.FriendGroupsingame_only
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_RETAIL_ONLY"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsshow_retail end,
        func = function()
            ShiGuangDB.FriendGroupsshow_retail = not ShiGuangDB.FriendGroupsshow_retail
            FriendGroups_FriendsListUpdate()
        end
    },

    -- SECTION 2: APPEARANCE
    { text = FGL["SETTINGS_APPEARANCE"], notCheckable = true, isTitle = true },
    {
        text = FGL["SET_SHOW_FLAGS"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsshow_flags end,
        func = function()
            ShiGuangDB.FriendGroupsshow_flags = not ShiGuangDB.FriendGroupsshow_flags
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_SHOW_REALM"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsshow_realm end,
        func = function()
            ShiGuangDB.FriendGroupsshow_realm = not ShiGuangDB.FriendGroupsshow_realm
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_CLASS_COLOR"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupscolour_classes end,
        func = function()
            ShiGuangDB.FriendGroupscolour_classes = not ShiGuangDB.FriendGroupscolour_classes
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_FACTION_ICONS"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsshow_faction_icons end,
        func = function()
            ShiGuangDB.FriendGroupsshow_faction_icons = not ShiGuangDB.FriendGroupsshow_faction_icons
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_GRAY_FACTION"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsgray_faction end,
        func = function()
            ShiGuangDB.FriendGroupsgray_faction = not ShiGuangDB.FriendGroupsgray_faction
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_SHOW_BTAG"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsshow_btag end,
        func = function()
            ShiGuangDB.FriendGroupsshow_btag = not ShiGuangDB.FriendGroupsshow_btag
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_HIDE_MAX_LEVEL"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupshide_high_level end,
        func = function()
            ShiGuangDB.FriendGroupshide_high_level = not ShiGuangDB.FriendGroupshide_high_level
            FriendGroups_FriendsListUpdate()
        end
    },

    -- SECTION 3: GROUP SETTINGS
    { text = FGL["SETTINGS_BEHAVIOR"], notCheckable = true, isTitle = true },
    {
        text = FGL["SET_FAV_GROUP"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsadd_favorite_group end,
        func = function()
            ShiGuangDB.FriendGroupsadd_favorite_group = not ShiGuangDB.FriendGroupsadd_favorite_group
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_GUILDMATES"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsshow_guildmates end,
        func = function()
            ShiGuangDB.FriendGroupsshow_guildmates = not ShiGuangDB.FriendGroupsshow_guildmates
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_COLLAPSE"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsopen_one_group end,
        func = function()
            ShiGuangDB.FriendGroupsopen_one_group = not ShiGuangDB.FriendGroupsopen_one_group
            FriendGroups_FriendsListUpdate()
        end
    },
	{
        text = FGL["SET_OFFLINE_TRACKER"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsoffline_tracker end,
        func = function()
            ShiGuangDB.FriendGroupsoffline_tracker = not ShiGuangDB.FriendGroupsoffline_tracker
            if ShiGuangDB.FriendGroupsoffline_tracker then
                ShiGuangDB.FriendGroupshide_offline = false
                ShiGuangDB.FriendGroupshide_afk = false
                ShiGuangDB.FriendGroupsshow_mobile_afk = false
                ShiGuangDB.FriendGroupshide_empty_groups = false
                ShiGuangDB.FriendGroupsingame_only = false
                ShiGuangDB.FriendGroupsshow_retail = false
            end
            FriendGroups_FriendsListUpdate()
        end
    },
	
	-- SECTION 4: AUTOMATION
    { text = FGL["SETTINGS_AUTOMATION"], notCheckable = true, isTitle = true },
    {
        text = FGL["SET_AUTO_ACCEPT"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsauto_accept_invite end,
        func = function()
            ShiGuangDB.FriendGroupsauto_accept_invite = not ShiGuangDB.FriendGroupsauto_accept_invite
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_AUTO_PARTY_SYNC"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsauto_accept_sync end,
        func = function()
            ShiGuangDB.FriendGroupsauto_accept_sync = not ShiGuangDB.FriendGroupsauto_accept_sync
            FriendGroups_FriendsListUpdate()
        end
    },
    { text = "  " .. FGL["SET_SPIRIT_HEADER"], notCheckable = true, isTitle = true },
    {
        text = FGL["SET_SPIRIT_RES"],
        leftPadding = 16,
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsauto_accept_res end,
        func = function()
            ShiGuangDB.FriendGroupsauto_accept_res = not ShiGuangDB.FriendGroupsauto_accept_res
            if ShiGuangDB.FriendGroupsauto_accept_res then
                ShiGuangDB.FriendGroupsauto_release = false -- Prevent conflicts
            end
            FriendGroups_FriendsListUpdate()
        end
    },
    {
        text = FGL["SET_SPIRIT_RELEASE"],
        leftPadding = 16,
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsauto_release end,
        func = function()
            ShiGuangDB.FriendGroupsauto_release = not ShiGuangDB.FriendGroupsauto_release
            if ShiGuangDB.FriendGroupsauto_release then
                ShiGuangDB.FriendGroupsauto_accept_res = false -- Prevent conflicts
            end
            FriendGroups_FriendsListUpdate()
        end
    },
    
    -- SECTION 5: ALT CHARACTERS & CACHE
    { text = FGL["SETTINGS_ALT_CHARS"], notCheckable = true, isTitle = true },
    {
        text = FGL["SET_KNOWN_ALTS"],
        keepShownOnClick = true,
        checked = function() return ShiGuangDB.FriendGroupsshow_known_alts ~= false end,
        func = function()
            ShiGuangDB.FriendGroupsshow_known_alts = not (ShiGuangDB.FriendGroupsshow_known_alts ~= false)
            if not ShiGuangDB.FriendGroupsshow_known_alts and FriendGroupsAltTooltip then
                FriendGroupsAltTooltip:Hide()
            end
        end
    },
	{
        text = FGL["SETTINGS_PURGE_CACHE"],
        notCheckable = true,
		func = function()
            -- 1. Purge Known Alts Cache (Private BNet)
            if ShiGuangDB.FriendGroupsalt_cache then
                wipe(ShiGuangDB.FriendGroupsalt_cache)
            end
            
            -- 2. Purge Auto-Guild Caches & Known Guilds Memory
            if ShiGuangDB.FriendGroupsalt_guild_rosters then
                wipe(ShiGuangDB.FriendGroupsalt_guild_rosters)
            end
            if ShiGuangDB.FriendGroupsbnet_guild_map then
                wipe(ShiGuangDB.FriendGroupsbnet_guild_map)
            end
            if ShiGuangDB.FriendGroupsknown_player_guilds then
                wipe(ShiGuangDB.FriendGroupsknown_player_guilds)
            end
            
            -- Hide static panel if it is currently open
            if FriendGroupsAltTooltip then
                FriendGroupsAltTooltip:Hide()
            end
            
            -- 3. Force the native guild listener to flag the cache as dirty
            if C_GuildInfo and C_GuildInfo.GuildRoster then
                C_GuildInfo.GuildRoster()
            end
            
            print(FGL["MSG_PURGE_CACHE"])
        end
    },

    -- SECTION 6: SYSTEM & RESET
    { text = "", notCheckable = true, isTitle = true },
	{
        text = FGL["SETTINGS_RESET"],
        notCheckable = true,
        func = function()
            CloseDropDownMenus()
            ShiGuangDB.FriendGroupshide_offline = true
            ShiGuangDB.FriendGroupscolour_classes = true
            ShiGuangDB.FriendGroupsshow_faction_icons = true
            ShiGuangDB.FriendGroupsshow_realm = true
            ShiGuangDB.FriendGroupshide_high_level = false
            ShiGuangDB.FriendGroupsadd_favorite_group = true
            ShiGuangDB.FriendGroupsshow_guildmates = true
            ShiGuangDB.FriendGroupsshow_flags = true
            ShiGuangDB.FriendGroupsgray_faction = false
            ShiGuangDB.FriendGroupsshow_mobile_afk = false
            ShiGuangDB.FriendGroupsadd_mobile_text = true
            ShiGuangDB.FriendGroupsingame_only = true
            ShiGuangDB.FriendGroupsingame_retail = false
            ShiGuangDB.FriendGroupsshow_btag = false
            ShiGuangDB.FriendGroupsshow_retail = false
            ShiGuangDB.FriendGroupsshow_search = false
            ShiGuangDB.FriendGroupshide_empty_groups = true
            ShiGuangDB.FriendGroupshide_afk = false
            ShiGuangDB.FriendGroupsopen_one_group = false
            ShiGuangDB.FriendGroupsauto_accept_invite = false
            ShiGuangDB.FriendGroupsauto_accept_sync = false
            ShiGuangDB.FriendGroupsauto_accept_res = false
            ShiGuangDB.FriendGroupsauto_release = false
            ShiGuangDB.FriendGroupsoffline_tracker = true
            ShiGuangDB.FriendGroupsshow_known_alts = true
            ShiGuangDB.FriendGroupsextra_height = 190 
            ShiGuangDB.FriendGroupscollapsed = {}
            
            FriendGroups_UpdateSize()
            FriendGroups_UpdateContactCap()
            FriendGroups_FriendsListUpdate()
            
            print(FGL["MSG_RESET"])
        end
    },
}

--[[
	Init Values
]] --

currentExpansionMaxLevel = GetMaxLevelForPlayerExpansion and GetMaxLevelForPlayerExpansion() or GetMaxLevelForExpansionLevel and GetMaxLevelForExpansionLevel(GetExpansionLevel()) or 80

-- ============================================================================
-- [[ GUILD CACHE ENGINE (OPTIMIZED: LAZY HEARTBEAT & FINGERPRINTING) ]]
-- ============================================================================
local FriendGroups_GuildCacheDirty = true
local FriendGroups_PlayerGuildName = ""
local FriendGroups_LiveGuildSessionDict = {} -- [[ NEW: Fast O(1) Memory-Safe Lookup ]]

local fgGuildEventFrame = CreateFrame("Frame")
fgGuildEventFrame:RegisterEvent("GUILD_ROSTER_UPDATE")
fgGuildEventFrame:RegisterEvent("PLAYER_GUILD_UPDATE")
fgGuildEventFrame:SetScript("OnEvent", function()
    -- [[ DIRTY FLAG BUCKET: Ignore event spam, just flip the switch ]]
    FriendGroups_GuildCacheDirty = true
end)

function FG_IsCharOnline(charKey)
    if not charKey then return false end
    
    local cName = strsplit("-", charKey)
    local wowFriend = C_FriendList.GetFriendInfo(cName)
    if wowFriend and wowFriend.connected then return true end
    
    local numBNetTotal = C_BattleNet and C_BattleNet.GetFriendNum and C_BattleNet.GetFriendNum() or BNGetNumFriends and BNGetNumFriends() or 0
    if numBNetTotal > 0 then
        for i = 1, numBNetTotal do
            local accountInfo = C_BattleNet.GetFriendAccountInfo(i)
            if accountInfo and accountInfo.gameAccountInfo and accountInfo.gameAccountInfo.isOnline then
                if accountInfo.gameAccountInfo.clientProgram == BNET_CLIENT_WOW then
                    local bName = accountInfo.gameAccountInfo.characterName or ""
                    local bRealm = FriendGroups_CleanRealmName(accountInfo.gameAccountInfo.realmName or accountInfo.gameAccountInfo.realmDisplayName or "")
                    if (bName .. "-" .. bRealm) == charKey then return true end
                end
            end
        end
    end
    return false
end

function FriendGroups_BuildGuildCache()
    if not FriendGroups_GuildCacheDirty then return end
    
    if not ShiGuangDB.FriendGroupsknown_player_guilds then
        ShiGuangDB.FriendGroupsknown_player_guilds = {}
    end
    
    local guildName = GetGuildInfo("player")
    FriendGroups_PlayerGuildName = (type(guildName) == "string") and guildName or ""
    
    if FriendGroups_PlayerGuildName ~= "" then
        ShiGuangDB.FriendGroupsknown_player_guilds[FriendGroups_PlayerGuildName] = true
        wipe(FriendGroups_LiveGuildSessionDict)
        
        -- Fallback chain: Modern C_ API first, legacy global second
        local numTotal = (C_GuildInfo and C_GuildInfo.GetNumGuildMembers) and C_GuildInfo.GetNumGuildMembers() or GetNumGuildMembers()
        local myRealm = GetNormalizedRealmName()
        
        for i = 1, numTotal do
            -- Safe capture of the first return value, supporting both future tables and legacy multi-var returns
            local name = (C_GuildInfo and C_GuildInfo.GetGuildRosterInfo) and C_GuildInfo.GetGuildRosterInfo(i) or GetGuildRosterInfo(i)
            
            if name and type(name) == "string" then
                local baseName, realmName = strsplit("-", name)
                if baseName then
                    local targetRealm = realmName or myRealm
                    if targetRealm and type(targetRealm) == "string" then
                        local cleanRealm = FriendGroups_CleanRealmName(targetRealm)
                        local uniqueKey = baseName .. "-" .. cleanRealm
                        
                        FriendGroups_LiveGuildSessionDict[uniqueKey] = true
                        FriendGroups_LiveGuildSessionDict[baseName] = true
                    end
                end
            end
        end
    else
        wipe(FriendGroups_LiveGuildSessionDict)
    end
    
    if ShiGuangDB.FriendGroupsalt_guild_rosters then ShiGuangDB.FriendGroupsalt_guild_rosters = nil end
    if ShiGuangDB.FriendGroupsbnet_guild_map then ShiGuangDB.FriendGroupsbnet_guild_map = nil end
    
    FriendGroups_GuildCacheDirty = false
end

-- [[ THE LAZY HEARTBEAT ]]
local function FriendGroups_LazyGuildScanner()
    if FriendGroups_GuildCacheDirty then
        FriendGroups_BuildGuildCache()
    end
    C_Timer.After(180, FriendGroups_LazyGuildScanner)
end
C_Timer.After(5, FriendGroups_LazyGuildScanner)


--[[
	Helper Functions
]] --

function FriendGroups_DebugLog(tData, strName)
	if not DevTool then
		LoadAddOn("DevTool")
	end
	if DevTool then
		DevTool:AddData(tData, strName)
	end
end

local FriendGroups_OriginalHeight = nil
local FriendGroupsList_OriginalHeight = nil

function FriendGroups_UpdateSize()
    -- 1. Store the original Blizzard defaults the very first time we run
    if not FriendGroups_OriginalHeight then
        FriendGroups_OriginalHeight = FriendsFrame:GetHeight()
        FriendGroupsList_OriginalHeight = FriendsListFrame:GetHeight()
    end

    -- 2. Determine target height based on saved variable
    local extra = ShiGuangDB.FriendGroupsextra_height or 0

    -- 3. Apply
    FriendsFrame:SetHeight(FriendGroups_OriginalHeight + extra)
    FriendsListFrame:SetHeight(FriendGroupsList_OriginalHeight + extra)

    -- 4. Re-anchor ScrollBox to fill the new space
    FriendsListFrame.ScrollBox:ClearAllPoints()
    FriendsListFrame.ScrollBox:SetPoint("TOPLEFT", FriendsListFrame, "TOPLEFT", 7, -115)
    FriendsListFrame.ScrollBox:SetPoint("BOTTOMRIGHT", FriendsListFrame, "BOTTOMRIGHT", -28, 35)
end

function FriendGroups_Rename(self, oldGroup)
	local input = self:GetEditBox():GetText()
    
	if input == "" or not oldGroup then return end

	local groups = {}
    -- Modern API prioritization for total friends
    local numBNetTotal = C_BattleNet.GetFriendNum and C_BattleNet.GetFriendNum() or BNGetNumFriends()
    
	for i = 1, numBNetTotal do
        local accountInfo = C_BattleNet.GetFriendAccountInfo(i)
        if accountInfo then
            local presenceID = accountInfo.bnetAccountID
            local noteText = accountInfo.note
            local note = FriendGroups_NoteAndGroups(noteText, groups)
            if groups[oldGroup] then
                groups[oldGroup] = nil
                groups[input] = true
                note = FriendGroups_CreateNote(note, groups)
                
                -- Modern API prioritization for setting notes
                if C_BattleNet.SetFriendNote then
                    C_BattleNet.SetFriendNote(presenceID, note)
                else
                    BNSetFriendNote(presenceID, note)
                end
            end
        end
	end
    
    local numWoWTotal = C_FriendList.GetNumFriends and C_FriendList.GetNumFriends() or 0
	for i = 1, numWoWTotal do
		local friendInfo = C_FriendList.GetFriendInfoByIndex(i)
        if friendInfo then
            local note = friendInfo.notes
            local name = friendInfo.name
            note = FriendGroups_NoteAndGroups(note, groups)

            if groups[oldGroup] then
                groups[oldGroup] = nil
                groups[input] = true
                note = FriendGroups_CreateNote(note, groups)
                C_FriendList.SetFriendNotes(name, note)
            end
        end
	end
	FriendGroups_FriendsListUpdate()
end

function FriendGroups_InviteOrGroup(groupName, invite)
    if invite then
        -- Use the live UI cache so we only invite exactly who is visible in the group
        if not groupsTotal[groupName] then return end

        local inviteDelay = 0
        
        for _, playerData in ipairs(groupsTotal[groupName]) do
            local id = playerData.id
            local buttonType = playerData.buttonType

            if buttonType == FRIENDS_BUTTON_TYPE_BNET then
                local friendAccountInfo = C_BattleNet.GetFriendAccountInfo(id)
                if friendAccountInfo and friendAccountInfo.gameAccountInfo then
                    local gameAccountInfo = friendAccountInfo.gameAccountInfo
                    
                    -- Strictly verify they are online and playing Retail WoW to prevent silent API fails
                    if gameAccountInfo.isOnline and gameAccountInfo.clientProgram == BNET_CLIENT_WOW and gameAccountInfo.wowProjectID == WOW_PROJECT_MAINLINE then
                        local gameAccountID = gameAccountInfo.gameAccountID
                        if gameAccountID then
                            C_Timer.After(inviteDelay, function()
                                BNInviteFriend(gameAccountID)
                            end)
                            inviteDelay = inviteDelay + 0.2
                        end
                    end
                end
            elseif buttonType == FRIENDS_BUTTON_TYPE_WOW then
                local friend_info = C_FriendList.GetFriendInfoByIndex(id)
                if friend_info and friend_info.connected and friend_info.name then
                    local charName = friend_info.name
                    C_Timer.After(inviteDelay, function()
                        C_PartyInfo.InviteUnit(charName)
                    end)
                    inviteDelay = inviteDelay + 0.2
                end
            end
        end
    else
        -- Removing a group still relies on text parsing to strip the hashtag out of the raw notes
        local groups = {}

        local numBNetTotal = C_BattleNet and C_BattleNet.GetFriendNum and C_BattleNet.GetFriendNum() or BNGetNumFriends()
        for i = 1, numBNetTotal do
            local friendAccountInfo = C_BattleNet.GetFriendAccountInfo(i)
            if friendAccountInfo then
                local presenceID = friendAccountInfo.bnetAccountID
                local noteText = friendAccountInfo.note
                local note = FriendGroups_NoteAndGroups(noteText, groups)
                if groups[groupName] then
                    groups[groupName] = nil
                    note = FriendGroups_CreateNote(note, groups)
                    if C_BattleNet and C_BattleNet.SetFriendNote then
                        C_BattleNet.SetFriendNote(presenceID, note)
                    else
                        BNSetFriendNote(presenceID, note)
                    end
                end
            end
        end
        for i = 1, C_FriendList.GetNumFriends() do
            local friend_info = C_FriendList.GetFriendInfoByIndex(i)
            if friend_info then
                local name = friend_info.name
                local noteText = friend_info.notes
                local note = FriendGroups_NoteAndGroups(noteText, groups)

                if groups[groupName] then
                    groups[groupName] = nil
                    note = FriendGroups_CreateNote(note, groups)
                    C_FriendList.SetFriendNotes(name, note)
                end
            end
        end
    end
end

function FriendGroups_AddGroup(note, group)
	local groups = {}
	note = FriendGroups_NoteAndGroups(note, groups)
	groups[""] = nil
	groups[group] = true
	return FriendGroups_CreateNote(note, groups)
end

function FriendGroups_Create(self, data)
	local input = self:GetEditBox():GetText()
	if input == "" then
		return
	end
	local note = FriendGroups_AddGroup(data.note, input)
	if data.name then
		data.set(data.name, note)
	else
		data.set(data.id, note)

		ShiGuangDB.FriendGroupscollapsed[input] = true
	end
end

function FriendGroups_NoteAndGroups(note, groups)
    -- 12.0 FIX: Secure note string check to prevent strsplit crash
    if type(note) ~= "string" then note = "" end

	if not note or note == "" then
		return FriendGroups_FillGroups(groups, "")
	end
	if groups then
		return FriendGroups_FillGroups(groups, strsplit("#", note))
	end
	return strsplit("#", note)
end

function FriendGroups_RemoveGroup(note, group)
	local groups = {}
	note = FriendGroups_NoteAndGroups(note, groups)
	groups[""] = nil
	groups[group] = nil

	return FriendGroups_CreateNote(note, groups)
end

function FriendGroups_CreateNote(note, groups)
	local value = ""
	if note then
		value = note
	end
	for group in pairs(groups) do
		value = value .. "#" .. group
	end
	return value
end

function FriendGroups_FillGroups(groups, note, ...)
	wipe(groups)
	local n = select('#', ...)
	for i = 1, n do
		local v = select(i, ...)
		v = strtrim(v)
		groups[v] = true
	end
	if n == 0 then
		groups[""] = true
	end
	return note
end

function FriendGroups_HasValue(tab, val)
	for index, value in ipairs(tab) do
		if value == val then
			return true
		end
	end

	return false
end

function FriendGroups_SplitBattleTag(battleTag)
	if type(battleTag) ~= "string" then return battleTag end

	local sep = "#"
	if sep == nil then
		sep = "%s"
	end
	local t = {}
	for str in string.gmatch(battleTag, "([^" .. sep .. "]+)") do
		table.insert(t, str)
	end
	return t[1]
end

function FriendGroups_GetClassColorCode(class, returnTable)
	if not class then
		return returnTable and FRIENDS_GRAY_COLOR or
			string.format("|cFF%02x%02x%02x", FRIENDS_GRAY_COLOR.r * 255, FRIENDS_GRAY_COLOR.g * 255,
				FRIENDS_GRAY_COLOR.b * 255)
	end

	local initialClass = class
	for k, v in pairs(LOCALIZED_CLASS_NAMES_FEMALE) do
		if class == v then
			class = k
			break
		end
	end

	if class == initialClass then
		for k, v in pairs(LOCALIZED_CLASS_NAMES_MALE) do
			if class == v then
				class = k
				break
			end
		end
	end

	local color = class ~= "" and RAID_CLASS_COLORS[class] or FRIENDS_GRAY_COLOR
	if returnTable then
		return color
	else
		return string.format("|cFF%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255)
	end
end

FriendGroups_ActiveAKA = {}

function FriendGroups_GetBNetButtonNameText(accountName, client, canCoop, characterName, class, level, battleTag, timerunningSeasonID, realmName)
	local nameText

	-- set up player name and character name
	if accountName then
		if ShiGuangDB.FriendGroupsshow_btag and battleTag then
			nameText = FriendGroups_SplitBattleTag(battleTag)
		else
			nameText = accountName
		end
	else
		nameText = UNKNOWN
	end

	-- append character name
	if characterName then
		if timerunningSeasonID then
			characterName = TimerunningUtil.AddSmallIcon(characterName)
		end

		local levelSuffix
		if (not level) or (ShiGuangDB.FriendGroupshide_high_level and level == currentExpansionMaxLevel) or level == 0 then
			levelSuffix = ""
		else
			levelSuffix = level .. " "  --" " .. level
		end

        -- [[ NEW: Separate AKA string generation (Outside of Suffix Brackets) ]]
        local akaText = ""
        local accountIdentifier = battleTag or accountName
        if accountIdentifier and FriendGroups_ActiveAKA and FriendGroups_ActiveAKA[accountIdentifier] then
            local akaInfo = FriendGroups_ActiveAKA[accountIdentifier]
            -- Only render the AKA if they are not actively logged into the main character
            if akaInfo.name and akaInfo.name ~= characterName then
                local akaFormattedName = "[" .. akaInfo.name .. "]"
                
                -- Apply class or faction colors strictly to the name inside the AKA brackets
                if ShiGuangDB.FriendGroupscolour_classes and akaInfo.class then
                    akaFormattedName = FriendGroups_GetClassColorCode(akaInfo.class) .. akaFormattedName .. FONT_COLOR_CODE_CLOSE
                elseif not canCoop and ShiGuangDB.FriendGroupsgray_faction then
                    akaFormattedName = "|cFF949694" .. akaFormattedName .. "|r"
                end
                
                if FGL["FORMAT_AKA_DISPLAY"] then
                    akaText = string.format(FGL["FORMAT_AKA_DISPLAY"], akaFormattedName)
                end
            end
        end

		if client == BNET_CLIENT_WOW then
			if characterName ~= "" and level ~= 0 then
				if not canCoop and ShiGuangDB.FriendGroupsgray_faction then
					nameText = "|CFF949694" .. nameText .. " [" .. levelSuffix .. characterName .. "]" .. "|r"
				elseif ShiGuangDB.FriendGroupscolour_classes then
					local nameColor = FriendGroups_GetClassColorCode(class)
					nameText = nameText .. " " .. nameColor .. "[" .. levelSuffix.. characterName .. "]"  .. FONT_COLOR_CODE_CLOSE
				else
					nameText = nameText .. " [" .. levelSuffix .. characterName .. "]" .. FONT_COLOR_CODE_CLOSE
				end
			end
		else
			if ENABLE_COLORBLIND_MODE == "1" then
				characterName = characterName
			end
			nameText = nameText .. " " .. FRIENDS_OTHER_NAME_COLOR_CODE .. "[" .. levelSuffix .. "]" .. characterName .. FONT_COLOR_CODE_CLOSE
		end
        
        -- [[ Append the newly formatted AKA text outside the main brackets ]]
        nameText = nameText .. akaText
	end

	return nameText
end

-- ============================================================================
-- [[ MEMORY OPTIMIZATION & CACHING ]]
-- ============================================================================
local FriendGroups_TablePool = {}
local FriendGroups_NoteCache = { [""] = {} }
local FriendGroups_WorkingGroupTable = {}

local function FG_GetTable()
    local t = table.remove(FriendGroups_TablePool) or {}
    wipe(t)
    return t
end

local function FG_ReleaseTable(t)
    if type(t) == "table" then
        wipe(t)
        table.insert(FriendGroups_TablePool, t)
    end
end

local FriendGroups_NoteCacheCount = 0

function FriendGroups_GetPlayerGroups(note)
    -- 12.0 FIX: Secure note string check to prevent string.match crash
    if type(note) ~= "string" then note = "" end
    
    if note == "" then 
        return FriendGroups_NoteCache[""] 
    end

    if FriendGroups_NoteCache[note] then
        return FriendGroups_NoteCache[note]
    end

    -- Safety Flush: Prevent infinite memory bloat from external note-sync addons
    if FriendGroups_NoteCacheCount > 1500 then
        wipe(FriendGroups_NoteCache)
        FriendGroups_NoteCache[""] = {}
        FriendGroups_NoteCacheCount = 0
    end

    local groups = {}
    local formattedNote = string.match(note, "#.*")

    if formattedNote then
        for s in string.gmatch(formattedNote, "[^#]+") do
            table.insert(groups, s)
        end
    end

    FriendGroups_NoteCache[note] = groups
    FriendGroups_NoteCacheCount = FriendGroups_NoteCacheCount + 1
    
    return groups
end

function FriendGroups_GetPlayerData(friendsListData, playerId, playerType)
	for _, playerData in pairs(friendsListData) do
		if playerData and playerData.id and playerData.id == playerId and playerData.buttonType and playerData.buttonType == playerType then
			return playerData
		end
	end

	return nil
end

function FriendGroups_ShowRichPresenceOnly(client, wowProjectID, faction, realmID, areaName)
	if (client ~= BNET_CLIENT_WOW) or (wowProjectID ~= WOW_PROJECT_ID) then
		return true;
	elseif (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC) and ((faction ~= playerFactionGroup) or (realmID ~= playerRealmID)) then
		return true;
	else
		-- Display Rich Presence as a safety fallback if the areaName is explicitly blank or nil.
        -- This accurately catches Plunderstorm, Character Select screens, and transitional loading states.
		if (client == BNET_CLIENT_WOW) and (wowProjectID == WOW_PROJECT_ID) then
			if not areaName or areaName == "" then
				return true;
			end
		end

		return false;
	end;
end

function FriendGroups_GetOnlineInfoText(client, isMobile, rafLinkType, locationText, realmText)
	if not locationText then
		return UNKNOWN;
	end

	if isMobile then
		return LOCATION_MOBILE_APP;
	end
	if (client == BNET_CLIENT_WOW) and (rafLinkType ~= Enum.RafLinkType.None) and not isMobile then
		if rafLinkType == Enum.RafLinkType.Recruit then
			return RAF_RECRUIT_FRIEND:format(locationText);
		else
			return RAF_RECRUITER_FRIEND:format(locationText);
		end
	end

	if ShiGuangDB.FriendGroupsshow_realm and realmText and realmText ~= "" then
		locationText = locationText .. " - " .. realmText
	end

	return locationText;
end

function FriendGroups_GetFriendInfoById(id)
	local accountName, characterName, class, level, isFavoriteFriend, isOnline,
	bnetAccountId, client, canCoop, wowProjectID, lastOnline,
	isAFK, isGameAFK, isDND, isGameBusy, mobile, zoneName, battleTag, factionName,
	gameText, realmName, timerunningSeasonID

    -- 12.0 FIX: Strictly use C_BattleNet. Old global BNet functions are permanently removed.
	if C_BattleNet and C_BattleNet.GetFriendAccountInfo then
		local accountInfo = C_BattleNet.GetFriendAccountInfo(id)
		if accountInfo then
			accountName = accountInfo.accountName
			isFavoriteFriend = accountInfo.isFavorite
			bnetAccountId = accountInfo.bnetAccountID
			isAFK = accountInfo.isAFK
			isDND = accountInfo.isDND
			lastOnline = accountInfo.lastOnlineTime
			battleTag = accountInfo.battleTag

			local gameAccountInfo = accountInfo.gameAccountInfo
			if gameAccountInfo then
				isOnline = gameAccountInfo.isOnline
				isGameAFK = gameAccountInfo.isGameAFK
				isGameBusy = gameAccountInfo.isGameBusy
				mobile = gameAccountInfo.isWowMobile
				characterName = gameAccountInfo.characterName
				class = gameAccountInfo.className
				level = gameAccountInfo.characterLevel
				client = gameAccountInfo.clientProgram
				wowProjectID = gameAccountInfo.wowProjectID
				gameText = gameAccountInfo.richPresence
				zoneName = gameAccountInfo.areaName
				realmName = gameAccountInfo.realmName
				factionName = gameAccountInfo.factionName
				timerunningSeasonID = gameAccountInfo.timerunningSeasonID
			end

			canCoop = CanCooperateWithGameAccount(accountInfo)
		end
	end

	if realmName and realmName ~= "" then
		if zoneName then
			zoneName = zoneName .. " - " .. realmName
		end
	end

	return accountName, characterName, class, level, isFavoriteFriend, isOnline,
		bnetAccountId, client, canCoop, wowProjectID, lastOnline,
		isAFK, isGameAFK, isDND, isGameBusy, mobile, zoneName, gameText, battleTag, factionName, timerunningSeasonID
end

function FriendGroups_GetFactionIcon(factionGroup)
	if (factionGroup and factionGroup ~= "" and factionGroup ~= "Neutral") then
		return "Interface\\FriendsFrame\\PlusManz-" .. factionGroup;
	else
		return ""
	end
end

function FriendGroups_GetStatusString(playerData)
	local status = "Offline"

	if playerData.buttonType == FRIENDS_BUTTON_TYPE_BNET then
		local friendAccountInfo = C_BattleNet.GetFriendAccountInfo(playerData.id)

		if friendAccountInfo then
			local gameAccountInfo = friendAccountInfo.gameAccountInfo

			if friendAccountInfo.isAFK and gameAccountInfo and gameAccountInfo.isOnline then
				status = "AFK"
			end

			if friendAccountInfo.isDND and gameAccountInfo and gameAccountInfo.isOnline then
				status = "DND"
			end

			if not friendAccountInfo.isAFK and not friendAccountInfo.isDND then
				if gameAccountInfo.isOnline then
					status = "Online"

					if gameAccountInfo.isGameBusy then
						status = "DND"
					end

					if gameAccountInfo.isGameAFK then
						status = "AFK"
					end

					if gameAccountInfo.clientProgram == "BSAp" then
						status = status .. "Mobile"
					end

					if gameAccountInfo.clientProgram == BNET_CLIENT_WOW then
						status = status .. "InGame"
					end
				end
			end
		end
	elseif playerData.buttonType == FRIENDS_BUTTON_TYPE_WOW then
		local friendInfo = C_FriendList.GetFriendInfoByIndex(playerData.id)

		if friendInfo.connected then
			status = "OnlineInGame"

			if friendInfo.dnd then
				status = "DNDInGame"
			end

			if friendInfo.afk then
				status = "AFKInGame"
			end
		end
	end

	return status
end

function FriendGroups_SortTableByStatus(playerA, playerB)
	if not playerA then
		playerA = {}
	end

	if not playerB then
		playerB = {}
	end

	-- 1. Presence tier: actively in WoW first, then otherwise-online, then offline.
	local rankA = playerA.isInGame and 1 or (playerA.isOnline and 2 or 3)
	local rankB = playerB.isInGame and 1 or (playerB.isOnline and 2 or 3)
	if rankA ~= rankB then
		return rankA < rankB
	end

	-- 2. Battle.net friends before WoW (in-game) friends within the same tier
	local typeA = (playerA.buttonType == FRIENDS_BUTTON_TYPE_BNET) and 1 or 2
	local typeB = (playerB.buttonType == FRIENDS_BUTTON_TYPE_BNET) and 1 or 2
	if typeA ~= typeB then
		return typeA < typeB
	end

	-- 3. Alphabetical by display name
	local nameA = playerA.sortName or ""
	local nameB = playerB.sortName or ""
	if nameA ~= nameB then
		return nameA < nameB
	end

	-- 4. Deterministic backstop (unique per friend within the same tier/type)
	return (playerA.id or 0) < (playerB.id or 0)
end

function FriendGroups_SortGroupsCustom(groupA, groupB)
	if groupA == FGL["GROUP_FAVORITES"] then
		return true
	end
    if groupB == FGL["GROUP_FAVORITES"] then
        return false
    end

    local isGuildA = string.find(groupA, FGL["GROUP_GUILDMATES"], 1, true)
    local isGuildB = string.find(groupB, FGL["GROUP_GUILDMATES"], 1, true)

    if isGuildA and not isGuildB then
        return true
    end
    if isGuildB and not isGuildA then
        return false
    end

	if groupA == FGL["GROUP_NONE"] then
		return false
	end
	if groupB == FGL["GROUP_NONE"] then
		return true
	end

	return groupA < groupB
end

local FriendGroups_AssignmentCache = {}
local FriendGroups_CacheGeneration = 0

function FriendGroups_SetGroups(id, buttonType, passedAccountInfo)
    local noteText = ""
    local statusText = "Offline"
    local favorite = false
    local charName, client, isOnline, isRetail, accountIdentifier = "", "", false, false, nil
    local altCacheCount = 0

    -- Inline state resolution to completely bypass redundant API table allocations
    if buttonType == FRIENDS_BUTTON_TYPE_BNET then
        local friendAccountInfo = passedAccountInfo or C_BattleNet.GetFriendAccountInfo(id)
        if friendAccountInfo then
            noteText = friendAccountInfo.note or ""
            favorite = friendAccountInfo.isFavorite
            accountIdentifier = friendAccountInfo.battleTag or friendAccountInfo.accountName

            local gameAccountInfo = friendAccountInfo.gameAccountInfo
            if gameAccountInfo then
                isOnline = gameAccountInfo.isOnline
                client = gameAccountInfo.clientProgram
                isRetail = (gameAccountInfo.wowProjectID == WOW_PROJECT_MAINLINE)
                charName = (type(gameAccountInfo.characterName) == "string") and gameAccountInfo.characterName or ""

                if friendAccountInfo.isAFK and isOnline then statusText = "AFK"
                elseif friendAccountInfo.isDND and isOnline then statusText = "DND"
                elseif isOnline then
                    statusText = "Online"
                    if gameAccountInfo.isGameBusy then statusText = "DND"
                    elseif gameAccountInfo.isGameAFK then statusText = "AFK" end
                    if client == "BSAp" then statusText = statusText .. "Mobile" end
                    if client == BNET_CLIENT_WOW then statusText = statusText .. "InGame" end
                end
            end
            
            if accountIdentifier and type(ShiGuangDB.FriendGroupsalt_cache) == "table" and ShiGuangDB.FriendGroupsalt_cache[accountIdentifier] then
                altCacheCount = #ShiGuangDB.FriendGroupsalt_cache[accountIdentifier]
            end
        end
    elseif buttonType == FRIENDS_BUTTON_TYPE_WOW then
        local info = passedAccountInfo or C_FriendList.GetFriendInfoByIndex(id)
        if info then
            noteText = info.notes or ""
            isOnline = info.connected
            client = BNET_CLIENT_WOW
            charName = (type(info.name) == "string") and info.name or ""

            if isOnline then
                statusText = "OnlineInGame"
                if info.dnd then statusText = "DNDInGame"
                elseif info.afk then statusText = "AFKInGame" end
            end
        end
    end

    if type(noteText) ~= "string" then noteText = "" end

    -- [[ OPTIMIZATION: Use Unique Identifier for Cache instead of dynamic List Index ]]
    local uniqueID = id
    if buttonType == FRIENDS_BUTTON_TYPE_BNET and accountIdentifier then
        uniqueID = accountIdentifier
    elseif buttonType == FRIENDS_BUTTON_TYPE_WOW and charName ~= "" then
        uniqueID = charName
    end
    
    local cacheKey = buttonType .. "_" .. tostring(uniqueID)
    local cache = FriendGroups_AssignmentCache[cacheKey]
    
    local isCacheValid = cache 
        and cache.noteText == noteText 
        and cache.statusText == statusText 
        and cache.favorite == favorite 
        and cache.charName == charName 
        and cache.isOnline == isOnline 
        and cache.altCacheCount == altCacheCount
        and cache.guildSetting == ShiGuangDB.FriendGroupsshow_guildmates
        and cache.offlineSetting == ShiGuangDB.FriendGroupsoffline_tracker
        and cache.favSetting == ShiGuangDB.FriendGroupsadd_favorite_group

    if not isCacheValid then
        if buttonType == FRIENDS_BUTTON_TYPE_BNET and accountIdentifier then
            local friendAccountInfo = passedAccountInfo or C_BattleNet.GetFriendAccountInfo(id)
            if friendAccountInfo and friendAccountInfo.gameAccountInfo then
                FriendGroups_SaveToAltCache(accountIdentifier, friendAccountInfo.gameAccountInfo)
                if C_BattleNet.GetFriendNumGameAccounts then
                    local numAccounts = C_BattleNet.GetFriendNumGameAccounts(id)
                    if numAccounts and numAccounts > 1 then
                        for i = 1, numAccounts do
                            local gameAccountInfo = C_BattleNet.GetFriendGameAccountInfo(id, i)
                            if gameAccountInfo then
                                FriendGroups_SaveToAltCache(accountIdentifier, gameAccountInfo)
                            end
                        end
                    end
                end
            end
        end

        local resolvedGroups = {}
        local parsedGroups = FriendGroups_GetPlayerGroups(noteText)
        
        for _, g in ipairs(parsedGroups) do table.insert(resolvedGroups, g) end

        if ShiGuangDB.FriendGroupsadd_favorite_group and favorite then
            table.insert(resolvedGroups, FGL["GROUP_FAVORITES"])
        end

        local akaName, akaClass = nil, nil

        if ShiGuangDB.FriendGroupsshow_guildmates then
            local hasManualGuild = false
            if noteText ~= "" then
                for manualGuildName in string.gmatch(noteText, "<([^>]+)>") do
                    manualGuildName = string.match(manualGuildName, "^%s*(.-)%s*$")
                    if manualGuildName and #manualGuildName >= 2 then
                        local formattedGuildGroup = string.format(FGL["FORMAT_GUILD_TAG"], FGL["GROUP_GUILDMATES"], manualGuildName)
                        local alreadyExists = false
                        for _, g in ipairs(resolvedGroups) do
                            if g == formattedGuildGroup then alreadyExists = true break end
                        end
                        if not alreadyExists then
                            table.insert(resolvedGroups, formattedGuildGroup)
                            hasManualGuild = true
                        end
                    end
                end
            end
            
            local matchedGuild = nil
            if buttonType == FRIENDS_BUTTON_TYPE_BNET then
                if accountIdentifier and ShiGuangDB.FriendGroupsalt_cache and ShiGuangDB.FriendGroupsalt_cache[accountIdentifier] then
                    local bestAlt = nil
                    local highestTimestamp = 0
                    for _, alt in ipairs(ShiGuangDB.FriendGroupsalt_cache[accountIdentifier]) do
                        local altTime = alt.timestamp or 0
                        if altTime >= highestTimestamp then
                            highestTimestamp = altTime
                            bestAlt = alt
                        end
                    end
                    if bestAlt then
                        akaName = bestAlt.charName
                        akaClass = bestAlt.class
                    end
                end

                if not hasManualGuild then
                    if isOnline and client == BNET_CLIENT_WOW and isRetail and charName ~= "" then
                        local friendAccountInfo = passedAccountInfo or C_BattleNet.GetFriendAccountInfo(id)
                        if friendAccountInfo and friendAccountInfo.gameAccountInfo then
                            local rName = (type(friendAccountInfo.gameAccountInfo.realmName) == "string") and friendAccountInfo.gameAccountInfo.realmName or ""
                            local fullName = charName
                            if rName ~= "" then fullName = charName .. "-" .. FriendGroups_CleanRealmName(rName) end

                            if FriendGroups_LiveGuildSessionDict[fullName] or FriendGroups_LiveGuildSessionDict[charName] then
                                matchedGuild = FriendGroups_PlayerGuildName
                            end
                        end
                    end
                    
                    if not matchedGuild and accountIdentifier and ShiGuangDB.FriendGroupsalt_cache and ShiGuangDB.FriendGroupsalt_cache[accountIdentifier] then
                        for _, alt in ipairs(ShiGuangDB.FriendGroupsalt_cache[accountIdentifier]) do
                            if alt.guild and alt.guild ~= "" and alt.guild ~= "NONE" and alt.guild ~= "-" then
                                if ShiGuangDB.FriendGroupsknown_player_guilds and ShiGuangDB.FriendGroupsknown_player_guilds[alt.guild] then
                                    matchedGuild = alt.guild
                                    break
                                end
                            end
                            if FriendGroups_LiveGuildSessionDict[alt.key] or FriendGroups_LiveGuildSessionDict[alt.charName] then
                                matchedGuild = FriendGroups_PlayerGuildName
                                break
                            end
                        end
                    end
                end
            elseif buttonType == FRIENDS_BUTTON_TYPE_WOW then
                if not hasManualGuild and charName ~= "" then
                    if FriendGroups_LiveGuildSessionDict[charName] then
                        matchedGuild = FriendGroups_PlayerGuildName
                    end
                end
            end
            
            if matchedGuild then
                local formattedGuildGroup = string.format(FGL["FORMAT_GUILD_TAG"], FGL["GROUP_GUILDMATES"], matchedGuild)
                local alreadyExists = false
                for _, g in ipairs(resolvedGroups) do
                    if g == formattedGuildGroup then alreadyExists = true break end
                end
                if not alreadyExists then
                    table.insert(resolvedGroups, formattedGuildGroup)
                end
            end
        end

        if ShiGuangDB.FriendGroupsoffline_tracker and buttonType == FRIENDS_BUTTON_TYPE_BNET and statusText == "Offline" then
            local friendAccountInfo = passedAccountInfo or C_BattleNet.GetFriendAccountInfo(id)
            if friendAccountInfo and friendAccountInfo.lastOnlineTime and friendAccountInfo.lastOnlineTime > 0 then
                local daysOffline = (time() - friendAccountInfo.lastOnlineTime) / 86400
                if daysOffline >= 90 then table.insert(resolvedGroups, FGL["GROUP_OFFLINE_3"])
                elseif daysOffline >= 60 then table.insert(resolvedGroups, FGL["GROUP_OFFLINE_2"])
                elseif daysOffline >= 30 then table.insert(resolvedGroups, FGL["GROUP_OFFLINE_1"]) end
            end
        end

        if #resolvedGroups == 0 then table.insert(resolvedGroups, FGL["GROUP_NONE"]) end
        
        FriendGroups_AssignmentCache[cacheKey] = {
            noteText = noteText,
            statusText = statusText,
            favorite = favorite,
            charName = charName,
            isOnline = isOnline,
            altCacheCount = altCacheCount,
            guildSetting = ShiGuangDB.FriendGroupsshow_guildmates,
            offlineSetting = ShiGuangDB.FriendGroupsoffline_tracker,
            favSetting = ShiGuangDB.FriendGroupsadd_favorite_group,
            groups = resolvedGroups,
            akaName = akaName,
            akaClass = akaClass
        }
        
        cache = FriendGroups_AssignmentCache[cacheKey]
    end

	-- [[ CACHE PRUNE SUPPORT ]]
    -- Mark this entry as live for the current rebuild generation. Entries for friends who are
    -- no longer present keep an older generation and are pruned at the end of FriendsListUpdate.
    cache.generation = FriendGroups_CacheGeneration
	
    if accountIdentifier then
        FriendGroups_ActiveAKA[accountIdentifier] = nil
        if cache.akaName then
            FriendGroups_ActiveAKA[accountIdentifier] = { name = cache.akaName, class = cache.akaClass }
        end
    end

    for _, groupName in ipairs(cache.groups) do
        local addToTable = false

        if not groupsTotal[groupName] then
            groupsTotal[groupName] = FG_GetTable()
            groupsCount[groupName] = FG_GetTable()
            groupsCount[groupName].Total = 0
            groupsCount[groupName].Online = 0
            table.insert(groupsSorted, groupName)
        end

        if isOnline then
            if (ShiGuangDB.FriendGroupshide_afk and statusText ~= "AFK" and statusText ~= "AFKMobile") or not ShiGuangDB.FriendGroupshide_afk then
                if (ShiGuangDB.FriendGroupsingame_only and client == BNET_CLIENT_WOW) or not ShiGuangDB.FriendGroupsingame_only then
                    if ShiGuangDB.FriendGroupsshow_retail and client == BNET_CLIENT_WOW then
                        if isRetail then addToTable = true end
                    else
                        addToTable = true
                    end
                end
            end
        else
            if not ShiGuangDB.FriendGroupshide_offline and ((ShiGuangDB.FriendGroupsingame_only and client == BNET_CLIENT_WOW) or not ShiGuangDB.FriendGroupsingame_only) then
                addToTable = true
            end
        end

        if searchValue ~= "" then
            addToTable = FriendGroups_Search(id, buttonType, passedAccountInfo)
        end

        if addToTable then
            groupsCount[groupName].Total = groupsCount[groupName].Total + 1
            if statusText ~= "Offline" then
                groupsCount[groupName].Online = groupsCount[groupName].Online + 1
            end
            
			local playerData = FG_GetTable()
            playerData.id = id
            playerData.buttonType = buttonType
            playerData.statusText = statusText

            -- [[ PRESENCE FLAGS (drive the primary sort tier) ]]
            -- isInGame: online AND active client is WoW (actively playing) -> top tier.
            -- isOnline: online in any client (WoW, app, mobile, other game) -> middle tier.
            -- Offline (neither flag) -> bottom tier.
            playerData.isOnline = isOnline
            playerData.isInGame = isOnline and (client == BNET_CLIENT_WOW)

            -- [[ ALPHABETICAL SORT KEY ]]
            -- BNet -> account identifier (BattleTag; its name prefix is the displayed bold name).
            -- WoW  -> character name. Lowercased for case-insensitive ordering. Consumed by the
            -- secondary sort in FriendGroups_SortTableByStatus.
            local sortName = charName
            if buttonType == FRIENDS_BUTTON_TYPE_BNET and accountIdentifier then
                sortName = accountIdentifier
            end
            playerData.sortName = (sortName or ""):lower()

            table.insert(groupsTotal[groupName], playerData)
		end
    end
end

function FriendGroups_Search(playerId, playerButtonType, passedAccountInfo)
    if searchValue == "" then return true end

    local characterName, bnetAccountName, battleTag, noteText, realmName, className, richPresence, regionSearchText, factionSearchText = "", "", "", "", "", "", "", "", ""
    local classMatch = false
    local altMatch = false
    local matchedGuildName = ""
    local hasManualGuild = false
    local searchLower = searchLowerValue
    local searchLen = #searchLower

    if playerButtonType == FRIENDS_BUTTON_TYPE_BNET then
        -- Fast Path: Use passed memory reference instead of querying the API
        local accountInfo = passedAccountInfo or C_BattleNet.GetFriendAccountInfo(playerId)
        if accountInfo and accountInfo.gameAccountInfo then
            bnetAccountName = (type(accountInfo.accountName) == "string") and accountInfo.accountName or ""
            battleTag = (type(accountInfo.battleTag) == "string") and accountInfo.battleTag or ""
            noteText = (type(accountInfo.note) == "string") and accountInfo.note or ""
            characterName = (type(accountInfo.gameAccountInfo.characterName) == "string") and accountInfo.gameAccountInfo.characterName or ""
            realmName = (type(accountInfo.gameAccountInfo.realmName) == "string") and accountInfo.gameAccountInfo.realmName or ""
            className = (type(accountInfo.gameAccountInfo.className) == "string") and accountInfo.gameAccountInfo.className or ""
            richPresence = (type(accountInfo.gameAccountInfo.richPresence) == "string") and accountInfo.gameAccountInfo.richPresence or ""
            factionSearchText = (type(accountInfo.gameAccountInfo.factionName) == "string") and accountInfo.gameAccountInfo.factionName or ""

            local rid = accountInfo.gameAccountInfo.regionID
            local database = (rid == 3) and FriendGroups_RealmDataEU or FriendGroups_RealmData
            
            if realmName ~= "" then
                local cleanRealm = FriendGroups_CleanRealmName(realmName)
                local data = database[cleanRealm]
                if data and data.region then
                    regionSearchText = data.region
                end
            end
            
            local accountIdentifier = accountInfo.battleTag or accountInfo.accountName
            if accountIdentifier and type(ShiGuangDB.FriendGroupsalt_cache) == "table" then
                local alts = ShiGuangDB.FriendGroupsalt_cache[accountIdentifier]
                if alts then
                    for _, alt in ipairs(alts) do
                        local aName = (type(alt.searchName) == "string") and alt.searchName or (type(alt.charName) == "string" and alt.charName:lower() or "")
                        local aRealm = (type(alt.searchRealm) == "string") and alt.searchRealm or (type(alt.realm) == "string" and alt.realm:lower() or "")
                        local aClass = (type(alt.searchClass) == "string") and alt.searchClass or (type(alt.class) == "string" and alt.class:lower() or "")
                        local aZone = (type(alt.searchZone) == "string") and alt.searchZone or (type(alt.zone) == "string" and alt.zone:lower() or "")
                        
                        if (aName:find(searchLower, 1, true)) or 
                           (aRealm:find(searchLower, 1, true)) or 
                           (aZone:find(searchLower, 1, true)) or 
                           (aClass ~= "" and aClass:sub(1, searchLen) == searchLower) then
                            altMatch = true
                            break
                        end
                    end
                end
            end
            
            if ShiGuangDB.FriendGroupsshow_guildmates then
                if noteText ~= "" then
                    for manualGuildName in string.gmatch(noteText, "<([^>]+)>") do
                        manualGuildName = string.match(manualGuildName, "^%s*(.-)%s*$")
                        if manualGuildName and #manualGuildName >= 2 then
                            if manualGuildName:lower():find(searchLower, 1, true) then
                                matchedGuildName = manualGuildName
                            end
                            hasManualGuild = true
                        end
                    end
                end
                
                if not hasManualGuild then
                    if accountInfo.gameAccountInfo.isOnline and accountInfo.gameAccountInfo.clientProgram == BNET_CLIENT_WOW and accountInfo.gameAccountInfo.wowProjectID == WOW_PROJECT_MAINLINE then
                        local cName = characterName
                        local rName = realmName
                        if cName ~= "" then
                            local fullName = cName
                            if rName ~= "" then fullName = cName .. "-" .. FriendGroups_CleanRealmName(rName) end
                            
                            if FriendGroups_LiveGuildSessionDict[fullName] or FriendGroups_LiveGuildSessionDict[cName] then
                                matchedGuildName = FriendGroups_PlayerGuildName
                            end
                        end
                    end
                end
            end
        end
    elseif playerButtonType == FRIENDS_BUTTON_TYPE_WOW then
        local info = passedAccountInfo or C_FriendList.GetFriendInfoByIndex(playerId)
        if info then
            characterName = (type(info.name) == "string") and info.name or ""
            noteText = (type(info.notes) == "string") and info.notes or ""
            className = (type(info.className) == "string") and info.className or ""
            factionSearchText = (type(playerFactionGroup) == "string") and playerFactionGroup or ""
            regionSearchText = "Local"
            
            if ShiGuangDB.FriendGroupsshow_guildmates then
                if noteText ~= "" then
                    for manualGuildName in string.gmatch(noteText, "<([^>]+)>") do
                        manualGuildName = string.match(manualGuildName, "^%s*(.-)%s*$")
                        if manualGuildName and #manualGuildName >= 2 then
                            if manualGuildName:lower():find(searchLower, 1, true) then
                                matchedGuildName = manualGuildName
                            end
                            hasManualGuild = true
                        end
                    end
                end
                
                if not hasManualGuild and characterName ~= "" then
                    if FriendGroups_LiveGuildSessionDict[characterName] then
                        matchedGuildName = FriendGroups_PlayerGuildName
                    end
                end
            end
        end
    end

    if className ~= "" and className:lower():sub(1, searchLen) == searchLower then classMatch = true end

    if (bnetAccountName:lower():find(searchLower, 1, true)) or 
       (battleTag:lower():find(searchLower, 1, true)) or 
       (characterName:lower():find(searchLower, 1, true)) or
       (noteText:lower():find(searchLower, 1, true)) or
       (realmName:lower():find(searchLower, 1, true)) or
       (richPresence:lower():find(searchLower, 1, true)) or 
       (regionSearchText:lower():find(searchLower, 1, true)) or 
       (factionSearchText:lower():find(searchLower, 1, true)) or 
       (matchedGuildName ~= "" and matchedGuildName:lower():find(searchLower, 1, true)) or
       classMatch or altMatch then
        return true
    end
    return false
end

function FriendGroups_AddDropDownNew(ownerRegion, rootDescription, contextData)
    if not contextData then return end
    if not FriendsListFrame or not FriendsListFrame:IsMouseOver() then
        return 
    end

    local bnetfriend = false
    local accountInfo = nil
    local bnetIDAccount = contextData.bnetIDAccount
    local wowName = contextData.name

    if bnetIDAccount then
        bnetfriend = true
        accountInfo = C_BattleNet.GetAccountInfoByID(bnetIDAccount)
    elseif wowName then
        bnetfriend = false
        accountInfo = C_FriendList.GetFriendInfo(wowName)
    else
        return
    end

    if not accountInfo then return end

    local note = ""
    if bnetfriend then
        note = accountInfo.note
    else
        note = accountInfo.notes
    end

    local groups = FriendGroups_GetPlayerGroups(note)

    rootDescription:CreateDivider()
    rootDescription:CreateTitle(FGL["DROP_TITLE"])

    rootDescription:CreateButton(FGL["DROP_COPY_NAME"], function(data)
        local textToCopy = ""
        if data.bnetfriend then
            local info = C_BattleNet.GetAccountInfoByID(data.id)
            if info and info.gameAccountInfo and info.gameAccountInfo.characterName then
                local char = info.gameAccountInfo.characterName
                local realm = info.gameAccountInfo.realmName
                local richPresence = info.gameAccountInfo.richPresence
                local friendProject = info.gameAccountInfo.wowProjectID
                local myProject = WOW_PROJECT_ID

                if not realm or realm == "" then
                    realm = info.gameAccountInfo.realmDisplayName
                end
                
                if (not realm or realm == "") and richPresence and type(richPresence) == "string" then
                    local extraction = richPresence:match("%s%-%s(.+)")
                    if extraction then
                        realm = extraction
                    end
                end
                if (not realm or realm == "") and info.gameAccountInfo.clientProgram == BNET_CLIENT_WOW then
                    if friendProject == myProject then
                        realm = GetNormalizedRealmName()
                    end
                end

                if realm and realm ~= "" and type(realm) == "string" then
                    realm = realm:gsub("[%s%p]+", "") 
                    textToCopy = char .. "-" .. realm
                else
                    textToCopy = char
                end
            elseif info and info.battleTag and type(info.battleTag) == "string" then
                textToCopy = info.battleTag
            end
		else
            textToCopy = data.name
            if textToCopy and textToCopy ~= "" and type(textToCopy) == "string" and not string.find(textToCopy, "-") then
                local myRealm = GetNormalizedRealmName()
                if type(myRealm) == "string" and myRealm ~= "" then
                    textToCopy = textToCopy .. "-" .. myRealm:gsub("[%s%p]+", "")
                end
            end
        end
		
        if textToCopy and textToCopy ~= "" then
            local dialog = StaticPopup_Show("FRIENDGROUPS_COPY_POPUP")
            if dialog and dialog.EditBox then
                dialog.EditBox:SetText(textToCopy)
                dialog.EditBox:HighlightText()
                dialog.EditBox:SetFocus()
            end
        end
    end, { id = bnetIDAccount, name = wowName, bnetfriend = bnetfriend })

    if bnetfriend then
        rootDescription:CreateButton(FGL["DROP_COPY_BTAG"], function(data)
            local info = C_BattleNet.GetAccountInfoByID(data.id)
            if info and info.battleTag and type(info.battleTag) == "string" then
                local dialog = StaticPopup_Show("FRIENDGROUPS_COPY_POPUP")
                if dialog and dialog.EditBox then
                    dialog.EditBox:SetText(info.battleTag)
                    dialog.EditBox:HighlightText()
                    dialog.EditBox:SetFocus()
                end
            end
        end, { id = bnetIDAccount })
    end

    rootDescription:CreateButton(FGL["DROP_CREATE"], function(data)
        if data.bnetfriend then
            local info = C_BattleNet.GetAccountInfoByID(data.id)
            if info then
                StaticPopup_Show("FRIENDGROUPS_CREATE", nil, nil, { id = info.bnetAccountID, note = info.note, set = function(id, note)
                    if C_BattleNet and C_BattleNet.SetFriendNote then
                        C_BattleNet.SetFriendNote(id, note)
                    else
                        BNSetFriendNote(id, note)
                    end
                end })
            end
        else
            local info = C_FriendList.GetFriendInfo(data.name)
            if info then
                StaticPopup_Show("FRIENDGROUPS_CREATE", nil, nil, { name = data.name, note = info.notes, set = C_FriendList.SetFriendNotes })
            end
        end
    end, { id = bnetIDAccount, name = wowName, bnetfriend = bnetfriend })

    local add = rootDescription:CreateButton(FGL["DROP_ADD"])

	for _, group in ipairs(groupsSorted) do
        local isGuildGroup = string.find(group, FGL["GROUP_GUILDMATES"], 1, true)
        if not FriendGroups_HasValue(groups, group) and group ~= "" and group ~= FGL["GROUP_FAVORITES"] and group ~= FGL["GROUP_EMPTY"] and group ~= FGL["GROUP_NONE"] 
           and group ~= FGL["GROUP_OFFLINE_1"] and group ~= FGL["GROUP_OFFLINE_2"] and group ~= FGL["GROUP_OFFLINE_3"] and not isGuildGroup then
		   add:CreateButton(group, function(data)
                local newNote = FriendGroups_AddGroup(data.note, data.group)
                if data.bnetfriend then
                    if C_BattleNet and C_BattleNet.SetFriendNote then
                        C_BattleNet.SetFriendNote(data.id, newNote)
                    else
                        BNSetFriendNote(data.id, newNote)
                    end
                else
                    C_FriendList.SetFriendNotes(data.name, newNote)
                end
            end, { group = group, note = note, id = bnetIDAccount, name = wowName, bnetfriend = bnetfriend })
        end
    end

    local remove = rootDescription:CreateButton(FGL["DROP_REMOVE"])

	for _, group in ipairs(groupsSorted) do
        local isGuildGroup = string.find(group, FGL["GROUP_GUILDMATES"], 1, true)
        if FriendGroups_HasValue(groups, group) 
           and group ~= FGL["GROUP_FAVORITES"] and group ~= FGL["GROUP_OFFLINE_1"] and group ~= FGL["GROUP_OFFLINE_2"] and group ~= FGL["GROUP_OFFLINE_3"] and not isGuildGroup then
		   remove:CreateButton(group, function(data)
                local newNote = FriendGroups_RemoveGroup(data.note, data.group)
                if data.bnetfriend then
                    if C_BattleNet and C_BattleNet.SetFriendNote then
                        C_BattleNet.SetFriendNote(data.id, newNote)
                    else
                        BNSetFriendNote(data.id, newNote)
                    end
                else
                    C_FriendList.SetFriendNotes(data.name, newNote)
                end
            end, { group = group, note = note, id = bnetIDAccount, name = wowName, bnetfriend = bnetfriend })
        end
    end
end

function FriendGroups_FrameFriendDividerTemplateHeaderClick(self, button, down)
    local frame = self
    if not frame.name then
        frame = self:GetParent()
    end

    if not frame or not frame.name then return end

    local groupName = frame.rawGroupName or frame.name:GetText()

    if groupName == "Search..." then
        if _G["FriendGroupsSearch"] then
            _G["FriendGroupsSearch"]:SetFocus()
        end
    elseif button == "RightButton" then
        if groupName then
            MenuUtil.CreateContextMenu(self, function(ownerRegion, rootDescription)
                local displayTitle = frame.name:GetText() or groupName
                rootDescription:CreateTitle(displayTitle)
                
                for i = 2, #groupMenuItems do
                    local item = groupMenuItems[i]
                    local disabled = false
                    local text = item.text
                    
                    local isGuildGroup = string.find(groupName, FGL["GROUP_GUILDMATES"], 1, true)

					if groupName == FGL["GROUP_NONE"] or groupName == FGL["GROUP_FAVORITES"] or groupName == FGL["GROUP_EMPTY"] or groupName == "" 
					   or groupName == FGL["GROUP_OFFLINE_1"] or groupName == FGL["GROUP_OFFLINE_2"] or groupName == FGL["GROUP_OFFLINE_3"] or isGuildGroup then
						if text == FGL["MENU_RENAME"] or text == FGL["MENU_REMOVE"] then
							disabled = true
						end
						if text == FGL["MENU_INVITE"] and groupName == FGL["GROUP_EMPTY"] then
							disabled = true
						end
					end
	
                    if text == FGL["MENU_INVITE"] then
                        if groupsCount[groupName] and groupsCount[groupName].Total > 40 then
                            disabled = true
                            text = text .. FGL["MENU_MAX_40"]
                        end
                    end

                    local btn = rootDescription:CreateButton(text, function()
                        item.func(nil, groupName)
                    end)
                    
                    if disabled then
                        btn:SetEnabled(false)
                    end
                end
            end)
        end
    else
        FriendGroups_FrameFriendDividerTemplateCollapseClick(self, button, down)
    end
end

function FriendGroups_GetFriendNote(id, buttonType)
	local noteText = ""

	if buttonType == FRIENDS_BUTTON_TYPE_BNET then
		local accountInfo = C_BattleNet.GetFriendAccountInfo(id)

		if accountInfo then
			noteText = accountInfo.note
		end
	elseif buttonType == FRIENDS_BUTTON_TYPE_WOW then
		noteText = C_FriendList.GetFriendInfoByIndex(id) and C_FriendList.GetFriendInfoByIndex(id).notes
	end

	return noteText
end

function FriendGroups_GetFriendFavorite(id, buttonType)
	local isFavorite = false

	if buttonType == FRIENDS_BUTTON_TYPE_BNET then
		local accountInfo = C_BattleNet.GetFriendAccountInfo(id)

		if accountInfo then
			isFavorite = accountInfo.isFavorite
		end
	end

	return isFavorite
end

function FriendGroups_SetGroupsCount()
	for groupName, groupData in pairs(groupsTotal) do
		if not groupsCount[groupName] then
			groupsCount[groupName] = {}
		end

		groupsCount[groupName].Total = 0
		groupsCount[groupName].Online = 0

		for _, playerData in ipairs(groupData) do
			local statusText = FriendGroups_GetStatusString(playerData)

			groupsCount[groupName].Total = groupsCount[groupName].Total + 1
			if statusText ~= "Offline" then
				groupsCount[groupName].Online = groupsCount[groupName].Online + 1
			end
		end
	end
end

-- ============================================================================
-- [[ FRIEND GROUPS FRAME (STATE) ]]
-- ============================================================================
FriendGroupsFrame = CreateFrame("Frame", "FriendGroupsFrame")
FriendGroupsFrame.selectionLocked = false

-- Note: We no longer need the global UIDropDownMenu templates or initialization 
-- functions here because we are using the modern inline MenuUtil API below.


-- Popups
StaticPopupDialogs["FRIENDGROUPS_CREATE"] = {
	text = FGL["POPUP_ENTER_NAME"],
	button1 = ACCEPT,
	button2 = CANCEL,
	hasEditBox = 1,
	OnAccept = function(self)
		local parent = self:GetEditBox():GetParent()
		FriendGroups_Create(parent, parent.data)
		parent:Hide()
	end,
	EditBoxOnEnterPressed = function(self)
		local parent = self:GetParent()
		FriendGroups_Create(parent, parent.data)
		parent:Hide()
	end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1
}
StaticPopupDialogs["FRIENDGROUPS_RENAME"] = {
	text = FGL["POPUP_ENTER_NAME"],
	button1 = ACCEPT,
	button2 = CANCEL,
	hasEditBox = 1,
	OnAccept = function(self)
		local parent = self:GetEditBox():GetParent()
		FriendGroups_Rename(parent, parent.data)
		parent:Hide()
	end,
	EditBoxOnEnterPressed = function(self)
		local parent = self:GetParent()
		FriendGroups_Rename(parent, parent.data)
		parent:Hide()
	end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1
}

-- [[ NEW POPUP: COPY TEXT ]] --
StaticPopupDialogs["FRIENDGROUPS_COPY_POPUP"] = {
	text = FGL["POPUP_COPY"],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1,
	hasEditBox = 1,
	preferredIndex = 3,
	OnShow = function(self)
        -- FIX: Use .EditBox (Capital E)
		if self.EditBox then
			self.EditBox:SetFocus()
			self.EditBox:HighlightText()
		end
	end,
	EditBoxOnEnterPressed = function(self)
		self:GetParent():Hide()
	end,
	EditBoxOnEscapePressed = function(self)
		self:GetParent():Hide()
	end,
}

--[[
	Functions
]] --

function FriendGroups_PurgeStaleAlts()

    -- 14-Day Rolling TTL for Local BNet Cache
    if type(ShiGuangDB.FriendGroupsalt_cache) == "table" then
        local currentTime = time()
        local maxAge = 1209600 -- 14 days in seconds

        for accountIdentifier, altList in pairs(ShiGuangDB.FriendGroupsalt_cache) do
            -- Iterate backwards to safely remove items from a shifting array
            for i = #altList, 1, -1 do
                local alt = altList[i]
                if alt.timestamp and (currentTime - alt.timestamp > maxAge) then
                    table.remove(altList, i)
                end
            end
            
            -- Clean up orphaned accounts
            if #altList == 0 then
                ShiGuangDB.FriendGroupsalt_cache[accountIdentifier] = nil
            end
        end
    end
end

-- [[ CORE ACTIVATOR ]] --
EnableFriendGroups = function()
    if FriendGroups_Loaded then return end
    FriendGroups_Loaded = true
    
		if ShiGuangDB["FriendGroupscollapsed"] == nil then ShiGuangDB["FriendGroupscollapsed"] = {} end
		if ShiGuangDB["FriendGroupshide_offline"] == nil then ShiGuangDB["FriendGroupshide_offline"] = true end
		if ShiGuangDB["FriendGroupscolour_classes"] == nil then ShiGuangDB["FriendGroupscolour_classes"] = true end
		if ShiGuangDB["FriendGroupsshow_faction_icons"] == nil then ShiGuangDB["FriendGroupsshow_faction_icons"] = true end
		if ShiGuangDB["FriendGroupsshow_realm"] == nil then ShiGuangDB["FriendGroupsshow_realm"] = true end
		if ShiGuangDB["FriendGroupshide_high_level"] == nil then ShiGuangDB["FriendGroupshide_high_level"] = false end
		if ShiGuangDB["FriendGroupsadd_favorite_group"] == nil then ShiGuangDB["FriendGroupsadd_favorite_group"] = true end
		if ShiGuangDB["FriendGroupsgray_faction"] == nil then ShiGuangDB["FriendGroupsgray_faction"] = false end
		if ShiGuangDB["FriendGroupsshow_mobile_afk"] == nil then ShiGuangDB["FriendGroupsshow_mobile_afk"] = false end
		if ShiGuangDB["FriendGroupsadd_mobile_text"] == nil then ShiGuangDB["FriendGroupsadd_mobile_text"] = true end
		if ShiGuangDB["FriendGroupsingame_only"] == nil then ShiGuangDB["FriendGroupsingame_only"] = true end
		if ShiGuangDB["FriendGroupsingame_retail"] == nil then ShiGuangDB["FriendGroupsingame_retail"] = false end
		if ShiGuangDB["FriendGroupsshow_btag"] == nil then ShiGuangDB["FriendGroupsshow_btag"] = false end
		if ShiGuangDB["FriendGroupsshow_retail"] == nil then ShiGuangDB["FriendGroupsshow_retail"] = false end
		if ShiGuangDB["FriendGroupsshow_search"] == nil then ShiGuangDB["FriendGroupsshow_search"] = false end
		if ShiGuangDB["FriendGroupshide_empty_groups"] == nil then ShiGuangDB["FriendGroupshide_empty_groups"] = true end
		if ShiGuangDB["FriendGroupshide_afk"] == nil then ShiGuangDB["FriendGroupshide_afk"] = false end
		if ShiGuangDB["FriendGroupsopen_one_group"] == nil then ShiGuangDB["FriendGroupshide_afk"] = false end
		if ShiGuangDB["FriendGroupsauto_accept_invite"] == nil then ShiGuangDB["FriendGroupsauto_accept_invite"] = false end
		if ShiGuangDB["FriendGroupsauto_accept_sync"] == nil then ShiGuangDB["FriendGroupsauto_accept_sync"] = false end
		if ShiGuangDB["FriendGroupsauto_accept_res"] == nil then ShiGuangDB["FriendGroupshide_afk"] = false end
		if ShiGuangDB["FriendGroupsshow_flags"] == nil then ShiGuangDB["FriendGroupsshow_flags"] = true end
		if ShiGuangDB["FriendGroupsoffline_tracker"] == nil then ShiGuangDB["FriendGroupssoffline_tracker"] = true end

    -- [[ DEFAULTS MIGRATION FOR EXISTING USERS ]] --
    if ShiGuangDB.FriendGroupsshow_flags == nil then
        ShiGuangDB.FriendGroupsshow_flags = true
    end
    if ShiGuangDB.FriendGroupsshow_contact_cap == nil then
        ShiGuangDB.FriendGroupsshow_contact_cap = true
    end
    if ShiGuangDB.FriendGroupsoffline_tracker == nil then
        ShiGuangDB.FriendGroupsoffline_tracker = true
    end

-- 1. Create Search Box
    FriendGroups_SearchBox = CreateFrame("EditBox", "FriendGroupsGlobalSearch", FriendsListFrame, "SearchBoxTemplate")
    FriendGroups_SearchBox:SetSize(20, 20)
    FriendGroups_SearchBox:SetPoint("TOPLEFT", FriendsListFrame, "TOPLEFT", 15, -85) 
    FriendGroups_SearchBox:SetPoint("TOPRIGHT", FriendsListFrame, "TOPRIGHT", -90, -85)
    FriendGroups_SearchBox:SetAutoFocus(false)
    FriendGroups_SearchBox.Instructions:SetText(FGL["SEARCH_PLACEHOLDER"])
    
    FriendGroups_SearchBox:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(SEARCH, 1, 1, 1) 
        GameTooltip:AddLine(FGL["SEARCH_TOOLTIP"], nil, nil, nil, true) 
        GameTooltip:Show()
    end)

    FriendGroups_SearchBox:SetScript("OnLeave", function(self)
        GameTooltip:Hide()
    end)

    local FriendGroups_SearchDebounceTimer = nil
FriendGroups_SearchBox:SetScript("OnTextChanged", function(self)
    SearchBoxTemplate_OnTextChanged(self)
    local text = self:GetText()
    if text ~= searchValue then
        searchValue = text
        searchLowerValue = text:lower() -- Cache it here once!
        
        if FriendGroups_SearchDebounceTimer then
            FriendGroups_SearchDebounceTimer:Cancel()
        end
        FriendGroups_SearchDebounceTimer = C_Timer.NewTimer(0.3, function()
            FriendGroups_FriendsListUpdate(true)
            FriendGroups_SearchDebounceTimer = nil
        end)
    end
end)
    
    -- 2. Create Contact Text Tracker
    FriendGroups_ContactText = FriendsListFrame:CreateFontString("FriendGroupsContactText", "OVERLAY", "GameFontNormalSmall")
    FriendGroups_ContactText:SetPoint("LEFT", FriendGroups_SearchBox, "RIGHT", 8, 0)
	
	-- 3. Create Settings Button
    FriendGroupsGlobalSettings = CreateFrame("Button", "FriendGroupsGlobalSettings", FriendsListFrame)
    FriendGroupsGlobalSettings:SetSize(21, 21)
    FriendGroupsGlobalSettings:SetPoint("TOPRIGHT", FriendsListFrame, "TOPRIGHT", -8, -66)
    FriendGroupsGlobalSettings:SetNormalTexture("Interface\\Buttons\\UI-OptionsButton")
    FriendGroupsGlobalSettings:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD")
	
    FriendGroupsGlobalSettings:SetScript("OnClick", function(self)
        MenuUtil.CreateContextMenu(self, function(ownerRegion, rootDescription)
            for _, item in ipairs(settingsMenuItems) do
                if item.isTitle then
                    if item.text == "" then
                        rootDescription:CreateDivider()
                    else
                        rootDescription:CreateTitle(item.text)
                    end
                elseif item.notCheckable then
                    -- Standard buttons (like Reset or Size)
                    rootDescription:CreateButton(item.text, function()
                        -- Run the function. (Modern menus auto-close on button clicks)
                        if item.func then item.func() end
                    end)
                else
                    -- Checkboxes (Filters, Appearance, Group Behavior)
                    rootDescription:CreateCheckbox(
                        item.text,
                        function() return item.checked() end,
                        function() item.func() end
                    )
                end
            end
        end)
    end)
    
    --[[FriendGroupsGlobalSettings:SetScript("OnEnter", function(self) 
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(FGL["SETTINGS_TITLE"] or "Settings", 1, 1, 1)
        GameTooltip:Show()
    end)
    
    FriendGroupsGlobalSettings:SetScript("OnLeave", function() GameTooltip:Hide() end)]]
	
	-- 3. Apply Hooks
    -- [[ OVERWRITE NATIVE LIST UPDATE TO PREVENT SCROLL JUMPING DESYNC ]]
    local original_FriendsList_Update = FriendsList_Update
	FriendsList_Update = function()
        -- Route through the coalescer so bursts of roster events collapse into one refresh.
        -- (Still relies on the Dirty Roster Engine; visibility + combat guards apply at fire time.)
        FriendGroups_RequestListUpdate()
    end
    
    hooksecurefunc("FriendsFrame_UpdateFriendButton", FriendGroups_FriendsListUpdateFriendButton)
    Menu.ModifyMenu("MENU_UNIT_GLUE_FRIEND", FriendGroups_AddDropDownNew)
    Menu.ModifyMenu("MENU_UNIT_FRIEND", FriendGroups_AddDropDownNew)
    Menu.ModifyMenu("MENU_UNIT_FRIEND_OFFLINE", FriendGroups_AddDropDownNew)
    Menu.ModifyMenu("MENU_UNIT_BN_FRIEND", FriendGroups_AddDropDownNew)
    Menu.ModifyMenu("MENU_UNIT_BN_FRIEND_OFFLINE", FriendGroups_AddDropDownNew)
    hooksecurefunc(FriendsListButtonMixin, "OnClick", FriendGroups_FriendsListButtonTemplateClick)

    -- 4. Setup Scroll View
    SetupGroupedView()
    
	-- [[ UPDATED DEFAULT: Set to 380 (Large) ]] --
    if not ShiGuangDB.FriendGroupsextra_height then
        ShiGuangDB.FriendGroupsextra_height = 190
    end
    FriendGroups_UpdateSize()

    FriendGroups_UpdateContactCap()
    FriendGroups_FriendsListUpdate(true)
end

hooksecurefunc(FriendsTooltip, "Hide", function(self)
    -- [[ ANTI-FLICKER FIX: Ignore hide command if mouse is still on the Friends List ]]
    if FriendsFrame and FriendsFrame:IsMouseOver() then return end
    
    if FriendGroupsAltTooltip then FriendGroupsAltTooltip:Hide() end
    FriendGroups_CurrentHoverAnchor = nil
end)

hooksecurefunc(GameTooltip, "Hide", function(self)
    -- [[ ANTI-FLICKER FIX: Protect against GameTooltip native hide collisions ]]
    if FriendsFrame and FriendsFrame:IsMouseOver() then return end
    if CommunitiesFrame and CommunitiesFrame:IsMouseOver() then return end
    
    if FriendGroupsAltTooltip then FriendGroupsAltTooltip:Hide() end
    FriendGroups_CurrentHoverAnchor = nil
end)

SetupGroupedView = function()
    local view = CreateScrollBoxListLinearView()
    view:SetElementFactory(function(factory, elementData)
        local buttonType = elementData.buttonType;
        if buttonType == FRIENDS_BUTTON_TYPE_DIVIDER then
            factory("FriendGroupsFrameFriendDividerTemplate", FriendGroups_FriendsListUpdateDividerTemplate);
        elseif buttonType == FRIENDS_BUTTON_TYPE_INVITE_HEADER then
            factory("FriendsPendingInviteHeaderButtonTemplate",
                FriendGroups_FriendsFrameUpdateFriendInviteHeaderButton);
        elseif buttonType == FRIENDS_BUTTON_TYPE_INVITE then
            factory("FriendsFrameFriendInviteTemplate", FriendGroups_FriendsFrameUpdateFriendInviteButton);
        else
            -- 12.0 FIX: Switched from custom XML button to Blizzard's Native Secure Template
            -- This completely bypasses the Chat/Secret ID taint vector when clicking friends.
            factory("FriendsListButtonTemplate", FriendGroups_FriendsListUpdateFriendButton);
        end
    end);
    ScrollUtil.InitScrollBoxListWithScrollBar(FriendsListFrame.ScrollBox, FriendsListFrame.ScrollBar, view);
end

FriendGroups_FriendsListUpdateFriendButton = function(button, elementData)
    if not FriendGroups_Loaded then return end

    local isFriendFrame = false
    if button and button.GetParent and FriendsListFrame then
        local current = button:GetParent()
        for i = 1, 15 do
            if not current then break end
            if current == FriendsListFrame then isFriendFrame = true break end
            current = current.GetParent and current:GetParent()
        end
    end
    if not isFriendFrame then return end

	local id = elementData.id;
	local buttonType = elementData.buttonType;
	button.buttonType = buttonType;
	button.id = id;

	if button.facIcon then button.facIcon:Hide() end
    if button.realmFlag then button.realmFlag:Hide() end 

	local nameText, nameColor, infoText, isFavoriteFriend, statusTexture;
	local hasTravelPassButton = false;
	local isCrossFactionInvite = false;
	local inviteFaction = nil;

	if button.buttonType == FRIENDS_BUTTON_TYPE_WOW then
		local info = C_FriendList.GetFriendInfoByIndex(id);
		if (info.connected) then
			button.background:SetColorTexture(FRIENDS_WOW_BACKGROUND_COLOR.r, FRIENDS_WOW_BACKGROUND_COLOR.g, FRIENDS_WOW_BACKGROUND_COLOR.b, FRIENDS_WOW_BACKGROUND_COLOR.a);
			if (info.afk) then button.status:SetTexture(FRIENDS_TEXTURE_AFK);
			elseif (info.dnd) then button.status:SetTexture(FRIENDS_TEXTURE_DND);
			else button.status:SetTexture(FRIENDS_TEXTURE_ONLINE); end
			nameText = info.name .. ", " .. format(FRIENDS_LEVEL_TEMPLATE, info.level, info.className);
			nameColor = FRIENDS_WOW_NAME_COLOR;
			infoText = FriendGroups_GetOnlineInfoText(BNET_CLIENT_WOW, info.mobile, info.rafLinkType, info.area);
			
			button.gameIcon:Hide();
		else
			button.background:SetColorTexture(FRIENDS_OFFLINE_BACKGROUND_COLOR.r, FRIENDS_OFFLINE_BACKGROUND_COLOR.g, FRIENDS_OFFLINE_BACKGROUND_COLOR.b, FRIENDS_OFFLINE_BACKGROUND_COLOR.a);
			button.status:SetTexture(FRIENDS_TEXTURE_OFFLINE);
			nameText = info.name;
			nameColor = FRIENDS_GRAY_COLOR;
			infoText = FRIENDS_LIST_OFFLINE;
			
			button.gameIcon:Show();
			C_Texture.SetTitleIconTexture(button.gameIcon, BNET_CLIENT_WOW, Enum.TitleIconVersion.Medium);
			button.gameIcon:SetAlpha(0.3);
		end
		button.summonButton:ClearAllPoints();
		button.summonButton:SetPoint("TOPRIGHT", button, "TOPRIGHT", 1, -1);
		FriendsFrame_SummonButton_Update(button.summonButton);

	elseif button.buttonType == FRIENDS_BUTTON_TYPE_BNET then
		local accountInfo = C_BattleNet.GetFriendAccountInfo(id);
		if accountInfo then
            -- [[ MASSIVE OPTIMIZATION: Pull localized API data inline instead of running a redundant global function search ]]
			nameText, nameColor, statusTexture = FriendsFrame_GetBNetAccountNameAndStatus(accountInfo);
            
            local accountName = accountInfo.accountName
            isFavoriteFriend = accountInfo.isFavorite
            local battleTag = accountInfo.battleTag
            local canCoop = CanCooperateWithGameAccount(accountInfo)
            
            local client, characterName, class, level, timerunningSeasonID, realmName, factionName
            if accountInfo.gameAccountInfo then
                client = accountInfo.gameAccountInfo.clientProgram
                characterName = accountInfo.gameAccountInfo.characterName
                class = accountInfo.gameAccountInfo.className
                level = accountInfo.gameAccountInfo.characterLevel
                timerunningSeasonID = accountInfo.gameAccountInfo.timerunningSeasonID
                realmName = accountInfo.gameAccountInfo.realmName
                factionName = accountInfo.gameAccountInfo.factionName
            end

			if ShiGuangDB.FriendGroupsshow_mobile_afk and client == 'BSAp' then statusTexture = FRIENDS_TEXTURE_AFK end

			nameText = FriendGroups_GetBNetButtonNameText(accountName, client, canCoop, characterName, class, level, battleTag, timerunningSeasonID, realmName)
			button.status:SetTexture(statusTexture);
            
            factionName = factionName or ""
			isCrossFactionInvite = factionName ~= "" and factionName ~= playerFactionGroup;
			inviteFaction = factionName;

			if accountInfo.gameAccountInfo and accountInfo.gameAccountInfo.isOnline then
				button.background:SetColorTexture(FRIENDS_BNET_BACKGROUND_COLOR.r, FRIENDS_BNET_BACKGROUND_COLOR.g, FRIENDS_BNET_BACKGROUND_COLOR.b, FRIENDS_BNET_BACKGROUND_COLOR.a);

				if FriendGroups_ShowRichPresenceOnly(client, accountInfo.gameAccountInfo.wowProjectID, factionName, accountInfo.gameAccountInfo.realmID, accountInfo.gameAccountInfo.areaName) then
					infoText = FriendGroups_GetOnlineInfoText(client, accountInfo.gameAccountInfo.isWowMobile, accountInfo.rafLinkType, accountInfo.gameAccountInfo.richPresence);
				else
					infoText = FriendGroups_GetOnlineInfoText(client, accountInfo.gameAccountInfo.isWowMobile, accountInfo.rafLinkType, accountInfo.gameAccountInfo.areaName, realmName);
				end

				C_Texture.SetTitleIconTexture(button.gameIcon, client, Enum.TitleIconVersion.Medium);
				local fadeIcon = (client == BNET_CLIENT_WOW) and (accountInfo.gameAccountInfo.wowProjectID ~= WOW_PROJECT_ID);
				if fadeIcon then button.gameIcon:SetAlpha(0.6); else button.gameIcon:SetAlpha(1); end

				local shouldShowSummonButton = FriendsFrame_ShouldShowSummonButton(button.summonButton);
				button.gameIcon:SetShown(not shouldShowSummonButton);

				hasTravelPassButton = true;
				local restriction = FriendsFrame_GetInviteRestriction(button.id);
				if restriction == INVITE_RESTRICTION_NONE then button.travelPassButton:Enable(); else button.travelPassButton:Disable(); end

                local factionShown = false
                if ShiGuangDB.FriendGroupsshow_faction_icons then
                    if not button.facIcon then
                        button.facIcon = button:CreateTexture("facIcon")
                        button.facIcon:SetSize(button.gameIcon:GetWidth(), button.gameIcon:GetHeight())
                    end
                    
                    button.facIcon:ClearAllPoints()
                    button.facIcon:SetPoint("RIGHT", button.gameIcon, "LEFT", 0, 0)
                    button.facIcon:SetTexture(FriendGroups_GetFactionIcon(factionName))
                    button.facIcon:Show()
                    factionShown = true

                    if factionName == "Horde" then
                        button.background:SetColorTexture(0.7, 0.2, 0.2, 0.2)
                    elseif factionName == "Alliance" then
                        button.background:SetColorTexture(0.2, 0.2, 0.7, 0.2)
                    end
                else
                    if button.facIcon then button.facIcon:Hide() end
                end

                if ShiGuangDB.FriendGroupsshow_flags then
                    if not button.realmFlag then
                        button.realmFlag = button:CreateTexture("realmFlag")
                        button.realmFlag:SetSize(button.gameIcon:GetWidth() * 0.75, button.gameIcon:GetHeight() * 0.75)
                    end
                    
                    local flagTexture, _ = FriendGroups_GetRealmInfo(accountInfo.gameAccountInfo)

                    if flagTexture then
                        button.realmFlag:SetTexture(flagTexture)
                        button.realmFlag:Show()
                        button.realmFlag:ClearAllPoints()
                        
                        if factionShown then
                            button.realmFlag:SetPoint("RIGHT", button.facIcon, "LEFT", -1, 0)
                        else
                            button.realmFlag:SetPoint("RIGHT", button.gameIcon, "LEFT", 0, 0)
                        end
                    else
                        button.realmFlag:Hide()
                    end
                else
                    if button.realmFlag then button.realmFlag:Hide() end
                end

			else
				button.background:SetColorTexture(FRIENDS_OFFLINE_BACKGROUND_COLOR.r, FRIENDS_OFFLINE_BACKGROUND_COLOR.g, FRIENDS_OFFLINE_BACKGROUND_COLOR.b, FRIENDS_OFFLINE_BACKGROUND_COLOR.a);
				infoText = FriendsFrame_GetLastOnlineText(accountInfo);
				
				button.gameIcon:Show();
				C_Texture.SetTitleIconTexture(button.gameIcon, BNET_CLIENT_APP, Enum.TitleIconVersion.Medium);
				button.gameIcon:SetAlpha(0.3);
			end

			if ShiGuangDB.FriendGroupsadd_mobile_text and infoText == '' and client == 'BSAp' then infoText = FGL["STATUS_MOBILE"] end

			button.summonButton:ClearAllPoints();
			button.summonButton:SetPoint("CENTER", button.gameIcon, "CENTER", 1, 0);
			FriendsFrame_SummonButton_Update(button.summonButton);
		end
	end

	if hasTravelPassButton then button.travelPassButton:Show(); else button.travelPassButton:Hide(); end

	local selected = (FriendsFrame.selectedFriendType == buttonType) and (FriendsFrame.selectedFriend == id);
	FriendsFrame_FriendButtonSetSelection(button, selected);

	if nameText then
		button.name:SetText(nameText);
		button.name:SetTextColor(nameColor.r, nameColor.g, nameColor.b);
		button.info:SetText(infoText);
		button:Show();
		if isFavoriteFriend then
			button.Favorite:Show();
			button.Favorite:ClearAllPoints()
			button.Favorite:SetPoint("TOPLEFT", button.name, "TOPLEFT", button.name:GetStringWidth(), 0);
		else
			button.Favorite:Hide();
		end
	else
		button:Hide();
	end
	
	if (FriendsTooltip.button == button) or (button:IsMouseMotionFocus()) then button:OnEnter() end

	if hasTravelPassButton and isCrossFactionInvite and not GetCVarBitfield("closedInfoFrames", LE_FRAME_TUTORIAL_CROSS_FACTION_INVITE) then
		local helpTipInfo = {
			text = CROSS_FACTION_INVITE_HELPTIP,
			buttonStyle = HelpTip.ButtonStyle.Close,
			cvarBitfield = "closedInfoFrames",
			bitfieldFlag = LE_FRAME_TUTORIAL_CROSS_FACTION_INVITE,
			targetPoint = HelpTip.Point.RightEdgeCenter,
			alignment = HelpTip.Alignment.Left,
		};
		local crossFactionHelpTipInfo = helpTipInfo;
		local crossFactionHelpTipButton = button;
		HelpTip:Show(FriendsFrame, helpTipInfo, button.travelPassButton);
	end
	
	if hasTravelPassButton then
		if isCrossFactionInvite and inviteFaction == "Horde" then
			button.travelPassButton.NormalTexture:SetAtlas("friendslist-invitebutton-horde-normal");
			button.travelPassButton.PushedTexture:SetAtlas("friendslist-invitebutton-horde-pressed");
			button.travelPassButton.DisabledTexture:SetAtlas("friendslist-invitebutton-horde-disabled");
		elseif isCrossFactionInvite and inviteFaction == "Alliance" then
			button.travelPassButton.NormalTexture:SetAtlas("friendslist-invitebutton-alliance-normal");
			button.travelPassButton.PushedTexture:SetAtlas("friendslist-invitebutton-alliance-pressed");
			button.travelPassButton.DisabledTexture:SetAtlas("friendslist-invitebutton-alliance-disabled");
		else
			button.travelPassButton.NormalTexture:SetAtlas("friendslist-invitebutton-default-normal");
			button.travelPassButton.PushedTexture:SetAtlas("friendslist-invitebutton-default-pressed");
			button.travelPassButton.DisabledTexture:SetAtlas("friendslist-invitebutton-default-disabled");
		end
	end

    if not button.fgAltTooltipHooked then
        button:HookScript("OnEnter", function(self)
            FriendGroups_ShowButtonAltTooltip(self)
        end)
        button:HookScript("OnLeave", function(self)
            if FriendGroupsAltTooltip then FriendGroupsAltTooltip:Hide() end
            FriendGroups_CurrentHoverAnchor = nil
        end)
        button.fgAltTooltipHooked = true
    end

	return nil;
end

local FriendGroups_UpdateQueued = false
local FriendGroups_UpdateTimer = nil
local FriendGroups_LastDataProvider = nil
local FriendGroups_LayoutPool = {}

local function FG_GetLayoutTable()
    local t = table.remove(FriendGroups_LayoutPool) or {}
    wipe(t)
    return t
end
local function FG_ReleaseLayoutTable(t)
    if type(t) == "table" then
        wipe(t)
        table.insert(FriendGroups_LayoutPool, t)
    end
end

function FriendGroups_FriendsListUpdate(forceUpdate)
    -- GUARD: Only run if Enabled and Visible
    if not FriendGroups_Loaded then return end

    if InCombatLockdown() then
        FriendGroups_UpdateQueued = true
        return
    end

    if (not FriendsListFrame:IsShown() and not forceUpdate) then return end

    -- Default to forcing an update if called from internal menus, clicks, or settings
    if forceUpdate == nil then forceUpdate = true end

    -- [[ MASSIVE OPTIMIZATION: IDLE GC CHURN PREVENTER ]]
    if not forceUpdate and not FriendGroups_RosterDirty then
        -- The roster structure hasn't changed. Just refresh the visible buttons (AFK timers, etc.)
        if FriendsListFrame.ScrollBox.ForEachFrame then
            FriendsListFrame.ScrollBox:ForEachFrame(function(frame)
                local elementData = frame:GetElementData()
                if elementData and (elementData.buttonType == FRIENDS_BUTTON_TYPE_BNET or elementData.buttonType == FRIENDS_BUTTON_TYPE_WOW) then
                    FriendGroups_FriendsListUpdateFriendButton(frame, elementData)
                end
            end)
        end
        return
    end

    -- Clear the flag now that we are running a full validated rebuild
    FriendGroups_RosterDirty = false

    -- [[ CACHE PRUNE SUPPORT: advance the generation for this full rebuild. ]]
    -- Every currently-present friend gets re-stamped via FriendGroups_SetGroups below; entries
    -- left on an older generation belong to removed friends and are pruned at the end.
    FriendGroups_CacheGeneration = FriendGroups_CacheGeneration + 1

    local hideGroups = ShiGuangDB.FriendGroupshide_empty_groups or (searchValue ~= "")

    -- Release memory for previous tables
    for _, groupData in pairs(groupsTotal) do
        for _, playerData in ipairs(groupData) do FG_ReleaseTable(playerData) end
        FG_ReleaseTable(groupData)
    end
    for _, countData in pairs(groupsCount) do FG_ReleaseTable(countData) end
    wipe(groupsTotal)
    wipe(groupsCount) 
    wipe(groupsSorted)

    -- Parse natively in a single linear pass & pass refs down
    local numBNetTotal = C_BattleNet.GetFriendNum and C_BattleNet.GetFriendNum() or BNGetNumFriends()
    for i = 1, numBNetTotal do
        local accountInfo = C_BattleNet.GetFriendAccountInfo(i)
        if accountInfo then
            FriendGroups_SetGroups(i, FRIENDS_BUTTON_TYPE_BNET, accountInfo)
        end
    end

    local numWoWTotal = C_FriendList.GetNumFriends()
    for i = 1, numWoWTotal do
        local info = C_FriendList.GetFriendInfoByIndex(i)
        if info then
            FriendGroups_SetGroups(i, FRIENDS_BUTTON_TYPE_WOW, info)
        end
    end

    table.sort(groupsSorted, FriendGroups_SortGroupsCustom)

    local targetLayout = FG_GetTable()
    
    local numInvites = C_BattleNet.GetFriendNumInvites and C_BattleNet.GetFriendNumInvites() or BNGetNumFriendInvites()
    if (numInvites > 0) and not GetCVarBool("friendInvitesCollapsed") then
        for i = 1, numInvites do
            local inv = FG_GetLayoutTable()
            inv.id = i
            inv.buttonType = FRIENDS_BUTTON_TYPE_INVITE
            table.insert(targetLayout, inv)
        end
    end

    for _, groupName in ipairs(groupsSorted) do
        if (not hideGroups or (hideGroups and #groupsTotal[groupName] > 0)) then
            if ShiGuangDB.FriendGroupscollapsed[groupName] == nil then
                ShiGuangDB.FriendGroupscollapsed[groupName] = false
            end

            local div = FG_GetLayoutTable()
            div.buttonType = FRIENDS_BUTTON_TYPE_DIVIDER
            div.groupName = groupName
            table.insert(targetLayout, div)

            if not ShiGuangDB.FriendGroupscollapsed[groupName] then
                table.sort(groupsTotal[groupName], FriendGroups_SortTableByStatus)
                for _, playerData in ipairs(groupsTotal[groupName]) do
                    if playerData.buttonType and playerData.id then
                        local p = FG_GetLayoutTable()
                        p.id = playerData.id
                        p.buttonType = playerData.buttonType
                        table.insert(targetLayout, p)
                    end
                end
            end
        end
    end

    if #targetLayout == 0 then
        local empty = FG_GetLayoutTable()
        empty.buttonType = FRIENDS_BUTTON_TYPE_DIVIDER
        empty.groupName = FGL["GROUP_EMPTY"]
        table.insert(targetLayout, empty)
    end

    local dataProvider = FriendsListFrame.ScrollBox:GetDataProvider()
    if not dataProvider then
        dataProvider = CreateDataProvider()
        for _, elem in ipairs(targetLayout) do dataProvider:Insert(elem) end
        FriendsListFrame.ScrollBox:SetDataProvider(dataProvider, true)
    else
        local currentElements = FG_GetTable()
        for index, element in dataProvider:EnumerateEntireRange() do
            table.insert(currentElements, element)
        end
        
        local needsRebuild = false
        if #currentElements ~= #targetLayout then
            needsRebuild = true
        else
            for i = 1, #targetLayout do
                local cur = currentElements[i]
                local tar = targetLayout[i]
                if cur.buttonType ~= tar.buttonType or cur.id ~= tar.id or cur.groupName ~= tar.groupName then
                    needsRebuild = true
                    break
                end
            end
        end

        if needsRebuild then
            local savedScrollPercentage = FriendsListFrame.ScrollBox:GetScrollPercentage() or 0

            for _, element in ipairs(currentElements) do
                FG_ReleaseLayoutTable(element)
            end

            -- [[ O(n) REBUILD: populate a DETACHED provider, then swap it in a single time. ]]
            -- A provider that is not yet attached to the ScrollBox has no registered listeners,
            -- so per-element Insert triggers no layout/OnSizeChanged work. SetDataProvider then
            -- performs exactly one layout pass, instead of one pass per inserted element.
            local rebuiltProvider = CreateDataProvider()
            for _, elem in ipairs(targetLayout) do
                rebuiltProvider:Insert(elem)
            end
            FriendsListFrame.ScrollBox:SetDataProvider(rebuiltProvider, true)

            if ScrollBoxConstants and ScrollBoxConstants.NoScrollInterpolation then
                FriendsListFrame.ScrollBox:SetScrollPercentage(savedScrollPercentage, ScrollBoxConstants.NoScrollInterpolation)
            else
                FriendsListFrame.ScrollBox:SetScrollPercentage(savedScrollPercentage)
            end
        else
            for _, element in ipairs(targetLayout) do
                FG_ReleaseLayoutTable(element)
            end

            if FriendsListFrame.ScrollBox.ForEachFrame then
                FriendsListFrame.ScrollBox:ForEachFrame(function(frame)
                    local elementData = frame:GetElementData()
                    if elementData and (elementData.buttonType == FRIENDS_BUTTON_TYPE_BNET or elementData.buttonType == FRIENDS_BUTTON_TYPE_WOW) then
                        FriendGroups_FriendsListUpdateFriendButton(frame, elementData)
                    end
                end)
            end
        end
        
        FG_ReleaseTable(currentElements)
    end
    
    FG_ReleaseTable(targetLayout)

    for groupName, _ in pairs(ShiGuangDB.FriendGroupscollapsed) do
        if not groupsTotal[groupName] then
            ShiGuangDB.FriendGroupscollapsed[groupName] = nil
        end
    end

    -- [[ CACHE PRUNE: drop assignment-cache entries for friends no longer present. ]]
    -- Setting an existing key to nil during pairs() traversal is safe in Lua (only adding new
    -- keys mid-traversal is not). This runs only on full rebuilds, after every present friend
    -- has been re-stamped with the current generation above.
    for cacheKey, entry in pairs(FriendGroups_AssignmentCache) do
        if entry.generation ~= FriendGroups_CacheGeneration then
            FriendGroups_AssignmentCache[cacheKey] = nil
        end
    end
    
    FriendGroups_UpdateContactCap() 
end

function FriendGroups_FilterTable(tableData, filterFunction)
	local returnTable = {}

	for key, value in pairs(tableData) do
		if filterFunction(value, key, tableData) then table.insert(returnTable, value) end
	end

	return returnTable
end

function FriendGroups_GetRealmInfo(gameAccountInfo)
    if not gameAccountInfo then return nil, nil end

    -- Secure String Verification up front
    local rawRealm = gameAccountInfo.realmName
    local safeRealm = (type(rawRealm) == "string") and rawRealm or ""
    
    local richPresence = gameAccountInfo.richPresence
    local safePresence = (type(richPresence) == "string") and richPresence or ""

    -- 1. Determine Region Database using API RegionID
    local rid = gameAccountInfo.regionID
    local database = FriendGroups_RealmData -- Default Region 1 (US/Oceanic)
    
    if rid == 3 then 
        database = FriendGroups_RealmDataEU 
    elseif rid == 2 or rid == 4 or rid == 5 then 
        database = FriendGroups_RealmDataAsia
    end

    -- 2. Standard Realm Lookup (Spaces & Punctuation stripped to match our normalized DB)
    if safeRealm ~= "" then
        local cleanRealm = safeRealm:gsub("[%s%p]+", "")
        local data = database[cleanRealm]
        
        if data then
            return "Interface\\AddOns\\OrzUI\\Media\\Modules\\FriendGroups\\" .. data.icon, data.region
        end
    end
    
    -- 3. Fallback: Parse Rich Presence for Classic or Hidden Realms
    if safePresence ~= "" then
        -- First try extracting the realm placed after the hyphen (standard API format)
        local extraction = safePresence:match("%s%-%s(.+)$")
        
        -- If no hyphen, use the full string (e.g. they are just logged into "Nightslayer" char screen)
        local cleanEx = (extraction or safePresence):gsub("[%s%p]+", "")
        
        local data = database[cleanEx]
        if data then
            return "Interface\\AddOns\\OrzUI\\Media\\Modules\\FriendGroups\\" .. data.icon, data.region
        end
    end

    return nil, nil
end

function FriendGroups_FriendsListUpdateDividerTemplate(frame, elementData)
    local groupName = elementData.groupName
    local groupOnline = groupsCount[groupName] and groupsCount[groupName]["Online"] or 0
    local groupTotal = groupsCount[groupName] and groupsCount[groupName]["Total"] or 0

    if groupName and frame.name then
        frame.rawGroupName = groupName -- Store raw name for click handlers
        
        -- Cleanup any old search/settings items if they exist on this recycled frame
        if _G["FriendGroupsSearch"] and _G["FriendGroupsSearch"]:GetParent() == frame then
             _G["FriendGroupsSearch"]:Hide()
        end
        local settingsBtn = _G["FriendGroupsSettingsBtn"]
        if settingsBtn and settingsBtn:GetParent() == frame then 
            settingsBtn:Hide() 
        end
        
        -- Standard Header Setup
        frame.name:Show()
        
        local displayGroupName = groupName
        if groupName == FGL["GROUP_GUILDMATES"] and FriendGroups_PlayerGuildName and FriendGroups_PlayerGuildName ~= "" then
            displayGroupName = string.format(FGL["FORMAT_GUILD_TAG"], groupName, FriendGroups_PlayerGuildName)
        end
        frame.name:SetText(displayGroupName)
        
        frame.collapseButton:Show()
        if frame.info then frame.info:Show() end

        if groupName ~= FGL["GROUP_EMPTY"] then
            local groupInfo = string.format("%d/%d", groupOnline, groupTotal)
            
            -- [[ NEW: Hide the "0/" for Offline Virtual Groups ]]
            if groupName == FGL["GROUP_OFFLINE_1"] or groupName == FGL["GROUP_OFFLINE_2"] or groupName == FGL["GROUP_OFFLINE_3"] then
                groupInfo = tostring(groupTotal)
            end
            
            if frame.info then frame.info:SetText(groupInfo) end
            
            if ShiGuangDB.FriendGroupscollapsed[groupName] then
                frame.collapseButton:SetNormalAtlas("Campaign_HeaderIcon_Closed")
            else
                frame.collapseButton:SetNormalAtlas("Campaign_HeaderIcon_Open")
            end
        else
            frame.collapseButton:Hide()
            if frame.info then frame.info:SetText("") end
        end

        frame:SetHighlightTexture("Interface\\Buttons\\UI-PlusButton-Hilight")
        frame:GetHighlightTexture():SetAlpha(0.2)

        -- [[ NEW: GUILD GROUP TOOLTIP HOOK ]]
        if not frame.fgTooltipHooked then
            frame:HookScript("OnEnter", function(self)
                local hGroupName = self.rawGroupName or (self.name and self.name:GetText())
                if not hGroupName then return end
                
                -- Check if this is an Auto Guild Group
                if string.find(hGroupName, FGL["GROUP_GUILDMATES"], 1, true) then
                    local guildNameMatch = string.match(hGroupName, "<(.-)>")
                    if guildNameMatch then
                        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                        GameTooltip:SetText(FGL["TOOLTIP_GUILD_GROUP_TITLE"], 1, 0.82, 0)
                        GameTooltip:AddLine(FGL["TOOLTIP_GUILD_GROUP_DESC_1"], 1, 1, 1, true)
                        GameTooltip:AddLine(string.format(FGL["TOOLTIP_GUILD_GROUP_DESC_2"], "<" .. guildNameMatch .. ">"), 1, 0.82, 0, true)
                        GameTooltip:Show()
                    end
                end
            end)
            frame:HookScript("OnLeave", function(self)
                GameTooltip:Hide()
            end)
            frame.fgTooltipHooked = true
        end
    end
end

function FriendGroups_UpdateContactCap()
    if not FriendGroups_ContactText then return end
    
    -- Hardcoded server limits (as Blizzard API does not provide a query for maximums)
    local BNET_MAX_FRIENDS = 600
    local WOW_MAX_FRIENDS = 100

    local numBNetTotal = C_BattleNet and C_BattleNet.GetFriendNum and C_BattleNet.GetFriendNum() or BNGetNumFriends() or 0
    local numBNetInvites = C_BattleNet and C_BattleNet.GetFriendNumInvites and C_BattleNet.GetFriendNumInvites() or BNGetNumFriendInvites() or 0
    local bnetConsumed = numBNetTotal + numBNetInvites

    -- Main text: Strict BNet Tracker
    FriendGroups_ContactText:SetText(string.format(FGL["TEXT_BNET_CONTACTS"], bnetConsumed, BNET_MAX_FRIENDS))
    
    -- Turn RED if at or over the BNet cap, otherwise White
    if bnetConsumed >= BNET_MAX_FRIENDS then
        FriendGroups_ContactText:SetTextColor(1, 0, 0) 
    else
        FriendGroups_ContactText:SetTextColor(1, 1, 1) 
    end
    
    FriendGroups_ContactText:Show()

    -- Lazy Load the invisible hover frame for the tooltip
    if not FriendGroups_ContactHoverFrame then
        FriendGroups_ContactHoverFrame = CreateFrame("Frame", "FriendGroupsContactHoverFrame", FriendsListFrame)
        FriendGroups_ContactHoverFrame:SetPoint("TOPLEFT", FriendGroups_ContactText, "TOPLEFT", 0, 0)
        FriendGroups_ContactHoverFrame:SetPoint("BOTTOMRIGHT", FriendGroups_ContactText, "BOTTOMRIGHT", 0, 0)
        
        FriendGroups_ContactHoverFrame:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(FGL["TOOLTIP_CONTACT_TITLE"], 1, 1, 1)
            GameTooltip:AddLine(FGL["TOOLTIP_CONTACT_DESC"], nil, nil, nil, true)
            GameTooltip:AddLine(" ")
            
            -- Dynamic values inside tooltip
            local bTotal = C_BattleNet and C_BattleNet.GetFriendNum and C_BattleNet.GetFriendNum() or BNGetNumFriends() or 0
            local bInvites = C_BattleNet and C_BattleNet.GetFriendNumInvites and C_BattleNet.GetFriendNumInvites() or BNGetNumFriendInvites() or 0
            local wTotal = C_FriendList.GetNumFriends() or 0
            
            GameTooltip:AddDoubleLine(FGL["TOOLTIP_CONTACT_BNET"], string.format("%d / %d", bTotal, BNET_MAX_FRIENDS), 1, 1, 1)
            GameTooltip:AddDoubleLine(FGL["TOOLTIP_CONTACT_INVITES"], string.format("%d %s", bInvites, FGL["TOOLTIP_CONTACT_INV_DESC"]), 1, 1, 1, 1, 0.5, 0)
            GameTooltip:AddDoubleLine(FGL["TOOLTIP_CONTACT_WOW"], string.format("%d / %d", wTotal, WOW_MAX_FRIENDS), 1, 1, 1)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine(string.format(FGL["TOOLTIP_CONTACT_TOTAL"], bTotal + wTotal), 1, 0.82, 0)
            
            GameTooltip:Show()
        end)
        
        FriendGroups_ContactHoverFrame:SetScript("OnLeave", function(self)
            GameTooltip:Hide()
        end)
    end
end

function FriendGroups_FrameFriendDividerTemplateCollapseClick(self, button, down)
    -- FIX: Determine if 'self' is the Header (has .name) or the Button (needs .GetParent)
    local frame = self
    if not frame.name then
        frame = self:GetParent()
    end

    -- Safety check: If we still can't find the name, stop to prevent crash
    if not frame or not frame.name then return end

    local groupName = frame.rawGroupName or frame.name:GetText()
    
    if groupName then
        -- Toggle the collapsed state
        if ShiGuangDB.FriendGroupscollapsed[groupName] then
            ShiGuangDB.FriendGroupscollapsed[groupName] = false
        else
            ShiGuangDB.FriendGroupscollapsed[groupName] = true
        end
        
        -- Handle "Open only one group" setting
        if ShiGuangDB.FriendGroupsopen_one_group and not ShiGuangDB.FriendGroupscollapsed[groupName] then
            for key, _ in pairs(ShiGuangDB.FriendGroupscollapsed) do
                if key ~= groupName then
                    ShiGuangDB.FriendGroupscollapsed[key] = true
                end
            end
        end
        
        -- Refresh the list
        FriendGroups_FriendsListUpdate()
    end
end

FriendGroups_FriendsListButtonTemplateClick = function(self, button, down)
    if not FriendGroups_Loaded then return end 
    
    FriendGroupsFrame.selectionLocked = false
    
    C_Timer.After(0.05, function()
        FriendGroups_FriendsListUpdate()
    end)
end

function FriendGroups_FriendsFrameUpdateFriendInviteHeaderButton(button, elementData)
    local numInvites = C_BattleNet.GetFriendNumInvites and C_BattleNet.GetFriendNumInvites() or BNGetNumFriendInvites()
	button:SetFormattedText(FRIEND_REQUESTS, numInvites);
	local collapsed = GetCVarBool("friendInvitesCollapsed");
	if (collapsed) then
		button.DownArrow:Hide();
		button.RightArrow:Show();
	else
		button.DownArrow:Show();
		button.RightArrow:Hide();
	end
end

function FriendGroups_FriendsFrameUpdateFriendInviteButton(button, elementData)
	local id = elementData.id;
	button.buttonType = elementData.buttonType;
	button.id = id;

    local inviteID, accountName
    if C_BattleNet and C_BattleNet.GetFriendInviteInfo then
        local inviteInfo = C_BattleNet.GetFriendInviteInfo(id)
        if inviteInfo then
            inviteID = inviteInfo.inviteID
            accountName = inviteInfo.accountName
        end
    else
        inviteID, accountName = BNGetFriendInviteInfo(id)
    end

	button.Name:SetText(accountName);
	button.inviteID = inviteID;
	button.inviteIndex = button.id;
end

-- ============================================================================
-- [[ FRIENDGROUPS SECURE HOUSING PROXY ]]
-- ============================================================================
local FG_Osirisnz_HousingProxy = nil
local FG_Osirisnz_HookedButtons = {}

local function Osirisnz_ActuallyWorkingVisitHouse()
    if FG_Osirisnz_HousingProxy then return FG_Osirisnz_HousingProxy end
    if InCombatLockdown() then return nil end 

    -- CRITICAL: Uses SecureActionButtonTemplate AND SecureHandlerStateTemplate
    FG_Osirisnz_HousingProxy = CreateFrame("Button", "FriendGroups_Osirisnz_SecureHouseProxy", UIParent, "SecureActionButtonTemplate, SecureHandlerStateTemplate")
    FG_Osirisnz_HousingProxy:SetFrameStrata("DIALOG")
    FG_Osirisnz_HousingProxy:SetFrameLevel(9999)
    FG_Osirisnz_HousingProxy:Hide()
    FG_Osirisnz_HousingProxy:RegisterForClicks("AnyUp", "AnyDown")
    FG_Osirisnz_HousingProxy:SetAttribute("type", "visithouse")

    -- Securely hide on combat start without causing Lua taint
    RegisterStateDriver(FG_Osirisnz_HousingProxy, "combatstate", "[combat] combat; nocombat")
    FG_Osirisnz_HousingProxy:SetAttribute("_onstate-combatstate", [[
        if newstate == "combat" then
            self:Hide()
            self:ClearAllPoints()
        end
    ]])

    FG_Osirisnz_HousingProxy:SetScript("OnEnter", function(self)
        if self.nativeButton then
            self.nativeButton:LockHighlight()
        end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(HOUSING_VISIT_HOUSE or "Visit House", 1, 1, 1)
        GameTooltip:Show()
    end)

    FG_Osirisnz_HousingProxy:SetScript("OnLeave", function(self)
        GameTooltip:Hide()
        self:Hide()
        self:ClearAllPoints()
        if self.nativeButton then
            self.nativeButton:UnlockHighlight()
        end
        self.nativeButton = nil
    end)
    
    FG_Osirisnz_HousingProxy:SetScript("OnMouseDown", function(self)
        if self.nativeButton then self.nativeButton:SetButtonState("PUSHED") end
    end)
    
    FG_Osirisnz_HousingProxy:SetScript("OnMouseUp", function(self)
        if self.nativeButton then self.nativeButton:SetButtonState("NORMAL") end
    end)

    return FG_Osirisnz_HousingProxy
end

local function Osirisnz_OnHouseButtonEnter(nativeButton)
    if InCombatLockdown() then return end 

    local parentRow = nativeButton:GetParent()
    local houseInfo = parentRow and parentRow.houseInfo
    
    if not houseInfo or not houseInfo.neighborhoodGUID or not houseInfo.houseGUID then return end

    local proxy = Osirisnz_ActuallyWorkingVisitHouse()
    if not proxy then return end

    proxy:ClearAllPoints()
    proxy:SetAllPoints(nativeButton)

    proxy:SetAttribute("house-neighborhood-guid", houseInfo.neighborhoodGUID)
    proxy:SetAttribute("house-guid", houseInfo.houseGUID)
    proxy:SetAttribute("house-plot-id", houseInfo.plotID)

    proxy.nativeButton = nativeButton
    
    proxy:Show()
    if proxy:GetScript("OnEnter") then
        proxy:GetScript("OnEnter")(proxy)
    end
end

local function Osirisnz_InitHousingScrollBox()
    local houseFrame = _G.HouseListFrame
    if not houseFrame or not houseFrame.ScrollBox then return end

    houseFrame.ScrollBox:RegisterCallback("OnInitializedFrame", function(_, frame)
        local btn = frame.VisitHouseButton
        if btn and not FG_Osirisnz_HookedButtons[btn] then
            btn:HookScript("OnEnter", Osirisnz_OnHouseButtonEnter)
            FG_Osirisnz_HookedButtons[btn] = true
        end
    end)
end

-- ============================================================================
-- [[ MAIN INITIALIZATION ]]
-- ============================================================================
local frame = CreateFrame("frame", "FriendGroups")
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
frame:RegisterEvent("ADDON_LOADED")

frame:SetScript("OnEvent", function(self, event, arg1, ...)
if event == "ADDON_LOADED" and arg1 == "OrzUI" then
        local version = "12.1.04" or "Unknown"
        --Print(string.format(FGL["MSG_WELCOME"], version))

    elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
        
        local myBattleTag = nil
        if C_BattleNet and C_BattleNet.GetAccountInfoByGUID then
            local accInfo = C_BattleNet.GetAccountInfoByGUID(UnitGUID("player"))
            if accInfo then myBattleTag = accInfo.battleTag end
        end
        ShiGuangDB.FriendGroupsMyBattleTag = myBattleTag
        
    elseif event == "PLAYER_LOGIN" then
        
        if ShiGuangDB.FriendGroupsshow_guildmates == nil then ShiGuangDB.FriendGroupsshow_guildmates = true end
        
        -- [[ MEMORY OPTIMIZATION: EXECTUE 14-DAY PURGE ]]
        FriendGroups_PurgeStaleAlts()

        EnableFriendGroups()

        FriendsFrame:HookScript("OnHide", function()
            FriendsFrame.selectedFriend = nil
            FriendsFrame.selectedFriendType = nil
            FriendGroupsFrame.selectionLocked = true
        end)
        FriendsFrame:HookScript("OnShow", function()
            FriendsFrame.selectedFriend = nil
            FriendsFrame.selectedFriendType = nil
            FriendGroupsFrame.selectionLocked = true 
            FriendGroups_FriendsListUpdate(true)
        end)
        
        if C_AddOns.IsAddOnLoaded("Blizzard_HouseList") then Osirisnz_InitHousingScrollBox() end
        if C_AddOns.IsAddOnLoaded("Blizzard_Communities") then FriendGroups_InitCommunitiesHook() end

    elseif event == "ADDON_LOADED" and arg1 == "Blizzard_HouseList" then
        Osirisnz_InitHousingScrollBox()
    elseif event == "ADDON_LOADED" and arg1 == "Blizzard_Communities" then
        FriendGroups_InitCommunitiesHook()
    end
end)

-- ============================================================================
-- [[ AUTOMATION LOGIC ]]
-- ============================================================================

local function FG_IsPlayerBusy()
    if InCombatLockdown() then return true end
    
    local inInstance, instanceType = IsInInstance()
    if inInstance then
        if instanceType == "pvp" or instanceType == "arena" then return true end
        if instanceType == "party" and C_ChallengeMode and C_ChallengeMode.IsChallengeModeActive() then return true end
        if IsEncounterInProgress() then return true end
    end
    
    return false
end

local FriendGroups_Automation = CreateFrame("Frame")
FriendGroups_Automation:RegisterEvent("PARTY_INVITE_REQUEST")
FriendGroups_Automation:RegisterEvent("RESURRECT_REQUEST")
FriendGroups_Automation:RegisterEvent("PLAYER_DEAD")

pcall(function()
    FriendGroups_Automation:RegisterEvent("QUEST_SESSION_CREATED")
end)

FriendGroups_Automation:SetScript("OnEvent", function(self, event, ...)
    -- 1. Auto Accept Group Invites
    if event == "PARTY_INVITE_REQUEST" then
        if FG_IsPlayerBusy() then return end 
        
        local inviterName = ...
        if ShiGuangDB.FriendGroupsauto_accept_invite then
            if FGL["MSG_AUTO_INVITE"] then
                DEFAULT_CHAT_FRAME:AddMessage(string.format(FGL["MSG_AUTO_INVITE"], inviterName or "Unknown"))
            end
            
            -- Removed Timer wrapper to preserve secure hardware event payload
            local success = pcall(AcceptGroup)
            if success then
                StaticPopup_Hide("PARTY_INVITE")
                StaticPopup_Hide("PARTY_INVITE_XREALM")
            else
                if FGL["MSG_AUTO_ACCEPT_FAILED"] then
                    DEFAULT_CHAT_FRAME:AddMessage(FGL["MSG_AUTO_ACCEPT_FAILED"])
                end
            end
        end

    -- 2. Auto Accept Resurrection
    elseif event == "RESURRECT_REQUEST" then
        local inviterName = ...
        if ShiGuangDB.FriendGroupsauto_accept_res then
            if FGL["MSG_AUTO_RES"] then
                DEFAULT_CHAT_FRAME:AddMessage(string.format(FGL["MSG_AUTO_RES"], inviterName or "Unknown"))
            end
            
            -- Removed Timer wrapper to preserve secure hardware event payload
            local success = pcall(AcceptResurrect)
            if success then
                StaticPopup_Hide("RESURRECT")
            else
                if FGL["MSG_AUTO_ACCEPT_FAILED"] then
                    DEFAULT_CHAT_FRAME:AddMessage(FGL["MSG_AUTO_ACCEPT_FAILED"])
                end
            end
        end
        
    -- 3. Auto Release Spirit
    elseif event == "PLAYER_DEAD" then
        if ShiGuangDB.FriendGroupsauto_release then
            if FGL["MSG_AUTO_RELEASE"] then
                DEFAULT_CHAT_FRAME:AddMessage(FGL["MSG_AUTO_RELEASE"])
            end

            local selfResOptions = C_DeathInfo.GetSelfResurrectOptions()
            if not selfResOptions or #selfResOptions == 0 then 
                local success = pcall(RepopMe)
                if not success then
                    if FGL["MSG_AUTO_RELEASE_FAILED"] then
                        DEFAULT_CHAT_FRAME:AddMessage(FGL["MSG_AUTO_RELEASE_FAILED"])
                    end
                end
            end
        end
        
    -- 4. Auto Accept Party Sync
    elseif event == "QUEST_SESSION_CREATED" then
        if ShiGuangDB.FriendGroupsauto_accept_sync then
            if UnitIsGroupLeader("player") then return end
            if InCombatLockdown() then return end
            
            local leaderName = "Party Leader"
            if IsInGroup() then
                for i = 1, 4 do
                    local unit = "party"..i
                    if UnitIsGroupLeader(unit) then
                        leaderName = UnitName(unit) or leaderName
                        break
                    end
                end
            end

            if FGL["MSG_AUTO_SYNC"] then
                DEFAULT_CHAT_FRAME:AddMessage(string.format(FGL["MSG_AUTO_SYNC"], leaderName))
            end

            if C_QuestSession and C_QuestSession.SendSessionBeginResponse then
                C_QuestSession.SendSessionBeginResponse(true)
            end
        end
    end
end)

-- ============================================================================
-- [[ KNOWN ALTS SPYGLASS ENGINE & STATIC PANEL ]]
-- ============================================================================

FriendGroups_SaveToAltCache = function(accountIdentifier, gameInfo)
    if not accountIdentifier or not gameInfo then return end
    if not gameInfo.isOnline or gameInfo.clientProgram ~= BNET_CLIENT_WOW then return end
    
    local charName = (type(gameInfo.characterName) == "string") and gameInfo.characterName or ""
    if charName == "" then return end
    
    local rawRealm = gameInfo.realmName
    if not rawRealm or rawRealm == "" then rawRealm = gameInfo.realmDisplayName end
    if (not rawRealm or rawRealm == "") and type(gameInfo.richPresence) == "string" then
        local extraction = gameInfo.richPresence:match("%s%-%s(.+)")
        if extraction then rawRealm = extraction end
    end
    if not rawRealm or rawRealm == "" then return end

    local displayRealm = (type(rawRealm) == "string") and rawRealm or ""
    local normalizedRealm = FriendGroups_CleanRealmName(displayRealm) 
    local uniqueKey = charName .. "-" .. normalizedRealm
    
    local class = (type(gameInfo.className) == "string") and gameInfo.className or ""
    local faction = (type(gameInfo.factionName) == "string") and gameInfo.factionName or ""
    local zone = (type(gameInfo.areaName) == "string") and gameInfo.areaName or FGL["UNKNOWN"]
    local level = tonumber(gameInfo.characterLevel) or 1
    local project = gameInfo.wowProjectID or WOW_PROJECT_MAINLINE

    local detectedGuild = nil
    if FriendGroups_LiveGuildSessionDict[uniqueKey] or FriendGroups_LiveGuildSessionDict[charName] then
        detectedGuild = FriendGroups_PlayerGuildName
    end
    
    if type(ShiGuangDB.FriendGroupsalt_cache[accountIdentifier]) ~= "table" then
        ShiGuangDB.FriendGroupsalt_cache[accountIdentifier] = {}
    end
    
    local altList = ShiGuangDB.FriendGroupsalt_cache[accountIdentifier]
    local existingIndex = nil
    
    for i = 1, #altList do
        local existingRealm = altList[i].realm or ""
        local existingNorm = FriendGroups_CleanRealmName(existingRealm)
        local existingMigratedKey = altList[i].charName .. "-" .. existingNorm

        if altList[i].key == uniqueKey or existingMigratedKey == uniqueKey or altList[i].key == (charName .. "-") then
            existingIndex = i
            break
        end
    end
    
    if existingIndex then
        local existingAlt = altList[existingIndex]
        local dataChanged = false
        
        if existingAlt.level ~= level then existingAlt.level = level; dataChanged = true end
        if existingAlt.zone ~= zone then existingAlt.zone = zone; dataChanged = true end
        if existingAlt.class ~= class then existingAlt.class = class; dataChanged = true end
        if existingAlt.faction ~= faction then existingAlt.faction = faction; dataChanged = true end
        if existingAlt.realm ~= displayRealm then existingAlt.realm = displayRealm; dataChanged = true end
        if existingAlt.project ~= project then existingAlt.project = project; dataChanged = true end
        
        -- Pre-calculate and store search strings to prevent GC search stutter
        existingAlt.searchName = charName:lower()
        existingAlt.searchRealm = displayRealm:lower()
        existingAlt.searchClass = class:lower()
        existingAlt.searchZone = zone:lower()

        -- STRICT PRESERVATION: Only update if a valid guild was detected. Never overwrite with nil.
        if detectedGuild and existingAlt.guild ~= detectedGuild then 
            existingAlt.guild = detectedGuild; dataChanged = true 
        end
        
        if dataChanged then
            existingAlt.timestamp = time()
        end
    else
        table.insert(altList, 1, {
            key = uniqueKey,
            charName = charName,
            realm = displayRealm, 
            level = level,
            class = class,
            faction = faction,
            zone = zone,
            guild = detectedGuild,
            project = project,
            timestamp = time(),
            -- Pre-calculate and store search strings
            searchName = charName:lower(),
            searchRealm = displayRealm:lower(),
            searchClass = class:lower(),
            searchZone = zone:lower()
        })
        
        while #altList > 10 do table.remove(altList) end
    end
end

-- 2. The Silent Tracker (Catches background changes)
local FriendGroups_BNetIndexMap = {}

local function FriendGroups_UpdateBNetIndexMap()
    wipe(FriendGroups_BNetIndexMap)
    local numBNetTotal = C_BattleNet.GetFriendNum and C_BattleNet.GetFriendNum() or BNGetNumFriends()
    for i = 1, numBNetTotal do
        local accountInfo = C_BattleNet.GetFriendAccountInfo(i)
        if accountInfo and accountInfo.bnetAccountID then
            FriendGroups_BNetIndexMap[accountInfo.bnetAccountID] = i
        end
    end
end

-- ============================================================================
-- [[ SECURE BACKGROUND TRACKER (STALE-SEAT PROTECTED) ]]
-- ============================================================================
local FriendGroups_AltTracker = CreateFrame("Frame")
FriendGroups_AltTracker:RegisterEvent("BN_FRIEND_INFO_CHANGED")
FriendGroups_AltTracker:SetScript("OnEvent", function(self, event, ...)
    -- [[ COMBAT CHURN GUARD ]]
    -- Skip background alt-caching while in combat. BN_FRIEND_INFO_CHANGED fires continuously in
    -- populated content; performing API lookups + SavedVars writes per event during combat is
    -- wasted work. No data is lost: the alt cache is rebuilt for every BNet friend on the next
    -- full list rebuild (FriendGroups_SetGroups -> FriendGroups_SaveToAltCache).
    if InCombatLockdown() then return end

    if type(ShiGuangDB.FriendGroupsalt_cache) ~= "table" then ShiGuangDB.FriendGroupsalt_cache = {} end

    local arg1 = ... 
    if not arg1 then return end
    
    -- Secure API Lookup by strict ID guarantees we never pull the wrong person's data
    local accountInfo = C_BattleNet.GetAccountInfoByID(arg1)
    if not accountInfo then 
        accountInfo = C_BattleNet.GetFriendAccountInfo(arg1) -- Legacy fallback just in case
    end
    if not accountInfo then return end
    
    local accountIdentifier = accountInfo.battleTag or accountInfo.accountName
    if not accountIdentifier then return end

    -- Secure Path: Save only the verified primary game account attached to this strict ID.
    -- Multibox alts are safely handled synchronously during the UI rebuild, bypassing stale seat errors.
    if accountInfo.gameAccountInfo then
        FriendGroups_SaveToAltCache(accountIdentifier, accountInfo.gameAccountInfo)
    end
end)

-- 3. The Static Side-Panel Injector
-- FIX: Changed parent to UIParent and forced FrameStrata so it is impossible to hide behind the Guild window
local FriendGroupsAltTooltip = CreateFrame("GameTooltip", "FriendGroupsAltTooltip", UIParent, "GameTooltipTemplate")
FriendGroupsAltTooltip:SetFrameStrata("TOOLTIP")
FriendGroupsAltTooltip:SetFrameLevel(9999)

local FriendGroups_CurrentHoverCharKey = nil
local FriendGroups_CurrentHoverBNetID = nil
local FriendGroups_CurrentHoverAnchor = nil

-- Reverse lookup to instantly find your own private BNet friends in the Guild Roster
local function FriendGroups_FindBNetIDByCharKey(charKey)
    if not ShiGuangDB.FriendGroupsalt_cache then return nil end
    for bnetID, alts in pairs(ShiGuangDB.FriendGroupsalt_cache) do
        for _, alt in ipairs(alts) do
            if alt.key == charKey then
                return bnetID
            end
        end
    end
    return nil
end

local function FriendGroups_DrawAltTooltip(anchorFrame, accountIdentifier, charKey, baselineData)
    if not FriendGroups_Loaded or ShiGuangDB.FriendGroupsshow_known_alts == false then
        FriendGroupsAltTooltip:Hide()
        return
    end

    -- Contextual UI Title Logic
    local hoverName = FGL["UNKNOWN"]
    if baselineData and baselineData.charName then
        hoverName = baselineData.charName
    elseif charKey then
        hoverName = strsplit("-", charKey)
    end
    
    -- [[ MASTER MERGE DICTIONARY ]]
    local masterDict = {}

    local function MergeIntoMaster(alt)
        if not alt or not alt.charName or not alt.realm then return end
        
        -- Force a universal, lowercase key to prevent case-sensitive splits
        local renderKey = string.lower(alt.charName .. "-" .. alt.realm:gsub("[%s%p]+", ""))
        
        if not masterDict[renderKey] then
            masterDict[renderKey] = {
                key = renderKey, charName = alt.charName, realm = alt.realm,
                class = alt.class, faction = alt.faction, level = alt.level,
                zone = alt.zone, guild = alt.guild, timestamp = alt.timestamp
            }
        else
            local existing = masterDict[renderKey]
            
            -- ALWAYS preserve a valid guild tag
            if alt.guild and alt.guild ~= "NONE" and alt.guild ~= "-" and alt.guild ~= "" then
                if not existing.guild or existing.guild == "NONE" or existing.guild == "-" or existing.guild == "" then
                    existing.guild = alt.guild
                end
            end

            -- Normal Timestamp Election for dynamic stats (Level, Zone)
            if (alt.timestamp or 0) > (existing.timestamp or 0) then
                existing.level = alt.level
                existing.zone = alt.zone
                existing.faction = alt.faction
                existing.timestamp = alt.timestamp
            end
        end
    end

    -- 1. PULL FROM LOCAL BNET CACHE
    if accountIdentifier and ShiGuangDB.FriendGroupsalt_cache and ShiGuangDB.FriendGroupsalt_cache[accountIdentifier] then
        for _, alt in ipairs(ShiGuangDB.FriendGroupsalt_cache[accountIdentifier]) do 
            MergeIntoMaster(alt) 
        end
    end

    -- 2. PULL FROM LIVE BASELINE DATA
    if baselineData then
        MergeIntoMaster(baselineData)
    end

    -- Flatten the merged dictionary back into a renderable array
    local alts = {}
    for _, alt in pairs(masterDict) do
        table.insert(alts, alt)
    end

    if #alts > 0 then
        -- SMART FALLBACK FIX: Heal missing name from cache
        if hoverName == FGL["UNKNOWN"] then
            local renderCharKey = string.lower(charKey or "")
            local foundHover = masterDict[renderCharKey]
            if foundHover and foundHover.charName and foundHover.charName ~= "" then
                hoverName = foundHover.charName
            elseif alts[1] and alts[1].charName and alts[1].charName ~= "" then
                hoverName = alts[1].charName
            end
        end

        local displayTitle = string.format(FGL["TOOLTIP_ALTS_PUBLIC_TITLE_FORMAT"], hoverName)
        if #alts >= 10 and FGL["TOOLTIP_ALTS_TITLE_FORMAT_MAX"] then
            displayTitle = string.format(FGL["TOOLTIP_ALTS_TITLE_FORMAT_MAX"], hoverName)
        end

        FriendGroupsAltTooltip:SetOwner(anchorFrame, "ANCHOR_NONE")

        if anchorFrame == FriendsFrame then
            FriendGroupsAltTooltip:SetPoint("TOPLEFT", FriendsFrame, "TOPRIGHT", 2, 0)
        elseif CommunitiesFrame and CommunitiesFrame:IsShown() then
            FriendGroupsAltTooltip:SetPoint("TOPLEFT", CommunitiesFrame, "TOPRIGHT", 2, 0)
        else
            FriendGroupsAltTooltip:SetPoint("TOPLEFT", anchorFrame, "TOPRIGHT", 2, 0)
        end

		FriendGroupsAltTooltip:ClearLines()
        FriendGroupsAltTooltip:AddLine(displayTitle, 1, 0.82, 0)
		
        local sortedAlts = {}
        for _, alt in ipairs(alts) do table.insert(sortedAlts, alt) end
        
        table.sort(sortedAlts, function(a, b)
            local timeA = a.timestamp or 0
            local timeB = b.timestamp or 0
            if timeA == timeB then
                if a.level == b.level then return a.charName < b.charName end
                return a.level > b.level
            end
            return timeA > timeB
        end)

        local rowCount = 0
        for _, alt in ipairs(sortedAlts) do
            if rowCount >= 10 then break end
            rowCount = rowCount + 1

            if GameTooltip_AddBlankLineToTooltip then
                GameTooltip_AddBlankLineToTooltip(FriendGroupsAltTooltip)
            else
                FriendGroupsAltTooltip:AddLine(" ")
            end

            local engClass = ""
            if alt.class and (LOCALIZED_CLASS_NAMES_MALE[alt.class] or RAID_CLASS_COLORS[alt.class]) then
                engClass = alt.class
            else
                for k, v in pairs(LOCALIZED_CLASS_NAMES_MALE) do if alt.class == v then engClass = k break end end
                if engClass == "" then for k, v in pairs(LOCALIZED_CLASS_NAMES_FEMALE) do if alt.class == v then engClass = k break end end end
            end

            local classIconStr = ""
            if engClass ~= "" then
                local atlas = GetClassAtlas(engClass)
                if atlas then classIconStr = "|A:" .. atlas .. ":16:16|a" else classIconStr = "|A:classicon-" .. string.lower(engClass) .. ":16:16|a" end
            end
            
            local nameColor = FriendGroups_GetClassColorCode(engClass ~= "" and engClass or alt.class)
            local coloredName = nameColor .. alt.charName .. (alt.realm ~= "" and ("-" .. alt.realm) or "") .. "|r"

            local factionPath = FriendGroups_GetFactionIcon(alt.faction)
            local factionIconStr = factionPath ~= "" and ("|T" .. factionPath .. ":16|t") or ""

            local diff = time() - (alt.timestamp or time())
            if diff < 0 then diff = 0 end

            local timeStr = ""
            if diff < 60 then timeStr = FGL["TIME_JUST_NOW"]
            elseif diff < 3600 then timeStr = string.format(FGL["TIME_MINUTES_AGO"], math.floor(diff / 60))
            elseif diff < 86400 then timeStr = string.format(FGL["TIME_HOURS_AGO"], math.floor(diff / 3600))
            else timeStr = string.format(FGL["TIME_DAYS_AGO"], math.floor(diff / 86400)) end

            local line1 = string.format(FGL["TOOLTIP_ALTS_FORMAT"], classIconStr, coloredName, factionIconStr, alt.level)
            
            if alt.guild and alt.guild ~= "NONE" and alt.guild ~= "-" and alt.guild ~= "" then
                if FGL["TOOLTIP_ALTS_GUILD_SUFFIX"] then
                    line1 = line1 .. string.format(FGL["TOOLTIP_ALTS_GUILD_SUFFIX"], alt.guild)
                end
            end

            local line2 = string.format(FGL["TOOLTIP_ALTS_SEEN"], alt.zone, timeStr)

            FriendGroupsAltTooltip:AddLine(line1, 1, 1, 1, false)
            FriendGroupsAltTooltip:AddLine(line2, 0.6, 0.6, 0.6, false)
        end

        FriendGroupsAltTooltip:Show()
    else
        FriendGroupsAltTooltip:Hide()
    end
end

function FriendGroups_RefreshAltTooltip()
    if FriendGroups_CurrentHoverAnchor then
        FriendGroups_DrawAltTooltip(FriendGroups_CurrentHoverAnchor, FriendGroups_CurrentHoverBNetID, FriendGroups_CurrentHoverCharKey)
    end
end

-- [[ DIRECT BUTTON HANDLER ]]
function FriendGroups_ShowButtonAltTooltip(button)
    if not FriendGroups_Loaded or ShiGuangDB.FriendGroupsshow_known_alts == false then FriendGroupsAltTooltip:Hide() return end

    if not button or not button.id then return end

    FriendGroups_CurrentHoverAnchor = FriendsFrame
    FriendGroups_CurrentHoverBNetID = nil
    FriendGroups_CurrentHoverCharKey = nil

    local baselineData = nil

    if button.buttonType == FRIENDS_BUTTON_TYPE_BNET then
        local accountInfo = C_BattleNet.GetFriendAccountInfo(button.id)
        if not accountInfo then return end

        local accountIdentifier = accountInfo.battleTag or accountInfo.accountName
        if not accountIdentifier then return end

        FriendGroups_SaveToAltCache(accountIdentifier, accountInfo.gameAccountInfo)
        
        -- [[ MULTIBOX FIX: Automatically harvest all secondary open wow clients natively ]]
        if C_BattleNet.GetFriendNumGameAccounts then
            local numAccounts = C_BattleNet.GetFriendNumGameAccounts(button.id)
            if numAccounts and numAccounts > 1 then
                for i = 1, numAccounts do
                    local gameAccountInfo = C_BattleNet.GetFriendGameAccountInfo(button.id, i)
                    if gameAccountInfo then
                        FriendGroups_SaveToAltCache(accountIdentifier, gameAccountInfo)
                    end
                end
            end
        end

        FriendGroups_CurrentHoverBNetID = accountIdentifier

        if accountInfo.gameAccountInfo and accountInfo.gameAccountInfo.characterName then
            local rawRealm = accountInfo.gameAccountInfo.realmName
            if not rawRealm or rawRealm == "" then rawRealm = accountInfo.gameAccountInfo.realmDisplayName end
            if rawRealm and rawRealm ~= "" then
                local cName = accountInfo.gameAccountInfo.characterName
                local rName = rawRealm:gsub("[%s%p]+", "")
                FriendGroups_CurrentHoverCharKey = cName .. "-" .. rName
                
                baselineData = {
                    charName = cName,
                    realm = rawRealm,
                    class = accountInfo.gameAccountInfo.className or "",
                    faction = accountInfo.gameAccountInfo.factionName or "",
                    level = tonumber(accountInfo.gameAccountInfo.characterLevel) or 1,
                    zone = accountInfo.gameAccountInfo.areaName or FGL["UNKNOWN_ZONE"],
                    timestamp = time()
                }
            end
        end

        FriendGroups_DrawAltTooltip(FriendsFrame, FriendGroups_CurrentHoverBNetID, FriendGroups_CurrentHoverCharKey, baselineData)

    elseif button.buttonType == FRIENDS_BUTTON_TYPE_WOW then
        local info = C_FriendList.GetFriendInfoByIndex(button.id)
        if not info or not info.name then return end

        local rawName = info.name
        local charKey = rawName
        local cName, rName = rawName, GetNormalizedRealmName()
        if not rawName:find("-") then charKey = rawName .. "-" .. rName
        else cName, rName = strsplit("-", rawName); rName = rName:gsub("[%s%p]+", ""); charKey = cName .. "-" .. rName end

        FriendGroups_CurrentHoverCharKey = charKey
        FriendGroups_CurrentHoverBNetID = FriendGroups_FindBNetIDByCharKey(charKey)
        
        baselineData = {
            charName = cName,
            realm = rName,
            class = info.className or "",
            faction = "", 
            level = tonumber(info.level) or 1,
            zone = info.area or FGL["UNKNOWN_ZONE"],
            timestamp = time()
        }

        FriendGroups_DrawAltTooltip(FriendsFrame, FriendGroups_CurrentHoverBNetID, charKey, baselineData)
    end
end

hooksecurefunc(FriendsTooltip, "Show", function(self)
    if not FriendGroups_Loaded or ShiGuangDB.FriendGroupsshow_known_alts == false then FriendGroupsAltTooltip:Hide() return end

    local button = self.button
    if not button or not button.id then return end

    FriendGroups_CurrentHoverAnchor = FriendsFrame
    FriendGroups_CurrentHoverBNetID = nil
    FriendGroups_CurrentHoverCharKey = nil

    local baselineData = nil

    if button.buttonType == FRIENDS_BUTTON_TYPE_BNET then
        local accountInfo = C_BattleNet.GetFriendAccountInfo(button.id)
        if not accountInfo then return end

        local accountIdentifier = accountInfo.battleTag or accountInfo.accountName
        if not accountIdentifier then return end

        FriendGroups_SaveToAltCache(accountIdentifier, accountInfo.gameAccountInfo)
        
        -- [[ MULTIBOX FIX: Mirror of above ]]
        if C_BattleNet.GetFriendNumGameAccounts then
            local numAccounts = C_BattleNet.GetFriendNumGameAccounts(button.id)
            if numAccounts and numAccounts > 1 then
                for i = 1, numAccounts do
                    local gameAccountInfo = C_BattleNet.GetFriendGameAccountInfo(button.id, i)
                    if gameAccountInfo then
                        FriendGroups_SaveToAltCache(accountIdentifier, gameAccountInfo)
                    end
                end
            end
        end

        FriendGroups_CurrentHoverBNetID = accountIdentifier

        if accountInfo.gameAccountInfo and accountInfo.gameAccountInfo.characterName then
            local rawRealm = accountInfo.gameAccountInfo.realmName
            if not rawRealm or rawRealm == "" then rawRealm = accountInfo.gameAccountInfo.realmDisplayName end
            if rawRealm and rawRealm ~= "" then
                local cName = accountInfo.gameAccountInfo.characterName
                local rName = rawRealm:gsub("[%s%p]+", "")
                FriendGroups_CurrentHoverCharKey = cName .. "-" .. rName
                
                baselineData = {
                    charName = cName,
                    realm = rawRealm,
                    class = accountInfo.gameAccountInfo.className or "",
                    faction = accountInfo.gameAccountInfo.factionName or "",
                    level = tonumber(accountInfo.gameAccountInfo.characterLevel) or 1,
                    zone = accountInfo.gameAccountInfo.areaName or FGL["UNKNOWN_ZONE"],
                    timestamp = time()
                }
            end
        end

        FriendGroups_DrawAltTooltip(FriendsFrame, FriendGroups_CurrentHoverBNetID, FriendGroups_CurrentHoverCharKey, baselineData)

    elseif button.buttonType == FRIENDS_BUTTON_TYPE_WOW then
        local info = C_FriendList.GetFriendInfoByIndex(button.id)
        if not info or not info.name then return end

        local rawName = info.name
        local charKey = rawName
        local cName, rName = rawName, GetNormalizedRealmName()
        if not rawName:find("-") then charKey = rawName .. "-" .. rName
        else cName, rName = strsplit("-", rawName); rName = rName:gsub("[%s%p]+", ""); charKey = cName .. "-" .. rName end

        FriendGroups_CurrentHoverCharKey = charKey
        FriendGroups_CurrentHoverBNetID = FriendGroups_FindBNetIDByCharKey(charKey)
        
        baselineData = {
            charName = cName,
            realm = rName,
            class = info.className or "",
            faction = "", 
            level = tonumber(info.level) or 1,
            zone = info.area or FGL["UNKNOWN_ZONE"],
            timestamp = time()
        }

        FriendGroups_DrawAltTooltip(FriendsFrame, FriendGroups_CurrentHoverBNetID, charKey, baselineData)
    end
end)

FriendsFrame:HookScript("OnHide", function()
    if FriendGroupsAltTooltip then FriendGroupsAltTooltip:Hide() end
    FriendGroups_CurrentHoverAnchor = nil
end)

local function FriendGroups_OnGuildRosterEnter(owner, memberInfo)
    local rawName = memberInfo.name
    local charKey = rawName
    local cName, rName = rawName, GetNormalizedRealmName()
    if not rawName:find("-") then 
        charKey = rawName .. "-" .. rName
    else 
        cName, rName = strsplit("-", rawName)
        rName = rName:gsub("[%s%p]+", "")
        charKey = cName .. "-" .. rName 
    end

    FriendGroups_CurrentHoverAnchor = owner
    FriendGroups_CurrentHoverBNetID = FriendGroups_FindBNetIDByCharKey(charKey)
    FriendGroups_CurrentHoverCharKey = charKey

    local className, classFilename = "", ""
    if memberInfo.classID then 
        className, classFilename = GetClassInfo(memberInfo.classID) 
    end

    -- [[ FACTION FIX: Hybrid Approach (Communities API -> Native GUID API) ]]
    local factionFallback = ""
    if type(memberInfo.faction) == "number" then
        if memberInfo.faction == 0 then 
            factionFallback = "Horde"
        elseif memberInfo.faction == 1 then 
            factionFallback = "Alliance" 
        end
    end
    
    -- Fallback to Native GUID check if Communities data is missing
    if factionFallback == "" and memberInfo.guid then
        local _, _, _, englishRace = GetPlayerInfoByGUID(memberInfo.guid)
        if englishRace then
            if englishRace == "Human" or englishRace == "Dwarf" or englishRace == "NightElf" or englishRace == "Gnome" or englishRace == "Draenei" or englishRace == "Worgen" or englishRace == "LightforgedDraenei" or englishRace == "VoidElf" or englishRace == "DarkIronDwarf" or englishRace == "KulTiran" or englishRace == "Mechagnome" or englishRace == "EarthenAlliance" then
                factionFallback = "Alliance"
            elseif englishRace == "Orc" or englishRace == "Scourge" or englishRace == "Tauren" or englishRace == "Troll" or englishRace == "BloodElf" or englishRace == "Goblin" or englishRace == "Nightborne" or englishRace == "HighmountainTauren" or englishRace == "MagharOrc" or englishRace == "ZandalariTroll" or englishRace == "Vulpera" or englishRace == "EarthenHorde" then
                factionFallback = "Horde"
            end
            -- Note: Neutral races (Pandaren/Dracthyr) safely bypass this to avoid assumptions.
            -- If their faction cannot be derived here, your BNet/Mesh Alt Cache will securely resolve it.
        end
    end

    local baselineData = {
        charName = cName,
        realm = rName,
        class = classFilename or className or "",
        faction = factionFallback, 
        level = tonumber(memberInfo.level) or 1,
        zone = memberInfo.zoneName or memberInfo.zone or FGL["UNKNOWN_ZONE"],
        guild = FriendGroups_PlayerGuildName, -- [[ FIX: Supply local guild explicitly ]]
        timestamp = time()
    }

    FriendGroups_DrawAltTooltip(owner, FriendGroups_CurrentHoverBNetID, charKey, baselineData)
end

function FriendGroups_InitCommunitiesHook()
    -- 1. Hook Standard Communities / Guild Roster Frame (Retail API)
    if CommunitiesFrame and not CommunitiesFrame.FG_HideHooked then
        CommunitiesFrame:HookScript("OnHide", function()
            if FriendGroupsAltTooltip then FriendGroupsAltTooltip:Hide() end
            FriendGroups_CurrentHoverAnchor = nil
        end)
        
        -- [[ NEW: Secure localized Tooltip Hook for Communities Roster (Replaces Global GameTooltip Hook) ]]
        if CommunitiesFrame.MemberList and CommunitiesFrame.MemberList.ScrollBox then
            CommunitiesFrame.MemberList.ScrollBox:RegisterCallback("OnInitializedFrame", function(_, frame)
                if not frame.FG_RosterTooltipHooked then
                    frame:HookScript("OnEnter", function(self)
                        if not FriendGroups_Loaded or ShiGuangDB.FriendGroupsshow_known_alts == false then return end
                        if type(self.GetMemberInfo) ~= "function" then return end
                        
                        local success, memberInfo = pcall(self.GetMemberInfo, self)
                        if success and memberInfo and memberInfo.name then
                            FriendGroups_OnGuildRosterEnter(self, memberInfo)
                        end
                    end)
                    
                    frame:HookScript("OnLeave", function(self)
                        if FriendGroupsAltTooltip then FriendGroupsAltTooltip:Hide() end
                        FriendGroups_CurrentHoverAnchor = nil
                    end)
                    
                    frame.FG_RosterTooltipHooked = true
                end
            end)
        end
        CommunitiesFrame.FG_HideHooked = true
    end

    -- 2. Hook Legacy/Standalone Guild UI (Fallback Support)
    if GuildRosterContainer and GuildRosterContainer.ScrollBox and not GuildRosterContainer.FG_Hooked then
        GuildRosterContainer.ScrollBox:RegisterCallback("OnInitializedFrame", function(_, frame)
            if not frame.FG_RosterTooltipHooked then
                frame:HookScript("OnEnter", function(self)
                    if not FriendGroups_Loaded or ShiGuangDB.FriendGroupsshow_known_alts == false then return end
                    if type(self.GetMemberInfo) ~= "function" then return end
                    
                    local success, memberInfo = pcall(self.GetMemberInfo, self)
                    if success and memberInfo and memberInfo.name then
                        FriendGroups_OnGuildRosterEnter(self, memberInfo)
                    end
                end)
                
                frame:HookScript("OnLeave", function(self)
                    if FriendGroupsAltTooltip then FriendGroupsAltTooltip:Hide() end
                    FriendGroups_CurrentHoverAnchor = nil
                end)
                
                frame.FG_RosterTooltipHooked = true
            end
        end)
        GuildRosterContainer.FG_Hooked = true
    end
end

-- ============================================================================
-- [[ DIAGNOSTIC & TELEMETRY ENGINE ]]
-- ============================================================================
SLASH_FRIENDGROUPSDEBUG1 = "/fg"
SlashCmdList["FRIENDGROUPSDEBUG"] = function(msg)
    local command = msg and msg:lower():match("^%s*(%w+)")
    
    if command == "debug" then
        -- 1. Measure Memory Churn (Strictly restricted to FriendGroups)
        UpdateAddOnMemoryUsage()
        local memUsageKB = GetAddOnMemoryUsage(addonName) or GetAddOnMemoryUsage("FriendGroups") or 0
        local memUsageMB = memUsageKB / 1024

        -- 2. Measure Database Bloat
        local privateCount = 0

            if type(ShiGuangDB.FriendGroupsalt_cache) == "table" then
                for _, alts in pairs(ShiGuangDB.FriendGroupsalt_cache) do
                    privateCount = privateCount + #alts
                end
            end

        -- 3. Output Telemetry Safely
        DEFAULT_CHAT_FRAME:AddMessage(FGL["DEBUG_HEADER"] or "Debug Header Missing")
        DEFAULT_CHAT_FRAME:AddMessage(string.format(FGL["DEBUG_MEM_USAGE"] or "Mem: %.2f", memUsageMB))
        DEFAULT_CHAT_FRAME:AddMessage(string.format(FGL["DEBUG_DB_SIZE"] or "Database Profiles: %d (Private BNet Cache)", privateCount))
    else
        DEFAULT_CHAT_FRAME:AddMessage(FGL["DEBUG_HELP"] or "Type /fg debug")
    end
end

-- ============================================================================
-- [[ OPEN-LIST UPDATE COALESCER ]]
-- ============================================================================
-- Collapses bursts of roster events (BN_FRIEND_INFO_CHANGED, FRIENDLIST_UPDATE, native idle
-- ticks) that route through the FriendsList_Update override into a single deferred refresh.
-- Schedules at most one timer per window, and only while the Friends list is actually visible.
-- The Dirty Roster Engine still flags changes regardless, so a hidden list rebuilds correctly
-- via OnShow the next time it is opened. All existing guards (combat, visibility, dirty) still
-- apply when the timer fires, so behaviour is identical to today aside from a sub-frame delay.
local FriendGroups_UpdateThrottleTimer = nil
function FriendGroups_RequestListUpdate()
    if not (FriendsListFrame and FriendsListFrame:IsShown()) then return end
    if FriendGroups_UpdateThrottleTimer then return end
    FriendGroups_UpdateThrottleTimer = C_Timer.NewTimer(0.1, function()
        FriendGroups_UpdateThrottleTimer = nil
        FriendGroups_FriendsListUpdate(false)
    end)
end

-- ============================================================================
-- [[ COMBAT LOCKOUT QUEUE HANDLER ]]
-- ============================================================================
local FriendGroups_CombatQueueFrame = CreateFrame("Frame")
FriendGroups_CombatQueueFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
FriendGroups_CombatQueueFrame:SetScript("OnEvent", function(self, event)
    if event == "PLAYER_REGEN_ENABLED" then
        if FriendGroups_UpdateQueued then
            FriendGroups_UpdateQueued = false
            -- Combat has safely ended. Use the standard (non-forcing) update path so the
            -- visibility guard applies: if the Friends list is hidden this returns immediately
            -- (OnShow will rebuild it later); if it is shown it rebuilds once via the O(n) swap.
            FriendGroups_FriendsListUpdate(false)
        end
    end
end)