{% extends "base.html" %}
{% block title %}消息 - 智联青云{% endblock %}

{% block content %}
<div id="chatApp" class="flex bg-white/80 backdrop-blur-xl rounded-3xl shadow-2xl border border-white/50 overflow-hidden" style="height:calc(100vh - 120px);min-height:500px;">
    <!-- 左侧面板 -->
    <div id="leftPanel" class="w-full sm:w-80 border-r border-slate-200/60 bg-white/50 flex flex-col flex-shrink-0 relative z-10">
        <!-- Tab 切换：聊天 / 通知 -->
        <div class="flex p-3 gap-2 bg-white/40 border-b border-slate-200/50 backdrop-blur-md sticky top-0 z-20">
            <button id="tabChat" onclick="switchTab('chat')" class="flex-1 px-4 py-2 text-sm font-bold text-white bg-gradient-to-r from-indigo-500 to-purple-500 rounded-xl shadow-md transition-all duration-300">💬 聊天</button>
            <button id="tabNotif" onclick="switchTab('notif')" class="flex-1 px-4 py-2 text-sm font-medium text-slate-600 hover:text-indigo-600 hover:bg-white bg-white/50 rounded-xl transition-all duration-300 relative border border-white/50">
                🔔 通知
                <span id="chatNotifBadge" class="hidden absolute -top-1.5 -right-1.5 bg-rose-500 text-white text-[10px] font-bold rounded-full h-5 min-w-[20px] px-1.5 flex items-center justify-center shadow-sm border-2 border-white transform animate-bounce">0</span>
            </button>
        </div>
        <!-- 聊天面板 -->
        <div id="chatPanel" class="flex-1 flex flex-col overflow-hidden">
            <!-- 搜索 + 创建 -->
            <div class="p-4 space-y-3 bg-white/30 backdrop-blur-sm border-b border-slate-200/50">
                <div class="relative group">
                    <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                        <svg class="w-4 h-4 text-slate-400 group-focus-within:text-indigo-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
                    </div>
                    <input id="roomSearch" type="text" placeholder="搜索聊天..." class="w-full pl-10 pr-4 py-2.5 text-sm bg-white border border-slate-200/80 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 transition-all shadow-sm" oninput="filterRooms()">
                </div>
                <button onclick="showCreateGroup()" class="w-full px-4 py-2.5 text-sm font-bold bg-white text-indigo-600 border border-indigo-100 rounded-xl hover:bg-indigo-50 hover:shadow-md transition-all flex items-center justify-center gap-2 group">
                    <div class="w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center group-hover:bg-indigo-600 group-hover:text-white transition-colors">
                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
                    </div>
                    发起群聊
                </button>
            </div>
            <!-- 会话列表 -->
            <div id="roomList" class="flex-1 overflow-y-auto hide-scrollbar p-2 space-y-1 bg-transparent"></div>
        </div>
        <!-- 通知面板 -->
        <div id="chatNotifPanel" class="flex-1 overflow-y-auto hide-scrollbar hidden p-2 space-y-2 bg-transparent">
            <div id="chatNotifList" class="space-y-2"></div>
            <div id="chatNotifEmpty" class="flex flex-col items-center justify-center h-64 text-slate-400">
                <div class="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-3">📭</div>
                <div class="text-sm font-medium">暂无新通知</div>
            </div>
        </div>
    </div>
    <!-- 右侧聊天区 -->
    <div id="rightPanel" class="flex-1 flex flex-col hidden sm:flex bg-[url('data:image/svg+xml,%3Csvg width=\'60\' height=\'60\' viewBox=\'0 0 60 60\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cg fill=\'none\' fill-rule=\'evenodd\'%3E%3Cg fill=\'%236366f1\' fill-opacity=\'0.03\'%3E%3Cpath d=\'M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z\'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E')] relative">
        <div class="absolute inset-0 bg-gradient-to-br from-white/90 to-white/50 backdrop-blur-[2px] z-0"></div>
        <!-- 未选中状态 -->
        <div id="emptyState" class="flex-1 flex items-center justify-center z-10">
            <div class="text-center transform transition-all hover:scale-105 duration-500">
                <div class="w-32 h-32 mx-auto bg-gradient-to-tr from-indigo-100 to-purple-100 rounded-full flex items-center justify-center mb-6 shadow-inner relative">
                    <div class="absolute inset-0 bg-indigo-400 blur-2xl opacity-20 rounded-full animate-pulse"></div>
                    <span class="text-6xl animate-bounce">💬</span>
                </div>
                <h3 class="text-2xl font-extrabold text-slate-800 tracking-tight mb-2">欢迎来到联考消息中心</h3>
                <p class="text-slate-500 font-medium">在左侧选择一个聊天，或发起新的对话</p>
            </div>
        </div>
        <!-- 通知详情视图 -->
        <div id="notifDetailView" class="flex-1 flex-col hidden z-10 bg-white/80 backdrop-blur-md">
            <div class="px-6 py-4 border-b border-slate-200/60 flex items-center gap-4 bg-white/50 sticky top-0 backdrop-blur-xl">
                <button onclick="mobileBack()" class="sm:hidden w-8 h-8 rounded-lg bg-slate-100 text-slate-600 flex items-center justify-center flex-shrink-0" aria-label="返回">
                    <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
                </button>
                <div class="w-12 h-12 rounded-2xl bg-gradient-to-br from-indigo-100 to-purple-100 flex items-center justify-center text-2xl shadow-sm border border-white" id="notifDetailIconContainer">
                    <span id="notifDetailIcon"></span>
                </div>
                <div>
                    <div id="notifDetailType" class="text-lg font-extrabold text-slate-900"></div>
                    <div id="notifDetailTime" class="text-xs font-medium text-slate-500 mt-1"></div>
                </div>
            </div>
            <div class="flex-1 overflow-y-auto p-8 hide-scrollbar">
                <div class="bg-white rounded-3xl p-8 shadow-sm border border-slate-100 relative overflow-hidden">
                    <div class="absolute top-0 right-0 w-32 h-32 bg-indigo-50/50 rounded-bl-full -z-10"></div>
                    <div id="notifDetailContent" class="text-base text-slate-700 leading-relaxed font-medium"></div>
                    <div id="notifDetailFrom" class="text-sm text-slate-400 mt-6 pt-6 border-t border-slate-100 flex items-center gap-2"></div>
                    <div id="notifDetailActions" class="mt-6"></div>
                </div>
            </div>
        </div>
        <!-- 聊天视图 -->
        <div id="chatView" class="flex-1 flex-col hidden z-10 relative">
            <!-- 顶栏 -->
            <div id="chatHeader" class="px-6 py-4 border-b border-slate-200/60 flex items-center justify-between bg-white/60 backdrop-blur-xl sticky top-0 z-20">
                <div class="flex items-center gap-3">
                    <button onclick="mobileBack()" class="sm:hidden w-8 h-8 rounded-lg bg-slate-100 text-slate-600 flex items-center justify-center mr-1" aria-label="返回">
                        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
                    </button>
                    <div id="chatAvatarContainer" class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-100 to-purple-100 flex items-center justify-center text-indigo-600 font-bold shadow-sm overflow-hidden">
                        <!-- 头像动态插入 -->
                    </div>
                    <div>
                        <div class="flex items-center gap-2">
                            <span id="chatName" class="text-lg font-bold text-slate-900 tracking-tight"></span>
                            <span id="chatMemberCount" class="text-xs font-bold text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full border border-indigo-100"></span>
                        </div>
                        <div id="typingIndicator" class="text-[11px] font-medium text-indigo-500 hidden animate-pulse mt-0.5"></div>
                    </div>
                </div>
                <div class="flex items-center gap-3">
                    <button onclick="showAnnouncement()" class="w-10 h-10 rounded-xl bg-white text-slate-500 hover:text-amber-600 hover:bg-amber-50 hover:shadow-sm border border-slate-100 transition-all flex items-center justify-center group" title="群公告" id="btnAnnouncement">
                        <svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"/></svg>
                    </button>
                    <button onclick="showSearchPanel()" class="w-10 h-10 rounded-xl bg-white text-slate-500 hover:text-indigo-600 hover:bg-indigo-50 hover:shadow-sm border border-slate-100 transition-all flex items-center justify-center group" title="搜索聊天记录">
                        <svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
                    </button>
                    <button onclick="showFileList()" class="w-10 h-10 rounded-xl bg-white text-slate-500 hover:text-emerald-600 hover:bg-emerald-50 hover:shadow-sm border border-slate-100 transition-all flex items-center justify-center group" title="群文件">
                        <svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
                    </button>
                    <button onclick="showMembers()" class="w-10 h-10 rounded-xl bg-white text-slate-500 hover:text-indigo-600 hover:bg-indigo-50 hover:shadow-sm border border-slate-100 transition-all flex items-center justify-center group" title="成员列表">
                        <svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
                    </button>
                </div>
            </div>
            <!-- 消息区域 -->
            <div id="messageArea" class="flex-1 overflow-y-auto hide-scrollbar px-6 py-4 space-y-5" onscroll="handleScroll()"></div>
            <!-- 引用回复预览 -->
            <div id="replyPreview" class="px-4 py-3 bg-indigo-50/80 backdrop-blur-md border-t border-indigo-100 hidden flex items-center justify-between shadow-inner">
                <div class="flex items-center gap-2 overflow-hidden">
                    <div class="w-1 h-4 bg-indigo-500 rounded-full"></div>
                    <div class="text-sm font-medium text-indigo-900 truncate"><span id="replyToText"></span></div>
                </div>
                <button onclick="cancelReply()" class="w-6 h-6 rounded-full bg-white/50 text-indigo-400 hover:text-indigo-600 hover:bg-white flex items-center justify-center transition-colors shadow-sm ml-2 flex-shrink-0">
                    <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
                </button>
            </div>
            <!-- 输入区域 -->
            <div class="p-4 bg-white/80 backdrop-blur-xl border-t border-slate-200/60 shadow-[0_-4px_20px_-15px_rgba(0,0,0,0.1)]">
                <!-- Emoji 面板 -->
                <div id="emojiPanel" class="hidden absolute bottom-[calc(100%+10px)] left-4 p-3 bg-white/95 backdrop-blur-xl border border-slate-200/60 rounded-2xl shadow-xl max-h-48 overflow-y-auto w-64 z-50">
                    <div class="flex flex-wrap gap-2 justify-center" id="emojiGrid"></div>
                </div>
                <!-- @提及自动补全 -->
                <div id="mentionPanel" class="hidden absolute bottom-[calc(100%+10px)] left-20 bg-white border border-slate-200 rounded-xl shadow-xl max-h-48 overflow-y-auto w-56 z-50">
                    <div id="mentionList" class="py-1"></div>
                </div>
                <!-- 录音指示器 -->
                <div id="recordingIndicator" class="hidden absolute bottom-[calc(100%+10px)] left-1/2 -translate-x-1/2 bg-rose-500 text-white px-4 py-2 rounded-xl shadow-lg flex items-center gap-2 z-50">
                    <div class="w-3 h-3 bg-white rounded-full animate-pulse"></div>
                    <span class="text-sm font-medium">录音中... <span id="recordingTime">0s</span></span>
                </div>
                <div class="flex items-end gap-3">
                    <div class="flex flex-col gap-2 pb-1.5">
                        <button onclick="toggleEmoji()" class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-amber-500 hover:bg-amber-50 transition-colors flex items-center justify-center shadow-sm" title="表情">
                            😀
                        </button>
                        <label class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-emerald-500 hover:bg-emerald-50 transition-colors flex items-center justify-center shadow-sm cursor-pointer" title="发送图片">
                            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
                            <input type="file" accept="image/*" class="hidden" onchange="uploadFile(this,'image')">
                        </label>
                        <label class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-blue-500 hover:bg-blue-50 transition-colors flex items-center justify-center shadow-sm cursor-pointer" title="发送文件">
                            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/></svg>
                            <input type="file" class="hidden" onchange="uploadFile(this,'file')">
                        </label>
                        <button onclick="insertMention()" class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-indigo-500 hover:bg-indigo-50 transition-colors flex items-center justify-center shadow-sm font-bold text-sm" title="@提及">
                            @
                        </button>
                        <button id="voiceBtn" onmousedown="startRecording()" onmouseup="stopRecording()" ontouchstart="startRecording()" ontouchend="stopRecording()" class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-rose-500 hover:bg-rose-50 transition-colors flex items-center justify-center shadow-sm" title="按住录音">
                            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/></svg>
                        </button>
                    </div>
                    
                    <div class="flex-1 bg-white rounded-2xl border border-slate-200/80 shadow-sm focus-within:ring-2 focus-within:ring-indigo-500/30 focus-within:border-indigo-400 transition-all overflow-hidden">
                        <textarea id="msgInput" rows="1" placeholder="输入消息... (Enter发送, Shift+Enter换行)" class="flex-1 w-full px-4 py-3.5 text-sm bg-transparent border-none focus:outline-none focus:ring-0 resize-none max-h-32 min-h-[48px] hide-scrollbar" oninput="handleTyping(); this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px';"></textarea>
                    </div>
                    
                    <button onclick="sendMessage()" class="w-12 h-12 rounded-2xl bg-gradient-to-r from-indigo-500 to-purple-600 text-white flex items-center justify-center hover:shadow-lg hover:shadow-indigo-500/30 transform hover:-translate-y-0.5 transition-all flex-shrink-0 group">
                        <svg class="w-5 h-5 transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
                    </button>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- 创建群聊弹窗 -->
<div id="createGroupModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
    <div class="bg-white rounded-xl shadow-xl w-96 max-h-[80vh] flex flex-col">
        <div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center">
            <h3 class="font-semibold">创建群聊</h3>
            <button onclick="hideCreateGroup()" class="text-slate-400 hover:text-slate-600">&times;</button>
        </div>
        <div class="p-4 space-y-3">
            <input id="groupName" type="text" placeholder="群聊名称" class="w-full px-3 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50">
            <p class="text-sm text-slate-500">选择好友加入群聊：</p>
            <div id="friendListForGroup" class="max-h-48 overflow-y-auto space-y-1"></div>
        </div>
        <div class="px-4 py-3 border-t border-slate-200">
            <button onclick="createGroup()" class="w-full px-4 py-2 bg-primary text-white rounded-lg hover:bg-blue-600 text-sm">创建</button>
        </div>
    </div>
</div>

<!-- 成员列表弹窗 -->
<div id="membersModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
    <div class="bg-white rounded-xl shadow-xl w-[420px] max-h-[80vh] flex flex-col">
        <div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center">
            <h3 class="font-semibold">群成员</h3>
            <div class="flex items-center gap-2">
                <button onclick="showNicknameModal()" class="text-xs text-indigo-500 hover:text-indigo-700" title="设置群昵称">我的昵称</button>
                <button onclick="hideMembers()" class="text-slate-400 hover:text-slate-600 text-lg">&times;</button>
            </div>
        </div>
        <div id="membersList" class="p-4 overflow-y-auto max-h-96 space-y-2"></div>
        <div id="inviteSection" class="px-4 py-3 border-t border-slate-200 hidden">
            <button onclick="showInvite()" class="w-full px-4 py-2 bg-primary text-white rounded-lg hover:bg-blue-600 text-sm">邀请好友</button>
        </div>
    </div>
</div>

<!-- 群公告弹窗 -->
<div id="announcementModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
    <div class="bg-white rounded-xl shadow-xl w-96 flex flex-col">
        <div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center">
            <h3 class="font-semibold">群公告</h3>
            <button onclick="hideAnnouncement()" class="text-slate-400 hover:text-slate-600 text-lg">&times;</button>
        </div>
        <div class="p-4 space-y-3">
            <div id="announcementDisplay" class="text-sm text-slate-600 min-h-[40px] whitespace-pre-wrap">暂无公告</div>
            <div id="announcementMeta" class="text-xs text-slate-400 hidden"></div>
            <div id="announcementEditSection" class="hidden">
                <textarea id="announcementInput" class="w-full px-3 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 resize-none" rows="4" placeholder="输入群公告内容..."></textarea>
                <div class="flex justify-end gap-2 mt-2">
                    <button onclick="cancelEditAnnouncement()" class="px-3 py-1.5 text-sm text-slate-500 hover:bg-slate-100 rounded-lg">取消</button>
                    <button onclick="saveAnnouncement()" class="px-3 py-1.5 text-sm bg-primary text-white rounded-lg hover:bg-blue-600">发布</button>
                </div>
            </div>
            <button id="btnEditAnnouncement" onclick="editAnnouncement()" class="hidden text-sm text-primary hover:text-blue-700">编辑公告</button>
        </div>
    </div>
</div>

<!-- 搜索聊天记录弹窗 -->
<div id="searchModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
    <div class="bg-white rounded-xl shadow-xl w-[480px] max-h-[80vh] flex flex-col">
        <div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center">
            <h3 class="font-semibold">搜索聊天记录</h3>
            <button onclick="hideSearchPanel()" class="text-slate-400 hover:text-slate-600 text-lg">&times;</button>
        </div>
        <div class="p-4">
            <input id="msgSearchInput" type="text" placeholder="输入关键词搜索..." class="w-full px-3 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50" oninput="debounceSearch()">
        </div>
        <div id="searchResults" class="flex-1 overflow-y-auto px-4 pb-4 space-y-2 max-h-96"></div>
    </div>
</div>

<!-- 群文件弹窗 -->
<div id="fileListModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
    <div class="bg-white rounded-xl shadow-xl w-[480px] max-h-[80vh] flex flex-col">
        <div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center">
            <h3 class="font-semibold">群文件</h3>
            <button onclick="hideFileList()" class="text-slate-400 hover:text-slate-600 text-lg">&times;</button>
        </div>
        <div id="fileListContent" class="flex-1 overflow-y-auto p-4 space-y-2 max-h-96"></div>
    </div>
</div>

<!-- 群昵称弹窗 -->
<div id="nicknameModal" class="fixed inset-0 bg-black/50 z-[9991] hidden items-center justify-center">
    <div class="bg-white rounded-xl shadow-xl w-80 flex flex-col">
        <div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center">
            <h3 class="font-semibold">设置群昵称</h3>
            <button onclick="hideNicknameModal()" class="text-slate-400 hover:text-slate-600 text-lg">&times;</button>
        </div>
        <div class="p-4 space-y-3">
            <input id="nicknameInput" type="text" placeholder="输入群内昵称（留空使用默认名称）" class="w-full px-3 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50" maxlength="50">
            <button onclick="saveNickname()" class="w-full px-4 py-2 bg-primary text-white rounded-lg hover:bg-blue-600 text-sm">保存</button>
        </div>
    </div>
</div>

<!-- 图片预览弹窗 -->
<div id="imagePreview" class="fixed inset-0 bg-black/80 z-[9990] hidden flex items-center justify-center cursor-pointer" onclick="this.classList.add('hidden')">
    <img id="previewImg" class="max-w-[90vw] max-h-[90vh] rounded-lg">
</div>
{% endblock %}

{% block scripts %}
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script>
const currentUser = {{ user | tojson }};
if (!currentUser) window.location.href = '/login';
const socket = io();
let currentRoomId = null;
let rooms = [];
let replyToId = null;
let loadingMore = false;
let oldestMsgId = null;
let currentRoomMembers = [];
let currentRoomCreatorId = null;
let myRoomRole = null;
let mediaRecorder = null;
let audioChunks = [];
let recordingStartTime = null;
let recordingInterval = null;
let searchTimer = null;
let mentionMembers = [];

function isMobile() { return window.innerWidth < 640; }

function mobileBack() {
    if (!isMobile()) return;
    document.getElementById('rightPanel').classList.add('hidden');
    document.getElementById('rightPanel').classList.remove('flex');
    document.getElementById('leftPanel').classList.remove('hidden');
    document.getElementById('leftPanel').classList.add('flex');
}

function mobileShowRight() {
    if (!isMobile()) return;
    document.getElementById('leftPanel').classList.add('hidden');
    document.getElementById('leftPanel').classList.remove('flex');
    document.getElementById('rightPanel').classList.remove('hidden');
    document.getElementById('rightPanel').classList.add('flex');
}

const EMOJIS = ['😀','😂','😍','🥰','😎','🤔','😅','😭','😤','👍','👎','❤️','🔥','🎉','👏','🙏','💪','😊','🥺','😢','😡','🤣','😘','🤗','😱','💯','✅','❌','⭐','🌟'];

// ========== 初始化 ==========
document.addEventListener('DOMContentLoaded', () => {
    loadRooms();
    initEmoji();
    checkUnreadNotifs();
    // 初始化富文本编辑器
    new RichEditor('msgInput', {
        compact: true,
        onEnter: sendMessage
    });
    // URL参数自动打开聊天室
    const params = new URLSearchParams(window.location.search);
    if (params.get('room_id')) {
        setTimeout(() => selectRoom(parseInt(params.get('room_id'))), 500);
    }
    if (params.get('dm')) {
        const dmUserId = parseInt(params.get('dm'));
        setTimeout(async () => {
            try {
                const res = await fetch(`/api/chat/private/${dmUserId}`, { method: 'POST' });
                const data = await res.json();
                if (data.success) {
                    await loadRooms();
                    selectRoom(data.room_id);
                }
            } catch(e) { console.error('打开私聊失败', e); }
        }, 500);
    }
    if (params.get('tab') === 'notif') {
        switchTab('notif');
    }
});

// ========== 通知系统 ==========
let notifData = [];

function switchTab(tab) {
    const chatPanel = document.getElementById('chatPanel');
    const notifPanel = document.getElementById('chatNotifPanel');
    const tabChat = document.getElementById('tabChat');
    const tabNotif = document.getElementById('tabNotif');
    if (tab === 'chat') {
        chatPanel.classList.remove('hidden');
        chatPanel.classList.add('flex');
        notifPanel.classList.add('hidden');
        tabChat.className = 'flex-1 px-3 py-2.5 text-sm font-medium text-primary border-b-2 border-primary';
        tabNotif.className = 'flex-1 px-3 py-2.5 text-sm font-medium text-slate-400 border-b-2 border-transparent hover:text-slate-600 relative';
    } else {
        chatPanel.classList.add('hidden');
        chatPanel.classList.remove('flex');
        notifPanel.classList.remove('hidden');
        tabChat.className = 'flex-1 px-3 py-2.5 text-sm font-medium text-slate-400 border-b-2 border-transparent hover:text-slate-600';
        tabNotif.className = 'flex-1 px-3 py-2.5 text-sm font-medium text-primary border-b-2 border-primary relative';
        loadNotifications();
    }
    const badge = document.getElementById('chatNotifBadge');
    tabNotif.appendChild(badge);
}

async function loadNotifications() {
    const res = await fetch('/api/notifications');
    const data = await res.json();
    if (data.success) {
        notifData = data.notifications;
        renderNotifications();
    }
}

function renderNotifications() {
    const list = document.getElementById('chatNotifList');
    const empty = document.getElementById('chatNotifEmpty');
    if (notifData.length === 0) {
        list.innerHTML = '';
        empty.classList.remove('hidden');
        return;
    }
    empty.classList.add('hidden');
    list.innerHTML = notifData.map(n => {
        const isContestApp = n.type === 'contest_application';
        const isTeacherApp = n.type === 'teacher_application';
        const isFriendReq = n.type === 'friend_request';
        const isResult = n.type === 'contest_result' || n.type === 'teacher_result';
        const isNewExam = n.type === 'contest_new_exam';
        const isGraded = n.type === 'exam_graded';
        const isSystem = n.type === 'system_announcement';
        const isPendingContest = isContestApp && n.application_status === 'pending';
        const isPendingTeacher = isTeacherApp && n.application_status === 'pending';
        const isPendingFriend = isFriendReq && n.application_status === 'pending';
        let statusHtml = '';
        if ((isContestApp || isTeacherApp) && n.application_status === 'approved') statusHtml = '<span class="text-xs text-green-600 font-medium">已批准</span>';
        else if ((isContestApp || isTeacherApp) && n.application_status === 'rejected') statusHtml = '<span class="text-xs text-red-600 font-medium">已拒绝</span>';
        else if (isFriendReq && n.application_status === 'accepted') statusHtml = '<span class="text-xs text-green-600 font-medium">已同意</span>';
        const icon = isContestApp ? '📋' : isTeacherApp ? '👨‍🏫' : isFriendReq ? '👤' : isResult ? '📢' : isNewExam ? '📝' : isGraded ? '✅' : isSystem ? '📢' : '🔔';
        const iconBg = isContestApp ? 'bg-orange-100' : isTeacherApp ? 'bg-purple-100' : isFriendReq ? 'bg-blue-100' : isResult ? 'bg-green-100' : isNewExam ? 'bg-indigo-100' : isGraded ? 'bg-emerald-100' : isSystem ? 'bg-amber-100' : 'bg-blue-100';
        const clickAction = `showNotifDetail(${n.id})`;
        let actionsHtml = '';
        if (isPendingContest && currentUser.role === 'admin') {
            actionsHtml = `<div class="flex gap-2 mt-2">
                <button onclick="event.stopPropagation();approveContest(${n.post_id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">批准</button>
                <button onclick="event.stopPropagation();rejectContest(${n.post_id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
            </div>`;
        }
        if (isPendingTeacher && currentUser.role === 'admin' && n.application_id) {
            actionsHtml = `<div class="flex gap-2 mt-2">
                <button onclick="event.stopPropagation();approveTeacher(${n.application_id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">批准</button>
                <button onclick="event.stopPropagation();rejectTeacher(${n.application_id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
            </div>`;
        }
        if (isPendingFriend && n.friend_request_id) {
            actionsHtml = `<div class="flex gap-2 mt-2">
                <button onclick="event.stopPropagation();acceptFriendReq(${n.friend_request_id},${n.id})" class="px-3 py-1 text-xs bg-green-500 text-white rounded hover:bg-green-600">同意</button>
                <button onclick="event.stopPropagation();rejectFriendReq(${n.friend_request_id},${n.id})" class="px-3 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600">拒绝</button>
            </div>`;
        }
        return `<div class="px-3 py-3 ${n.read ? '' : 'bg-blue-50'} hover:bg-slate-50 border-b border-slate-100 transition cursor-pointer" onclick="${clickAction}">
            <div class="flex items-start gap-2">
                <div class="w-8 h-8 rounded-full ${iconBg} flex items-center justify-center flex-shrink-0 mt-0.5">
                    <span class="text-sm">${icon}</span>
                </div>
                <div class="flex-1 min-w-0">
                    <p class="text-sm text-slate-700">${escHtml(n.content)}</p>
                    <div class="flex items-center gap-2 mt-1">
                        <span class="text-xs text-slate-400">${n.created_at}</span>
                        ${statusHtml}
                    </div>
                    ${actionsHtml}
                </div>
            </div>
        </div>`;
    }).join('');
}

async function markNotifRead(nid, el) {
    if (el && el.classList.contains('bg-blue-50')) {
        el.classList.remove('bg-blue-50');
        await fetch(`/api/notifications/${nid}/read`, { method: 'POST' });
        checkUnreadNotifs();
    }
}

async function approveContest(appId) {
    if (!confirm('确定批准该杯赛申请？')) return;
    const res = await fetch(`/api/contest-applications/${appId}/approve`, { method: 'POST' });
    const data = await res.json();
    if (data.success) {
        loadNotifications();
    } else {
        alert(data.message || '操作失败');
    }
}

async function rejectContest(appId) {
    if (!confirm('确定拒绝该杯赛申请？')) return;
    const res = await fetch(`/api/contest-applications/${appId}/reject`, { method: 'POST' });
    const data = await res.json();
    if (data.success) {
        loadNotifications();
    } else {
        alert(data.message || '操作失败');
    }
}

async function approveTeacher(appId) {
    if (!confirm('确定批准该教师申请？')) return;
    const res = await fetch(`/api/teacher-applications/${appId}/approve`, { method: 'POST' });
    const data = await res.json();
    if (data.success) {
        loadNotifications();
    } else {
        alert(data.message || '操作失败');
    }
}

async function rejectTeacher(appId) {
    if (!confirm('确定拒绝该教师申请？')) return;
    const res = await fetch(`/api/teacher-applications/${appId}/reject`, { method: 'POST' });
    const data = await res.json();
    if (data.success) {
        loadNotifications();
    } else {
        alert(data.message || '操作失败');
    }
}

async function acceptFriendReq(reqId, notifId) {
    const res = await fetch(`/api/friend/accept/${reqId}`, { method: 'POST' });
    const data = await res.json();
    if (data.success) {
        loadNotifications();
    } else {
        alert(data.message || '操作失败');
    }
}

async function rejectFriendReq(reqId, notifId) {
    const res = await fetch(`/api/friend/reject/${reqId}`, { method: 'POST' });
    const data = await res.json();
    if (data.success) {
        loadNotifications();
    } else {
        alert(data.message || '操作失败');
    }
}

async function checkUnreadNotifs() {
    const res = await fetch('/api/notifications/unread-count');
    const data = await res.json();
    if (data.success) {
        const badge = document.getElementById('chatNotifBadge');
        if (data.count > 0) {
            badge.textContent = data.count > 99 ? '99+' : data.count;
            badge.classList.remove('hidden');
        } else {
            badge.classList.add('hidden');
        }
    }
}
setInterval(checkUnreadNotifs, 30000);

function showNotifDetail(nid) {
    const n = notifData.find(x => x.id === nid);
    if (!n) return;
    // 标记已读
    if (!n.read) {
        n.read = true;
        fetch(`/api/notifications/${nid}/read`, { method: 'POST' });
        checkUnreadNotifs();
        renderNotifications();
    }
    // 隐藏聊天视图和空状态，显示通知详情
    document.getElementById('emptyState').classList.add('hidden');
    document.getElementById('chatView').classList.add('hidden');
    document.getElementById('chatView').classList.remove('flex');
    const detail = document.getElementById('notifDetailView');
    detail.classList.remove('hidden');
    detail.classList.add('flex');
    mobileShowRight();

    const typeLabels = {
        'teacher_application': '教师申请', 'teacher_result': '教师审核结果',
        'contest_application': '杯赛申请', 'contest_result': '杯赛通知',
        'contest_new_exam': '新考试', 'exam_graded': '成绩通知',
        'system_announcement': '系统通知', 'friend_request': '好友申请'
    };
    const typeIcons = {
        'teacher_application': '👨‍🏫', 'teacher_result': '🎓',
        'contest_application': '📋', 'contest_result': '🏅',
        'contest_new_exam': '📝', 'exam_graded': '✅',
        'system_announcement': '📢', 'friend_request': '👤'
    };

    document.getElementById('notifDetailIcon').textContent = typeIcons[n.type] || '🔔';
    document.getElementById('notifDetailType').textContent = typeLabels[n.type] || '通知';
    document.getElementById('notifDetailTime').textContent = n.created_at || '';
    document.getElementById('notifDetailContent').textContent = n.content || '';
    document.getElementById('notifDetailFrom').textContent = n.from_user ? '来自：' + n.from_user : '';

    // 操作按钮
    let actHtml = '';
    if (n.type === 'contest_application' && n.application_status === 'pending' && n.post_id && currentUser.role === 'admin') {
        actHtml = `<div class="flex gap-3">
            <button onclick="approveContest(${n.post_id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">批准</button>
            <button onclick="rejectContest(${n.post_id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
        </div>`;
    } else if (n.type === 'teacher_application' && n.application_status === 'pending' && n.application_id && currentUser.role === 'admin') {
        actHtml = `<div class="flex gap-3">
            <button onclick="approveTeacher(${n.application_id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">批准</button>
            <button onclick="rejectTeacher(${n.application_id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
        </div>`;
    } else if (n.type === 'contest_new_exam' || n.type === 'exam_graded') {
        actHtml = `<a href="/exams/${n.post_id}" class="inline-block px-4 py-2 text-sm bg-primary text-white rounded-md hover:bg-blue-700">查看考试</a>`;
    } else if (n.type === 'friend_request' && n.application_status === 'pending' && n.friend_request_id) {
        actHtml = `<div class="flex gap-3">
            <button onclick="acceptFriendReq(${n.friend_request_id},${n.id})" class="px-4 py-2 text-sm bg-green-500 text-white rounded-md hover:bg-green-600">同意</button>
            <button onclick="rejectFriendReq(${n.friend_request_id},${n.id})" class="px-4 py-2 text-sm bg-red-500 text-white rounded-md hover:bg-red-600">拒绝</button>
        </div>`;
    } else if (n.type === 'friend_request' && n.application_status === 'accepted') {
        actHtml = '<span class="text-sm text-green-600 font-medium">✅ 已同意</span>';
    } else if ((n.type === 'contest_application' || n.type === 'teacher_application') && n.application_status === 'approved') {
        actHtml = '<span class="text-sm text-green-600 font-medium">✅ 已批准</span>';
    } else if ((n.type === 'contest_application' || n.type === 'teacher_application') && n.application_status === 'rejected') {
        actHtml = '<span class="text-sm text-red-600 font-medium">❌ 已拒绝</span>';
    }
    document.getElementById('notifDetailActions').innerHTML = actHtml;
}

function initEmoji() {
    const grid = document.getElementById('emojiGrid');
    EMOJIS.forEach(e => {
        const btn = document.createElement('button');
        btn.className = 'w-8 h-8 text-lg hover:bg-slate-100 rounded flex items-center justify-center';
        btn.textContent = e;
        btn.onclick = () => { insertEmoji(e); };
        grid.appendChild(btn);
    });
}

function insertEmoji(e) {
    const input = document.getElementById('msgInput');
    input.value += e;
    input.focus();
}

function toggleEmoji() {
    document.getElementById('emojiPanel').classList.toggle('hidden');
}

// ========== 聊天室列表 ==========
async function loadRooms() {
    const res = await fetch('/api/chat/rooms');
    const data = await res.json();
    if (data.success) {
        rooms = data.rooms;
        renderRooms();
    }
}

function renderRooms() {
    const search = document.getElementById('roomSearch').value.toLowerCase();
    const list = document.getElementById('roomList');
    const filtered = rooms.filter(r => (r.name || '').toLowerCase().includes(search));
    list.innerHTML = filtered.map(r => `
        <div class="flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-slate-50 transition ${currentRoomId === r.id ? 'bg-blue-50 border-r-2 border-primary' : ''}" onclick="selectRoom(${r.id})">
            <div class="w-10 h-10 rounded-full bg-slate-200 flex items-center justify-center flex-shrink-0 overflow-hidden">
                ${r.avatar ? `<img src="${r.avatar}" class="w-full h-full object-cover">` :
                  `<span class="text-sm font-medium text-slate-500">${(r.name || '?')[0]}</span>`}
            </div>
            <div class="flex-1 min-w-0">
                <div class="flex justify-between items-center">
                    <span class="text-sm font-medium text-slate-800 truncate">${escHtml(r.name || '未命名')}</span>
                    <span class="text-xs text-slate-400 flex-shrink-0">${r.last_message ? formatTime(r.last_message.created_at) : ''}</span>
                </div>
                <div class="flex justify-between items-center mt-0.5">
                    <span class="text-xs text-slate-400 truncate">${r.last_message ? escHtml(getPreview(r)) : '暂无消息'}</span>
                    ${r.unread > 0 ? `<span class="bg-red-500 text-white text-xs rounded-full px-1.5 min-w-[18px] text-center flex-shrink-0">${r.unread > 99 ? '99+' : r.unread}</span>` : ''}
                </div>
            </div>
        </div>
    `).join('');
}

function getPreview(r) {
    if (!r.last_message) return '';
    const lm = r.last_message;
    let text = lm.content || '';
    if (lm.type === 'image') text = '[图片]';
    else if (lm.type === 'file') text = '[文件]';
    if (r.type !== 'private' && lm.sender_name) text = lm.sender_name + ': ' + text;
    return text;
}

function filterRooms() { renderRooms(); }

// ========== 选择聊天室 ==========
async function selectRoom(roomId) {
    currentRoomId = roomId;
    oldestMsgId = null;
    document.getElementById('emptyState').classList.add('hidden');
    // 隐藏通知详情
    const nd = document.getElementById('notifDetailView');
    nd.classList.add('hidden');
    nd.classList.remove('flex');
    const cv = document.getElementById('chatView');
    cv.classList.remove('hidden');
    cv.classList.add('flex');
    mobileShowRight();
    const room = rooms.find(r => r.id === roomId);
    if (room) {
        document.getElementById('chatName').textContent = room.name || '聊天';
        document.getElementById('chatMemberCount').textContent = room.type !== 'private' ? `(${room.member_count}人)` : '';
    }
    renderRooms();
    await loadMessages(roomId);
    markRead(roomId);
}
// ========== 消息加载与渲染 ==========
async function loadMessages(roomId, beforeId) {
    let url = `/api/chat/rooms/${roomId}/messages?limit=30`;
    if (beforeId) url += `&before_id=${beforeId}`;
    const res = await fetch(url);
    const data = await res.json();
    if (!data.success) return;
    const area = document.getElementById('messageArea');
    if (!beforeId) {
        area.innerHTML = '';
        data.messages.forEach(m => area.appendChild(createMsgEl(m)));
        area.scrollTop = area.scrollHeight;
    } else {
        const oldH = area.scrollHeight;
        data.messages.forEach((m, i) => area.insertBefore(createMsgEl(m), area.children[i]));
        area.scrollTop = area.scrollHeight - oldH;
    }
    if (data.messages.length > 0) oldestMsgId = data.messages[0].id;
    loadingMore = false;
}

function handleScroll() {
    const area = document.getElementById('messageArea');
    if (area.scrollTop < 50 && !loadingMore && oldestMsgId && currentRoomId) {
        loadingMore = true;
        loadMessages(currentRoomId, oldestMsgId);
    }
}

function createMsgEl(msg) {
    const div = document.createElement('div');
    div.id = `msg-${msg.id}`;
    div.dataset.msgId = msg.id;
    if (msg.type === 'system') {
        div.className = 'text-center';
        div.innerHTML = `<span class="text-xs text-slate-400 bg-slate-100 px-3 py-1 rounded-full">${escHtml(msg.content)}</span>`;
        return div;
    }
    const isMe = msg.sender_id === currentUser.id;
    if (msg.recalled) {
        div.className = 'text-center';
        div.innerHTML = `<span class="text-xs text-slate-400">${escHtml(msg.sender_name)} 撤回了一条消息</span>`;
        return div;
    }
    div.className = `flex ${isMe ? 'justify-end' : 'justify-start'} gap-2`;
    const avatarHtml = msg.sender_avatar
        ? `<img src="${msg.sender_avatar}" class="w-8 h-8 rounded-full object-cover flex-shrink-0">`
        : `<div class="w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center flex-shrink-0"><span class="text-xs text-slate-500">${(msg.sender_name||'?')[0]}</span></div>`;

    let contentHtml = '';
    if (msg.type === 'image' && msg.file_url) {
        contentHtml = `<img src="${msg.file_url}" class="max-w-[240px] rounded-lg cursor-pointer hover:opacity-90" onclick="previewImage('${msg.file_url}')">`;
    } else if (msg.type === 'file' && msg.file_url) {
        contentHtml = `<a href="${msg.file_url}" download="${escHtml(msg.file_name||'文件')}" class="flex items-center gap-2 text-sm text-primary hover:underline">
            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
            ${escHtml(msg.file_name||'文件')}</a>`;
    } else if (msg.type === 'voice' && msg.file_url) {
        contentHtml = `<div class="flex items-center gap-2 cursor-pointer voice-msg" onclick="playVoice(this, '${msg.file_url}')">
            <svg class="w-5 h-5 text-current" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072M12 6v12m-3.536-2.464a5 5 0 010-7.072"/></svg>
            <span class="text-sm">${escHtml(msg.content || '语音')}</span>
            <div class="voice-bars flex items-end gap-0.5 h-4"><span class="w-0.5 bg-current rounded-full" style="height:40%"></span><span class="w-0.5 bg-current rounded-full" style="height:70%"></span><span class="w-0.5 bg-current rounded-full" style="height:100%"></span><span class="w-0.5 bg-current rounded-full" style="height:60%"></span><span class="w-0.5 bg-current rounded-full" style="height:30%"></span></div>
        </div>`;
    } else {
        contentHtml = `<span class="whitespace-pre-wrap break-words">${renderRichContent(msg.content)}</span>`;
    }

    // 表情回应
    let reactionsHtml = '';
    if (msg.reactions && Object.keys(msg.reactions).length > 0) {
        const items = Object.entries(msg.reactions).map(([emoji, data]) =>
            `<button class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs border ${data.users.some(u=>u.id===currentUser.id) ? 'bg-indigo-50 border-indigo-200 text-indigo-600' : 'bg-slate-50 border-slate-200 text-slate-500'} hover:bg-indigo-50 transition" onclick="toggleReaction(${msg.id},'${emoji}')" title="${data.users.map(u=>u.name).join(', ')}">${emoji} ${data.count}</button>`
        ).join('');
        reactionsHtml = `<div class="flex flex-wrap gap-1 mt-1" id="reactions-${msg.id}">${items}</div>`;
    } else {
        reactionsHtml = `<div class="flex flex-wrap gap-1 mt-1" id="reactions-${msg.id}"></div>`;
    }

    let replyHtml = '';
    if (msg.reply_to) {
        replyHtml = `<div class="text-xs text-slate-400 mb-1 px-2 py-1 bg-slate-50 rounded border-l-2 border-slate-300 truncate">回复 ${escHtml(msg.reply_to.sender_name)}: ${escHtml(msg.reply_to.content)}</div>`;
    }

    const bubbleColor = isMe ? 'bg-primary text-white' : 'bg-white border border-slate-200 text-slate-800';
    const nameHtml = !isMe ? `<div class="text-xs text-slate-400 mb-0.5">${escHtml(msg.sender_name)}</div>` : '';

    const readStatus = isMe && rooms.find(r=>r.id===currentRoomId)?.type === 'private'
        ? `<div class="text-xs text-slate-400 mt-0.5 text-right" id="read-${msg.id}"></div>` : '';

    if (isMe) {
        div.innerHTML = `
            <div class="max-w-[70%] flex flex-col items-end">
                ${replyHtml}
                <div class="${bubbleColor} px-3 py-2 rounded-xl rounded-tr-sm text-sm shadow-sm cursor-pointer" oncontextmenu="showMsgMenu(event,${msg.id},true)">${contentHtml}</div>
                ${reactionsHtml}
                <div class="text-xs text-slate-400 mt-0.5">${formatTime(msg.created_at)}</div>
                ${readStatus}
            </div>
            ${avatarHtml}`;
    } else {
        div.innerHTML = `
            ${avatarHtml}
            <div class="max-w-[70%]">
                ${nameHtml}${replyHtml}
                <div class="${bubbleColor} px-3 py-2 rounded-xl rounded-tl-sm text-sm shadow-sm cursor-pointer" oncontextmenu="showMsgMenu(event,${msg.id},false)">${contentHtml}</div>
                ${reactionsHtml}
                <div class="text-xs text-slate-400 mt-0.5">${formatTime(msg.created_at)}</div>
            </div>`;
    }
    return div;
}
// ========== 发送消息 ==========
function sendMessage() {
    const input = document.getElementById('msgInput');
    const content = input.value.trim();
    if (!content || !currentRoomId) return;
    const mentionsStr = mentionMembers.length > 0 ? (mentionMembers.includes('all') ? 'all' : JSON.stringify(mentionMembers)) : '';
    socket.emit('send_message', {
        room_id: currentRoomId, type: 'text', content: content,
        reply_to_id: replyToId, mentions: mentionsStr
    });
    input.value = '';
    mentionMembers = [];
    cancelReply();
    document.getElementById('emojiPanel').classList.add('hidden');
}

function handleInputKey(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        sendMessage();
    }
    // @ 触发提及面板
    if (e.key === '@' || e.key === '2' && e.shiftKey) {
        setTimeout(() => insertMention(), 50);
    }
}

let typingTimer = null;
function handleTyping() {
    if (!currentRoomId) return;
    if (!typingTimer) {
        socket.emit('typing', { room_id: currentRoomId });
    }
    clearTimeout(typingTimer);
    typingTimer = setTimeout(() => { typingTimer = null; }, 2000);
}

// ========== 文件上传 ==========
async function uploadFile(input, type) {
    if (!input.files[0] || !currentRoomId) return;
    const fd = new FormData();
    fd.append('file', input.files[0]);
    const fileName = input.files[0].name;
    const res = await fetch('/api/upload', { method: 'POST', body: fd });
    const data = await res.json();
    if (data.success) {
        socket.emit('send_message', {
            room_id: currentRoomId,
            type: type,
            content: type === 'image' ? '[图片]' : fileName,
            file_url: data.url,
            file_name: fileName,
            reply_to_id: replyToId
        });
        cancelReply();
    } else {
        alert(data.message || '上传失败');
    }
    input.value = '';
}

// ========== 引用回复 ==========
function setReply(msgId) {
    const msgEl = document.getElementById(`msg-${msgId}`);
    if (!msgEl) return;
    replyToId = msgId;
    const text = msgEl.querySelector('.whitespace-pre-wrap')?.textContent || '[消息]';
    document.getElementById('replyToText').textContent = '回复: ' + text.substring(0, 30);
    document.getElementById('replyPreview').classList.remove('hidden');
    document.getElementById('msgInput').focus();
    hideMsgMenu();
}

function cancelReply() {
    replyToId = null;
    document.getElementById('replyPreview').classList.add('hidden');
}

// ========== 消息右键菜单 ==========
let msgMenuEl = null;
function showMsgMenu(e, msgId, isMe) {
    e.preventDefault();
    hideMsgMenu();
    const menu = document.createElement('div');
    menu.id = 'msgContextMenu';
    menu.className = 'fixed bg-white border border-slate-200 rounded-lg shadow-lg py-1 z-50';
    menu.style.left = e.clientX + 'px';
    menu.style.top = e.clientY + 'px';
    menu.innerHTML = `
        <div class="flex gap-1 px-2 py-1.5 border-b border-slate-100">${EMOJIS.slice(0,8).map(e => `<button class="w-7 h-7 text-base hover:bg-slate-100 rounded flex items-center justify-center" onclick="toggleReaction(${msgId},'${e}')">${e}</button>`).join('')}</div>
        <button class="w-full px-4 py-2 text-sm text-left hover:bg-slate-50" onclick="setReply(${msgId})">引用回复</button>
        <button class="w-full px-4 py-2 text-sm text-left hover:bg-slate-50" onclick="copyMsg(${msgId})">复制文字</button>
        ${isMe ? `<button class="w-full px-4 py-2 text-sm text-left text-red-500 hover:bg-red-50" onclick="recallMsg(${msgId})">撤回</button>` : ''}
    `;
    document.body.appendChild(menu);
    msgMenuEl = menu;
    setTimeout(() => document.addEventListener('click', hideMsgMenu, { once: true }), 10);
}

function hideMsgMenu() {
    if (msgMenuEl) { msgMenuEl.remove(); msgMenuEl = null; }
}

function copyMsg(msgId) {
    const el = document.querySelector(`#msg-${msgId} .whitespace-pre-wrap`);
    if (el) navigator.clipboard.writeText(el.textContent);
    hideMsgMenu();
}

async function recallMsg(msgId) {
    hideMsgMenu();
    const res = await fetch(`/api/chat/messages/${msgId}/recall`, { method: 'POST' });
    const data = await res.json();
    if (!data.success) alert(data.message || '撤回失败');
}

// ========== 图片预览 ==========
function previewImage(url) {
    document.getElementById('previewImg').src = url;
    document.getElementById('imagePreview').classList.remove('hidden');
}

// ========== 标记已读 ==========
function markRead(roomId) {
    fetch(`/api/chat/rooms/${roomId}/read`, { method: 'POST' });
    socket.emit('mark_read', { room_id: roomId });
    const room = rooms.find(r => r.id === roomId);
    if (room) { room.unread = 0; renderRooms(); }
}

// ========== 创建群聊 ==========
async function showCreateGroup() {
    const modal = document.getElementById('createGroupModal');
    modal.classList.remove('hidden');
    modal.classList.add('flex');
    // 重置弹窗标题和按钮（防止被邀请好友功能覆盖）
    document.querySelector('#createGroupModal h3').textContent = '创建群聊';
    const createBtn = document.querySelector('#createGroupModal .bg-primary');
    createBtn.textContent = '创建';
    createBtn.onclick = createGroup;
    document.getElementById('groupName').value = '';
    const res = await fetch('/api/user/friends');
    const data = await res.json();
    const list = document.getElementById('friendListForGroup');
    if (data.success && data.friends.length > 0) {
        list.innerHTML = data.friends.map(f => `
            <label class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-slate-50 cursor-pointer">
                <input type="checkbox" value="${f.id}" class="friend-check rounded">
                <div class="w-7 h-7 rounded-full bg-slate-200 flex items-center justify-center overflow-hidden flex-shrink-0">
                    ${f.avatar ? `<img src="${f.avatar}" class="w-full h-full object-cover">` : `<span class="text-xs">${f.name[0]}</span>`}
                </div>
                <span class="text-sm">${escHtml(f.name)}</span>
            </label>
        `).join('');
    } else {
        list.innerHTML = '<p class="text-sm text-slate-400 text-center py-4">暂无好友，请先添加好友</p>';
    }
}

function hideCreateGroup() {
    const modal = document.getElementById('createGroupModal');
    modal.classList.add('hidden');
    modal.classList.remove('flex');
}

async function createGroup() {
    const name = document.getElementById('groupName').value.trim();
    if (!name) return alert('请输入群名称');
    const ids = [...document.querySelectorAll('.friend-check:checked')].map(c => parseInt(c.value));
    const res = await fetch('/api/chat/rooms', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, member_ids: ids })
    });
    const data = await res.json();
    if (data.success) {
        hideCreateGroup();
        await loadRooms();
        selectRoom(data.room_id);
    } else {
        alert(data.message || '创建失败');
    }
}

// ========== 成员列表 ==========
async function showMembers() {
    if (!currentRoomId) return;
    const modal = document.getElementById('membersModal');
    modal.classList.remove('hidden');
    modal.classList.add('flex');
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/members`);
    const data = await res.json();
    if (!data.success) return;
    currentRoomMembers = data.members;
    currentRoomCreatorId = data.creator_id;
    myRoomRole = data.members.find(m => m.id === currentUser.id)?.role || 'member';
    const isAdmin = myRoomRole === 'admin';
    const isCreator = currentUser.id === data.creator_id;
    const room = rooms.find(r => r.id === currentRoomId);
    const isGroup = room && room.type !== 'private';
    const list = document.getElementById('membersList');
    list.innerHTML = data.members.map(m => {
        const roleTag = m.is_creator ? '<span class="text-xs bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded">群主</span>'
            : m.role === 'admin' ? '<span class="text-xs bg-indigo-100 text-indigo-700 px-1.5 py-0.5 rounded">管理员</span>' : '';
        const mutedTag = m.muted ? '<span class="text-xs bg-red-100 text-red-600 px-1.5 py-0.5 rounded">已禁言</span>' : '';
        const nicknameTag = m.nickname ? `<span class="text-xs text-slate-400">(${escHtml(m.nickname)})</span>` : '';
        let actions = '';
        if (isGroup && m.id !== currentUser.id) {
            if (isAdmin && !m.is_creator) {
                actions += `<button onclick="muteMember(${m.id})" class="text-xs ${m.muted ? 'text-green-500 hover:text-green-700' : 'text-orange-500 hover:text-orange-700'}">${m.muted ? '解禁' : '禁言'}</button>`;
                actions += `<button onclick="kickMember(${m.id},'${escHtml(m.name)}')" class="text-xs text-red-500 hover:text-red-700">移除</button>`;
            }
            if (isCreator && !m.is_creator) {
                actions += `<button onclick="setAdmin(${m.id})" class="text-xs text-indigo-500 hover:text-indigo-700">${m.role === 'admin' ? '取消管理' : '设为管理'}</button>`;
                actions += `<button onclick="transferOwner(${m.id},'${escHtml(m.name)}')" class="text-xs text-amber-500 hover:text-amber-700">转让群主</button>`;
            }
        }
        return `<div class="flex items-center gap-3 py-2 px-2 rounded-lg hover:bg-slate-50">
            <div class="w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center overflow-hidden flex-shrink-0">
                ${m.avatar ? `<img src="${m.avatar}" class="w-full h-full object-cover">` : `<span class="text-xs">${m.name[0]}</span>`}
            </div>
            <div class="flex-1 min-w-0">
                <div class="flex items-center gap-1.5 flex-wrap">
                    <span class="text-sm font-medium">${escHtml(m.name)}</span>${nicknameTag}${roleTag}${mutedTag}
                </div>
            </div>
            <div class="flex items-center gap-2 flex-shrink-0">${actions}</div>
        </div>`;
    }).join('');
    document.getElementById('inviteSection').classList.toggle('hidden', !isGroup);
}

function hideMembers() {
    const modal = document.getElementById('membersModal');
    modal.classList.add('hidden');
    modal.classList.remove('flex');
}

async function showInvite() {
    hideMembers();
    showCreateGroup();
    document.querySelector('#createGroupModal h3').textContent = '邀请好友';
    const btn = document.querySelector('#createGroupModal .bg-primary');
    btn.textContent = '邀请';
    btn.onclick = async () => {
        const ids = [...document.querySelectorAll('.friend-check:checked')].map(c => parseInt(c.value));
        if (ids.length === 0) return;
        const res = await fetch(`/api/chat/rooms/${currentRoomId}/members`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ user_ids: ids })
        });
        const data = await res.json();
        hideCreateGroup();
        if (data.success) loadRooms();
    };
}

// ========== 群管理操作 ==========
async function muteMember(uid) {
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/mute/${uid}`, { method: 'POST' });
    const data = await res.json();
    if (data.success) showMembers();
    else alert(data.message || '操作失败');
}

async function kickMember(uid, name) {
    if (!confirm(`确定移除 ${name} ？`)) return;
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/members/${uid}`, { method: 'DELETE' });
    const data = await res.json();
    if (data.success) { showMembers(); loadRooms(); }
    else alert(data.message || '操作失败');
}

async function setAdmin(uid) {
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/set-admin/${uid}`, { method: 'POST' });
    const data = await res.json();
    if (data.success) showMembers();
    else alert(data.message || '操作失败');
}

async function transferOwner(uid, name) {
    if (!confirm(`确定将群主转让给 ${name} ？此操作不可撤销！`)) return;
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/transfer/${uid}`, { method: 'POST' });
    const data = await res.json();
    if (data.success) { showMembers(); loadRooms(); }
    else alert(data.message || '操作失败');
}

// ========== 群公告 ==========
async function showAnnouncement() {
    if (!currentRoomId) return;
    const modal = document.getElementById('announcementModal');
    modal.classList.remove('hidden');
    modal.classList.add('flex');
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/announcement`);
    const data = await res.json();
    if (data.success) {
        const display = document.getElementById('announcementDisplay');
        const meta = document.getElementById('announcementMeta');
        display.textContent = data.announcement || '暂无公告';
        if (data.by) {
            meta.textContent = `${data.by} 发布于 ${data.at}`;
            meta.classList.remove('hidden');
        } else { meta.classList.add('hidden'); }
    }
    document.getElementById('announcementEditSection').classList.add('hidden');
    // 检查是否管理员
    if (!currentRoomMembers.length) {
        const mRes = await fetch(`/api/chat/rooms/${currentRoomId}/members`);
        const mData = await mRes.json();
        if (mData.success) { currentRoomMembers = mData.members; myRoomRole = mData.members.find(m => m.id === currentUser.id)?.role || 'member'; }
    }
    document.getElementById('btnEditAnnouncement').classList.toggle('hidden', myRoomRole !== 'admin');
}
function hideAnnouncement() {
    const modal = document.getElementById('announcementModal');
    modal.classList.add('hidden');
    modal.classList.remove('flex');
}
function editAnnouncement() {
    document.getElementById('announcementEditSection').classList.remove('hidden');
    document.getElementById('btnEditAnnouncement').classList.add('hidden');
    document.getElementById('announcementInput').value = document.getElementById('announcementDisplay').textContent === '暂无公告' ? '' : document.getElementById('announcementDisplay').textContent;
}
function cancelEditAnnouncement() {
    document.getElementById('announcementEditSection').classList.add('hidden');
    document.getElementById('btnEditAnnouncement').classList.remove('hidden');
}
async function saveAnnouncement() {
    const content = document.getElementById('announcementInput').value.trim();
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/announcement`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content })
    });
    const data = await res.json();
    if (data.success) { hideAnnouncement(); showAnnouncement(); }
    else alert(data.message || '操作失败');
}

// ========== 搜索聊天记录 ==========
function showSearchPanel() {
    if (!currentRoomId) return;
    const modal = document.getElementById('searchModal');
    modal.classList.remove('hidden');
    modal.classList.add('flex');
    document.getElementById('msgSearchInput').value = '';
    document.getElementById('searchResults').innerHTML = '';
    document.getElementById('msgSearchInput').focus();
}
function hideSearchPanel() {
    const modal = document.getElementById('searchModal');
    modal.classList.add('hidden');
    modal.classList.remove('flex');
}
function debounceSearch() { clearTimeout(searchTimer); searchTimer = setTimeout(doSearch, 300); }
async function doSearch() {
    const q = document.getElementById('msgSearchInput').value.trim();
    const container = document.getElementById('searchResults');
    if (!q) { container.innerHTML = ''; return; }
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/search?q=${encodeURIComponent(q)}`);
    const data = await res.json();
    if (!data.success) return;
    if (data.messages.length === 0) { container.innerHTML = '<p class="text-sm text-slate-400 text-center py-4">未找到相关消息</p>'; return; }
    container.innerHTML = data.messages.map(m => `
        <div class="p-3 bg-slate-50 rounded-lg hover:bg-slate-100 cursor-pointer transition" onclick="hideSearchPanel();scrollToMsg(${m.id})">
            <div class="flex justify-between items-center mb-1">
                <span class="text-xs font-medium text-slate-600">${escHtml(m.sender_name)}</span>
                <span class="text-xs text-slate-400">${m.created_at}</span>
            </div>
            <p class="text-sm text-slate-700 line-clamp-2">${escHtml(m.content)}</p>
        </div>
    `).join('');
}
function scrollToMsg(msgId) {
    const el = document.getElementById(`msg-${msgId}`);
    if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.style.background = '#fef3c7'; setTimeout(() => el.style.background = '', 2000); }
}

// ========== 群文件 ==========
async function showFileList() {
    if (!currentRoomId) return;
    const modal = document.getElementById('fileListModal');
    modal.classList.remove('hidden');
    modal.classList.add('flex');
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/files`);
    const data = await res.json();
    const container = document.getElementById('fileListContent');
    if (!data.success || data.files.length === 0) { container.innerHTML = '<p class="text-sm text-slate-400 text-center py-8">暂无文件</p>'; return; }
    container.innerHTML = data.files.map(f => {
        const icon = f.type === 'image' ? '🖼️' : '📄';
        return `<div class="flex items-center gap-3 p-3 bg-slate-50 rounded-lg hover:bg-slate-100 transition">
            <span class="text-xl">${icon}</span>
            <div class="flex-1 min-w-0">
                <div class="text-sm font-medium text-slate-700 truncate">${escHtml(f.file_name)}</div>
                <div class="text-xs text-slate-400">${escHtml(f.sender_name)} · ${f.created_at}</div>
            </div>
            <a href="${f.file_url}" download="${escHtml(f.file_name)}" class="text-xs text-primary hover:text-blue-700 flex-shrink-0" ${f.type === 'image' ? `onclick="event.preventDefault();previewImage('${f.file_url}')"` : ''}>
                ${f.type === 'image' ? '查看' : '下载'}
            </a>
        </div>`;
    }).join('');
}
function hideFileList() {
    const modal = document.getElementById('fileListModal');
    modal.classList.add('hidden');
    modal.classList.remove('flex');
}

// ========== 群昵称 ==========
function showNicknameModal() {
    const modal = document.getElementById('nicknameModal');
    modal.classList.remove('hidden');
    modal.classList.add('flex');
    const me = currentRoomMembers.find(m => m.id === currentUser.id);
    document.getElementById('nicknameInput').value = me?.nickname || '';
}
function hideNicknameModal() {
    const modal = document.getElementById('nicknameModal');
    modal.classList.add('hidden');
    modal.classList.remove('flex');
}
async function saveNickname() {
    const nickname = document.getElementById('nicknameInput').value.trim();
    const res = await fetch(`/api/chat/rooms/${currentRoomId}/nickname`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ nickname })
    });
    const data = await res.json();
    if (data.success) { hideNicknameModal(); showMembers(); }
    else alert(data.message || '操作失败');
}

// ========== 表情回应 ==========
async function toggleReaction(msgId, emoji) {
    hideMsgMenu();
    const res = await fetch(`/api/chat/messages/${msgId}/reactions`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ emoji })
    });
    const data = await res.json();
    if (data.success) updateReactionsUI(msgId, data.reactions);
}
function updateReactionsUI(msgId, reactions) {
    const container = document.getElementById(`reactions-${msgId}`);
    if (!container) return;
    if (!reactions || Object.keys(reactions).length === 0) { container.innerHTML = ''; return; }
    container.innerHTML = Object.entries(reactions).map(([emoji, data]) =>
        `<button class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs border ${data.users.some(u=>u.id===currentUser.id) ? 'bg-indigo-50 border-indigo-200 text-indigo-600' : 'bg-slate-50 border-slate-200 text-slate-500'} hover:bg-indigo-50 transition" onclick="toggleReaction(${msgId},'${emoji}')" title="${data.users.map(u=>u.name).join(', ')}">${emoji} ${data.count}</button>`
    ).join('');
}

// ========== @提及 ==========
async function insertMention() {
    if (!currentRoomId) return;
    if (!currentRoomMembers.length) {
        const res = await fetch(`/api/chat/rooms/${currentRoomId}/members`);
        const data = await res.json();
        if (data.success) currentRoomMembers = data.members;
    }
    showMentionPanel(currentRoomMembers);
}
function showMentionPanel(members) {
    const panel = document.getElementById('mentionPanel');
    const list = document.getElementById('mentionList');
    list.innerHTML = `<button class="w-full px-3 py-2 text-sm text-left hover:bg-indigo-50 text-indigo-600 font-medium" onclick="doMention('all','所有人')">@所有人</button>` +
        members.filter(m => m.id !== currentUser.id).map(m =>
            `<button class="w-full px-3 py-2 text-sm text-left hover:bg-slate-50 flex items-center gap-2" onclick="doMention(${m.id},'${escHtml(m.name)}')">
                <div class="w-5 h-5 rounded-full bg-slate-200 flex items-center justify-center overflow-hidden flex-shrink-0">${m.avatar ? `<img src="${m.avatar}" class="w-full h-full object-cover">` : `<span class="text-[10px]">${m.name[0]}</span>`}</div>
                ${escHtml(m.name)}</button>`
        ).join('');
    panel.classList.remove('hidden');
    setTimeout(() => document.addEventListener('click', closeMentionPanel, { once: true }), 10);
}
function closeMentionPanel() { document.getElementById('mentionPanel').classList.add('hidden'); }
function doMention(id, name) {
    const input = document.getElementById('msgInput');
    input.value += `@${name} `;
    input.focus();
    if (!mentionMembers.includes(id)) mentionMembers.push(id);
    closeMentionPanel();
}

// ========== 语音消息 ==========
function playVoice(el, url) {
    const existing = document.querySelector('audio.chat-voice-player');
    if (existing) { existing.pause(); existing.remove(); }
    const audio = new Audio(url);
    audio.className = 'chat-voice-player';
    audio.style.display = 'none';
    document.body.appendChild(audio);
    el.classList.add('opacity-60');
    audio.play();
    audio.onended = () => { el.classList.remove('opacity-60'); audio.remove(); };
}

async function startRecording() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
        mediaRecorder = new MediaRecorder(stream);
        audioChunks = [];
        mediaRecorder.ondataavailable = e => { if (e.data.size > 0) audioChunks.push(e.data); };
        mediaRecorder.start();
        recordingStartTime = Date.now();
        document.getElementById('recordingIndicator').classList.remove('hidden');
        document.getElementById('voiceBtn').classList.add('bg-rose-100', 'text-rose-500');
        recordingInterval = setInterval(() => {
            const sec = Math.floor((Date.now() - recordingStartTime) / 1000);
            document.getElementById('recordingTime').textContent = sec + 's';
        }, 200);
    } catch(e) { alert('无法访问麦克风，请检查权限设置'); }
}

async function stopRecording() {
    if (!mediaRecorder || mediaRecorder.state !== 'recording') return;
    clearInterval(recordingInterval);
    document.getElementById('recordingIndicator').classList.add('hidden');
    document.getElementById('voiceBtn').classList.remove('bg-rose-100', 'text-rose-500');
    const duration = Math.floor((Date.now() - recordingStartTime) / 1000);
    if (duration < 1) { mediaRecorder.stop(); mediaRecorder.stream.getTracks().forEach(t => t.stop()); return; }
    mediaRecorder.onstop = async () => {
        mediaRecorder.stream.getTracks().forEach(t => t.stop());
        const blob = new Blob(audioChunks, { type: 'audio/webm' });
        const fd = new FormData();
        fd.append('file', blob, 'voice.webm');
        fd.append('duration', duration);
        await fetch(`/api/chat/rooms/${currentRoomId}/voice`, { method: 'POST', body: fd });
    };
    mediaRecorder.stop();
}

// ========== SocketIO 事件监听 ==========
socket.on('new_message', (msg) => {
    if (msg.room_id === currentRoomId) {
        const area = document.getElementById('messageArea');
        area.appendChild(createMsgEl(msg));
        area.scrollTop = area.scrollHeight;
        markRead(currentRoomId);
    }
    // 更新会话列表
    const room = rooms.find(r => r.id === msg.room_id);
    if (room) {
        room.last_message = {
            content: msg.content, sender_name: msg.sender_name,
            created_at: msg.created_at, type: msg.type
        };
        if (msg.room_id !== currentRoomId) room.unread = (room.unread || 0) + 1;
        rooms.sort((a, b) => {
            const ta = a.last_message?.created_at || '';
            const tb = b.last_message?.created_at || '';
            return tb.localeCompare(ta);
        });
        renderRooms();
    } else {
        loadRooms();
    }
});

socket.on('message_recalled', (data) => {
    const el = document.getElementById(`msg-${data.message_id}`);
    if (el) {
        el.className = 'text-center';
        el.innerHTML = `<span class="text-xs text-slate-400">消息已撤回</span>`;
    }
});

socket.on('user_typing', (data) => {
    if (data.room_id === currentRoomId) {
        const ind = document.getElementById('typingIndicator');
        ind.textContent = `${data.user_name} 正在输入...`;
        ind.classList.remove('hidden');
        clearTimeout(ind._timer);
        ind._timer = setTimeout(() => ind.classList.add('hidden'), 3000);
    }
});

socket.on('read_update', (data) => {
    if (data.room_id === currentRoomId && data.user_id !== currentUser.id) {
        // 私聊已读回执
        const room = rooms.find(r => r.id === currentRoomId);
        if (room?.type === 'private') {
            document.querySelectorAll('[id^="read-"]').forEach(el => {
                el.textContent = '已读';
            });
        }
    }
});

socket.on('room_updated', () => { loadRooms(); });

socket.on('reaction_updated', (data) => {
    if (data.room_id === currentRoomId) {
        updateReactionsUI(data.message_id, data.reactions);
    }
});

socket.on('announcement_updated', (data) => {
    if (data.room_id === currentRoomId) {
        // 如果公告弹窗打开则刷新
        if (!document.getElementById('announcementModal').classList.contains('hidden')) {
            showAnnouncement();
        }
    }
});

socket.on('error', (data) => {
    if (data.message) alert(data.message);
});

// ========== 工具函数 ==========
function escHtml(s) {
    if (!s) return '';
    const d = document.createElement('div');
    d.textContent = s;
    return d.innerHTML;
}

function formatTime(ts) {
    if (!ts) return '';
    const d = new Date(ts.replace(' ', 'T'));
    const now = new Date();
    const diff = now - d;
    if (diff < 60000) return '刚刚';
    if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前';
    if (d.toDateString() === now.toDateString()) return ts.substring(11, 16);
    return ts.substring(5, 16);
}
</script>
{% endblock %}

