{{--
    文件功能：聊天室核心前端交互脚本（Blade 模板形式）

    包含：
    1. 消息渲染与路由（appendMessage）
    2. 在线用户列表管理（renderUserList / filterUserList）
    3. WebSocket 事件监听（chat:here / chat:message / chat:muted 等）
    4. 管理操作（adminClearScreen / promptAnnouncement 等）
    5. 存点心跳（saveExp，60秒自动）
    6. 钓鱼小游戏（startFishing / reelFish / autoFish）
    7. 发送消息（sendMessage，IME 防重触发）
    8. 特效控制（triggerEffect / applyFontSize / toggleSoundMute）

    已拆分至独立文件：
    - window.chatBanner     → chat-banner.blade.php
    - 头像选择器 JS         → layout/toolbar.blade.php
    - 好友通知/chatBanner监听 → user-actions.blade.php
    - 红包 HTML+CSS+JS      → games/red-packet-panel.blade.php

    通过 @include('chat.partials.scripts') 引入到 frame.blade.php

    @author  ChatRoom Laravel
    @version 2.0.0
--}}

<script>
    /**
     * 聊天室前端交互逻辑
     * 保留所有 WebSocket 事件监听，复刻原版 UI 交互
     */

    // ── DOM 元素引用 ──────────────────────────────────────
    const container = document.getElementById('chat-messages-container');
    const container2 = document.getElementById('chat-messages-container2');
    const userList = document.getElementById('online-users-list');
    const toUserSelect = document.getElementById('to_user');
    const onlineCount = document.getElementById('online-count');
    const onlineCountBottom = document.getElementById('online-count-bottom');

    let onlineUsers = {};
    let autoScroll = true;
    let _maxMsgId = 0; // 记录当前收到的最大消息 ID

    // ── Tab 切换 ──────────────────────────────────────
    let _roomsRefreshTimer = null;

    function switchTab(tab) {
        // 切换名单/房间 面板
        ['users', 'rooms'].forEach(t => {
            document.getElementById('panel-' + t).style.display = t === tab ? 'block' : 'none';
            document.getElementById('tab-' + t)?.classList.toggle('active', t === tab);
        });
        // 房间 Tab：立即拉取 + 每 30 秒自动刷新在线人数
        if (tab === 'rooms') {
            loadRoomsOnlineStatus();
            clearInterval(_roomsRefreshTimer);
            _roomsRefreshTimer = setInterval(loadRoomsOnlineStatus, 30000);
        } else {
            clearInterval(_roomsRefreshTimer);
            _roomsRefreshTimer = null;
        }
    }

    /**
     * 拉取所有房间在线人数并渲染到右侧面板
     */
    const _currentRoomId = {{ $room->id }};

    function loadRoomsOnlineStatus() {
        const container = document.getElementById('rooms-online-list');
        if (!container) {
            return;
        }

        fetch('{{ route('chat.rooms-online-status') }}')
            .then(r => r.json())
            .then(data => {
                if (!data.rooms || !data.rooms.length) {
                    container.innerHTML =
                        '<div style="text-align:center;color:#bbb;padding:16px 0;font-size:11px;">暂无房间</div>';
                    return;
                }
                container.innerHTML = data.rooms.map(room => {
                    const isCurrent = room.id === _currentRoomId;
                    const closed = !room.door_open;
                    const bg = isCurrent ? '#ecf4ff' : '#fff';
                    const border = isCurrent ? '#aac5f0' : '#e0eaf5';
                    const nameColor = isCurrent ? '#336699' : (closed ? '#bbb' : '#444');
                    const badge = room.online > 0 ?
                        `<span style="background:#e8f5e9;color:#2e7d32;border-radius:8px;padding:0 5px;font-size:10px;font-weight:bold;white-space:nowrap;flex-shrink:0;">${room.online} 人</span>` :
                        `<span style="background:#f5f5f5;color:#bbb;border-radius:8px;padding:0 5px;font-size:10px;white-space:nowrap;flex-shrink:0;">空</span>`;
                    const currentTag = isCurrent ?
                        `<span style="font-size:9px;color:#336699;opacity:.7;margin-left:3px;">当前</span>` :
                        '';
                    const clickHandler = isCurrent ? '' : `onclick="location.href='/room/${room.id}'"`;

                    return `<div ${clickHandler}
                        style="display:flex;align-items:center;justify-content:space-between;
                               padding:5px 8px;margin:2px 3px;border-radius:5px;
                               border:1px solid ${border};background:${bg};
                               cursor:${isCurrent ? 'default' : 'pointer'};
                               transition:background .15s;"
                        onmouseover="if(${!isCurrent}) this.style.background='#ddeeff';"
                        onmouseout="this.style.background='${bg}';">
                        <span style="color:${nameColor};font-size:11px;font-weight:${isCurrent?'bold':'normal'};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;margin-right:4px;">
                            ${room.name}${currentTag}
                        </span>
                        ${badge}
                    </div>`;
                }).join('');
            })
            .catch(() => {
                container.innerHTML =
                    '<div style="text-align:center;color:#f00;padding:10px;font-size:11px;">加载失败</div>';
            });
    }




    // ── 欢迎语快捷菜单 ──────────────────────────────────────
    /**
     * 切换欢迎语下拉浮层的显示/隐藏
     */
    function toggleWelcomeMenu(event) {
        event.stopPropagation();
        const menu = document.getElementById('welcome-menu');
        if (!menu) {
            return;
        }
        menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
    }

    /**
     * 将选中的欢迎语模板填入输入框，{name} 替换为当前选中的聊天对象，
     * 并在前面加上「部门 职务 姓名：」前缀，然后自动发送
     *
     * @param {string} tpl 欢迎语模板，含 {name} 占位符
     */
    function sendWelcomeTpl(tpl) {
        const toUser = document.getElementById('to_user')?.value || '大家';
        const name = toUser === '大家' ? '大家' : toUser;
        const prefix = window.chatContext?.welcomePrefix || window.chatContext?.username || '';
        const body = tpl.replace(/\{name\}/g, name);
        // 拼接格式：「部门 职务 姓名：欢迎语」
        const msg = `${prefix}：${body}`;

        const input = document.getElementById('content');
        if (input) {
            input.value = msg;
        }

        const menu = document.getElementById('welcome-menu');
        if (menu) {
            menu.style.display = 'none';
        }

        // 临时把 action 设为「欢迎」，让消息在聊天窗口以瓦蓝边框样式显示
        const actionSel = document.getElementById('action');
        const prevAction = actionSel?.value || '';
        if (actionSel) {
            actionSel.value = '欢迎';
        }

        // 自动触发发送
        sendMessage(null).finally(() => {
            // 发完后恢复原来的 action
            if (actionSel) {
                actionSel.value = prevAction;
            }
        });
    }

    // 点击页面任意位置，关闭欢迎语浮层
    document.addEventListener('click', function() {
        const menu = document.getElementById('welcome-menu');
        if (menu) {
            menu.style.display = 'none';
        }
    });

    // ── 动作选择 ──────────────────────────────────────
    /**
     * 设置发言动作并聚焦输入框
     *
     * @param {string} act 动作名称
     */
    function setAction(act) {
        document.getElementById('action').value = act;
        switchTab('users');
        document.getElementById('content').focus();
    }

    // ── 自动滚屏 ──────────────────────────────────────
    const autoScrollEl = document.getElementById('auto_scroll');
    if (autoScrollEl) {
        autoScrollEl.addEventListener('change', function() {
            autoScroll = this.checked;
        });
    }

    // ── 滚动到底部 ───────────────────────────────────
    /**
     * 将公聊窗口滚动到最新消息（受 autoScroll 开关控制）
     */
    function scrollToBottom() {
        if (autoScroll) {
            container.scrollTop = container.scrollHeight;
        }
    }

    // ── 渲染在线人员列表（支持排序） ──────────────────
    /**
     * 核心渲染函数：将在线用户渲染到指定容器（桌面端名单区和手机端抽屉共用）
     *
     * @param {HTMLElement} targetContainer 目标 DOM 容器
     * @param {string}      sortBy          排序方式：'default' | 'name' | 'level'
     * @param {string}      keyword         搜索关键词（小写）
     */
    function _renderUserListToContainer(targetContainer, sortBy, keyword) {
        if (!targetContainer) return;
        targetContainer.innerHTML = '';

        // 在列表顶部添加"大家"条目（原版风格）
        let allDiv = document.createElement('div');
        allDiv.className = 'user-item';
        allDiv.innerHTML = '<span class="user-name" style="padding-left: 4px; color: navy;">大家</span>';
        allDiv.onclick = () => {
            toUserSelect.value = '大家';
        };
        targetContainer.appendChild(allDiv);

        // ── AI 小助手（仅当全局开关开启时显示，与普通用户风格一致）──
        if (window.chatContext.chatBotEnabled) {
            let botDiv = document.createElement('div');
            botDiv.className = 'user-item';
            botDiv.innerHTML = `
                <img class="user-head" src="/images/ai_bot.png" onerror="this.src='/images/headface/1.gif'">
                <span class="user-name">AI小班长</span><span style="font-size:12px; margin-left:2px;" title="聊天机器人">🤖</span>
            `;
            botDiv.onclick = () => {
                toUserSelect.value = 'AI小班长';
                document.getElementById('content').focus();
            };
            targetContainer.appendChild(botDiv);
        }

        // 构建用户数组并排序
        let userArr = [];
        for (let username in onlineUsers) {
            userArr.push({
                username,
                ...onlineUsers[username]
            });
        }

        if (sortBy === 'name') {
            userArr.sort((a, b) => a.username.localeCompare(b.username, 'zh'));
        } else if (sortBy === 'level') {
            userArr.sort((a, b) => (b.user_level || 0) - (a.user_level || 0));
        }

        userArr.forEach(user => {
            const username = user.username;

            // 搜索过滤
            if (keyword && !username.toLowerCase().includes(keyword)) return;

            let item = document.createElement('div');
            item.className = 'user-item';
            item.dataset.username = username;

            const headface = (user.headface || '1.gif').toLowerCase();
            const headImgSrc = headface.startsWith('storage/') ? '/' + headface : '/images/headface/' +
                headface;

            // 徽章优先级：职务图标 > 管理员 > VIP
            let badges = '';
            if (user.position_icon) {
                const posTitle = (user.position_name || '在职') + ' · ' + username;
                badges +=
                    `<span style="font-size:13px; margin-left:2px;" title="${posTitle}">${user.position_icon}</span>`;
            } else if (user.is_admin) {
                badges += `<span style="font-size:12px; margin-left:2px;" title="最高统帅">🎖️</span>`;
            } else if (user.vip_icon) {
                const vipColor = user.vip_color || '#f59e0b';
                badges +=
                    `<span style="font-size:12px; margin-left:2px; color:${vipColor};" title="${user.vip_name || 'VIP'}">${user.vip_icon}</span>`;
            }

            // 女生名字使用玫粉色
            const nameColor = (user.sex == 2) ? 'color:#e91e8c;' : '';
            item.innerHTML = `
                <img class="user-head" src="${headImgSrc}" onerror="this.src='/images/headface/1.gif'">
                <span class="user-name" style="${nameColor}">${username}</span>${badges}
            `;

            // 单击/双击互斥：单击延迟 250ms 执行，双击取消单击定时器后直接执行双击逻辑
            let _clickTimer = null;
            item.onclick = () => {
                if (_clickTimer) {
                    return;
                }
                _clickTimer = setTimeout(() => {
                    _clickTimer = null;
                    toUserSelect.value = username;
                    // 手机端：点击名字时关闭名单抽屉
                    if (typeof closeMobileDrawer === 'function') {
                        closeMobileDrawer();
                    }
                    document.getElementById('content').focus();
                }, 250);
            };
            // 双击打开用户名片弹窗（全局统一入口）；手机端同时关闭名单抽屉
            item.ondblclick = () => {
                // 取消尚未执行的单击定时器
                if (_clickTimer) {
                    clearTimeout(_clickTimer);
                    _clickTimer = null;
                }
                if (typeof closeMobileDrawer === 'function') {
                    closeMobileDrawer();
                }
                openUserCard(username);
            };
            targetContainer.appendChild(item);
        });
    }

    function renderUserList() {
        userList.innerHTML = '';
        toUserSelect.innerHTML = '<option value="大家">大家</option>';

        // 获取排序方式和搜索词
        const sortSelect = document.getElementById('user-sort-select');
        const sortBy = sortSelect ? sortSelect.value : 'default';
        const searchInput = document.getElementById('user-search-input');
        const keyword = searchInput ? searchInput.value.trim().toLowerCase() : '';

        // 调用核心渲染（桌面端名单容器）
        _renderUserListToContainer(userList, sortBy, keyword);

        // 重新填充发言对象下拉框（不过滤关键词，始终显示全部用户）
        toUserSelect.innerHTML = '<option value="大家">大家</option>';
        if (window.chatContext.chatBotEnabled) {
            let botOption = document.createElement('option');
            botOption.value = 'AI小班长';
            botOption.textContent = '🤖 AI小班长';
            toUserSelect.appendChild(botOption);
        }
        for (let username in onlineUsers) {
            if (username !== window.chatContext.username) {
                let option = document.createElement('option');
                option.value = username;
                option.textContent = username;
                toUserSelect.appendChild(option);
            }
        }

        const count = Object.keys(onlineUsers).length;
        onlineCount.innerText = count;
        onlineCountBottom.innerText = count;
        const footer = document.getElementById('online-count-footer');
        if (footer) {
            footer.innerText = count;
        }

        // 派发用户列表更新事件，供手机端抽屉同步
        window.dispatchEvent(new Event('chatroom:users-updated'));
    }

    /**
     * 搜索/过滤用户列表
     */
    function filterUserList() {
        const searchInput = document.getElementById('user-search-input');
        const keyword = searchInput ? searchInput.value.trim().toLowerCase() : '';
        const items = userList.querySelectorAll('.user-item');

        items.forEach(item => {
            if (!keyword) {
                item.style.display = '';
                return;
            }
            const name = (item.dataset.username || item.textContent || '').toLowerCase();
            item.style.display = name.includes(keyword) ? '' : 'none';
        });
    }






    /**
     * 追加消息到聊天窗格（原版风格：非气泡模式，逐行显示）
     */
    function appendMessage(msg) {
        // 记录拉取到的最大消息ID，用于本地清屏功能
        if (msg && msg.id > _maxMsgId) {
            _maxMsgId = msg.id;
        }

        const isMe = msg.from_user === window.chatContext.username;
        const fontColor = msg.font_color || '#000000';

        const div = document.createElement('div');
        div.className = 'msg-line';

        const timeStr = msg.sent_at || '';
        let timeStrOverride = false;

        // 系统用户名列表（不可被选为聊天对象）
        const systemUsers = ['钓鱼播报', '星海小博士', '系统传音', '系统公告', 'AI小班长', '送花播报', '系统', '欢迎'];

        // 动作文字映射表：情绪型（着/地，放"对"之前）和动作型（了，替换"对X说"）
        const actionTextMap = {
            '微笑': {
                type: 'emotion',
                word: '微笑着'
            },
            '大笑': {
                type: 'emotion',
                word: '大笑着'
            },
            '愤怒': {
                type: 'emotion',
                word: '愤怒地'
            },
            '哭泣': {
                type: 'emotion',
                word: '哭泣着'
            },
            '害羞': {
                type: 'emotion',
                word: '害羞地'
            },
            '鄙视': {
                type: 'emotion',
                word: '鄙视地'
            },
            '得意': {
                type: 'emotion',
                word: '得意地'
            },
            '疑惑': {
                type: 'emotion',
                word: '疑惑地'
            },
            '同情': {
                type: 'emotion',
                word: '同情地'
            },
            '无奈': {
                type: 'emotion',
                word: '无奈地'
            },
            '拳打': {
                type: 'verb',
                word: '拳打了'
            },
            '飞吻': {
                type: 'verb',
                word: '飞吻了'
            },
            '偷看': {
                type: 'verb',
                word: '偷看了'
            },
        };
        // 生成自然语序的动作串：情绪型=[人][着/地]对[目标][verb]：；动作型=[人][了][目标]，[verb]：
        const buildActionStr = (action, fromHtml, toHtml, verb = '说') => {
            const info = actionTextMap[action];
            if (!info) return `${fromHtml}对${toHtml}${action}${verb}：`;
            if (info.type === 'emotion') return `${fromHtml}${info.word}对${toHtml}${verb}：`;
            return `${fromHtml}${info.word}${toHtml}，${verb}：`;
        };
        // 判断 【】 内的内容是否是游戏/活动标签而非真实用户名
        // 规则：命中已知游戏前缀，或内容含空格（如「双色球 第012期 开奖」）
        const isGameLabel = (name) => {
            const gamePrefixes = ['五子棋', '双色球', '钓鱼', '老虎机', '百家乐', '赛马'];
            if (gamePrefixes.some(p => name.startsWith(p))) return true;
            // 含空格 → 一定不是用户名（用户名不允许含空格）
            if (name.includes(' ')) return true;
            return false;
        };

        // 用户名（单击切换发言对象，双击查看资料；系统用户或游戏标签仅显示文本）
        const clickableUser = (uName, color) => {
            if (systemUsers.includes(uName) || isGameLabel(uName)) {
                return `<span class="msg-user" style="color: ${color};">${uName}</span>`;
            }
            return `<span class="msg-user" style="color: ${color}; cursor: pointer;" onclick="switchTarget('${uName}')" ondblclick="openUserCard('${uName}')">${uName}</span>`;
        };

        // 系统播报用户（不包含 AI小班长）使用军号图标，AI小班长用专属图，普通用户用头像
        const buggleUsers = ['钓鱼播报', '星海小博士', '送花播报', '系统传音', '系统公告'];
        const senderInfo = onlineUsers[msg.from_user];
        const senderHead = ((senderInfo && senderInfo.headface) || '1.gif').toLowerCase();
        let headImgSrc = senderHead.startsWith('storage/') ? '/' + senderHead : `/images/headface/${senderHead}`;
        if (msg.from_user === 'AI小班长') {
            headImgSrc = '/images/ai_bot.png';
        } else if (buggleUsers.includes(msg.from_user)) {
            headImgSrc = '/images/bugle.png';
        }
        const headImg =
            `<img src="${headImgSrc}" style="display:inline;width:16px;height:16px;vertical-align:middle;margin-right:2px;mix-blend-mode: multiply;" onerror="this.src='/images/headface/1.gif'">`;

        let html = '';

        // 第一个判断分支：如果是纯 HTML 系统的进出播报
        if (msg.action === 'system_welcome') {
            div.style.cssText = 'margin: 3px 0;';
            const iconImg =
                `<img src="/images/bugle.png" style="display:inline;width:16px;height:16px;vertical-align:middle;margin-right:2px;mix-blend-mode: multiply;" onerror="this.src='/images/headface/1.gif'">`;

            let parsedContent = msg.content;
            parsedContent = parsedContent.replace(/【([^】]+)】/g, function(match, uName) {
                return '【' + clickableUser(uName, '#000099') + '】';
            });

            html = `${iconImg} ${parsedContent}`;
        }
        // 贾妖语 —— 蓝色左边框渐变样式，比 系统公告 低调
        else if (msg.action === '欢迎') {
            div.style.cssText =
                'background: linear-gradient(135deg, #eff6ff, #f0f9ff); border: 1.5px solid #3b82f6; border-radius: 5px; padding: 5px 10px; margin: 3px 0; box-shadow: 0 1px 3px rgba(59,130,246,0.12);';

            let parsedContent = msg.content;
            parsedContent = parsedContent.replace(/【([^】]+)】/g, function(match, uName) {
                return '【' + clickableUser(uName, '#1d4ed8') + '】';
            });

            html =
                `<div style="color: #1e40af;">&#x1F4AC; ${parsedContent} <span style="color: #93c5fd; font-size: 11px; font-weight: normal;">(${timeStr})</span></div>`;
            timeStrOverride = true;
        }
        // 接下来再判断各类发话人
        else if (systemUsers.includes(msg.from_user)) {
            if (msg.from_user === '系统公告') {
                // 管理员公告：大字醒目红框样式
                div.style.cssText =
                    'background: linear-gradient(135deg, #fef2f2, #fff1f2); border: 2px solid #ef4444; border-radius: 6px; padding: 8px 12px; margin: 4px 0; box-shadow: 0 2px 4px rgba(239,68,68,0.15);';

                let parsedContent = msg.content;
                parsedContent = parsedContent.replace(/【([^】]+)】/g, function(match, uName) {
                    return '【' + clickableUser(uName, '#dc2626') + '】';
                });

                html =
                    `<div style="font-weight: bold; color: #dc2626;">${parsedContent} <span style="color: #999; font-weight: normal;">(${timeStr})</span></div>`;
                timeStrOverride = true;
            } else if (msg.from_user === '系统传音') {
                // 自动升级播报 / 赠礼通知 / 彩票购买广播：金色左边框，轻量提示样式，不喧宾夺主
                // 解析内容中 【用户名】 片段，使其支持单击（切换发言对象）和双击（查看名片）
                div.style.cssText =
                    'background: #fffbeb; border-left: 3px solid #d97706; border-radius: 4px; padding: 4px 10px; margin: 2px 0;';
                let sysTranContent = msg.content;
                sysTranContent = sysTranContent.replace(/【([^】]+)】/g, function(match, uName) {
                    return '【' + clickableUser(uName, '#000099') + '】';
                });
                html =
                    `<span style="color: #b45309;">🌟 ${sysTranContent}</span>`;
            } else if (msg.from_user === '系统' && msg.to_user && msg.to_user !== '大家') {
                // 系统私人通知（自动存点等）：无头像，绿色左边框简洁条形样式
                div.style.cssText =
                    'background:#f0fdf4;border-left:3px solid #16a34a;border-radius:4px;padding:3px 8px;margin:2px 0;';
                html =
                    `<span style="color:#16a34a;font-weight:bold;">📢 系统：</span><span style="color:#15803d;">${msg.content}</span>`;
            } else {
                // 其他系统用户（钓鱼播报、送花播报、AI小班长等）：普通样式
                let giftHtml = '';
                if (msg.gift_image) {
                    giftHtml =
                        `<img src="${msg.gift_image}" alt="${msg.gift_name || ''}" style="display:inline-block;width:40px;height:40px;vertical-align:middle;margin-left:6px;animation:giftBounce 0.6s ease-in-out;">`;
                }

                // 让带有【用户名】的系统通知变成可点击和双击的蓝色用户标
                let parsedContent = msg.content;
                // 利用正则匹配【用户名】结构，捕获组 $1 即是里面真正的用户名
                parsedContent = parsedContent.replace(/【([^】]+)】/g, function(match, uName) {
                    return '【' + clickableUser(uName, '#000099') + '】';
                });

                html =
                    `${headImg}<span class="msg-user" style="color: ${fontColor}; font-weight: bold;">${msg.from_user}：</span><span class="msg-content" style="color: ${fontColor}">${parsedContent}</span>${giftHtml}`;
            }
        } else if (msg.is_secret) {
            if (msg.from_user === '系统') {
                // 系统悄悄通知（自动存点等）：绿色左边框条形，无头像，不显示"悄悄说"
                div.style.cssText =
                    'background:#f0fdf4;border-left:3px solid #16a34a;border-radius:4px;padding:3px 8px;margin:2px 0;font-size:12px;';
                html =
                    `<span style="color:#16a34a;font-weight:bold;">📢 系统：</span><span style="color:#15803d;">${msg.content}</span>`;
            } else {
                // 普通悄悄话样式（原版：紫色斜体，使用自然语序动作）
                const fromHtml = clickableUser(msg.from_user, '#cc00cc');
                const toHtml = clickableUser(msg.to_user, '#cc00cc');
                const verbStr = msg.action ?
                    buildActionStr(msg.action, fromHtml, toHtml, '悄悄说') :
                    `${fromHtml}对${toHtml}悄悄说：`;
                html = `<span class="msg-secret">${headImg}${verbStr}${msg.content}</span>`;
            }
        } else if (msg.to_user && msg.to_user !== '大家') {
            // 对特定对象说话
            const fromHtml = clickableUser(msg.from_user, '#000099');
            const toHtml = clickableUser(msg.to_user, '#000099');
            const verbStr = msg.action ?
                buildActionStr(msg.action, fromHtml, toHtml) :
                `${fromHtml}对${toHtml}说：`;
            html = `${headImg}${verbStr}<span class="msg-content" style="color: ${fontColor}">${msg.content}</span>`;
        } else {
            // 对大家说话
            const fromHtml = clickableUser(msg.from_user, '#000099');
            const verbStr = msg.action ?
                buildActionStr(msg.action, fromHtml, '大家') :
                `${fromHtml}对大家说：`;
            html = `${headImg}${verbStr}<span class="msg-content" style="color: ${fontColor}">${msg.content}</span>`;
        }

        if (!timeStrOverride) {
            html += ` <span class="msg-time">(${timeStr})</span>`;
        }
        div.innerHTML = html;

        // 后端下发的带有 welcome_user 的也是系统欢迎/离开消息，加上属性标记
        if (msg.welcome_user) {
            div.setAttribute('data-system-user', msg.welcome_user);
            // 收到后端来的新欢迎消息时，把界面上该用户旧的都删掉
            const oldWelcomes = container.querySelectorAll(`[data-system-user="${msg.welcome_user}"]`);
            oldWelcomes.forEach(el => el.remove());
        }

        // 路由规则（复刻原版）：
        // 公众窗口(say1)：别人的公聊消息
        // 包厢窗口(say2)：自己发的消息 + 悄悄话 + 对自己说的消息
        const isRelatedToMe = isMe ||
            msg.is_secret ||
            msg.to_user === window.chatContext.username;

        // 自动存点通知：标记 data-autosave 属性，每次渲染时先删除旧的，实现"滚动替换"效果
        const isAutoSave = (msg.from_user === '系统' || msg.from_user === '') &&
            msg.content && msg.content.includes('自动存点');
        if (isAutoSave) {
            div.dataset.autosave = '1';
        }

        if (isRelatedToMe) {
            // 删除旧的自动存点通知，保持包厢窗口整洁
            if (isAutoSave) {
                container2.querySelectorAll('[data-autosave="1"]').forEach(el => el.remove());
            }
            container2.appendChild(div);
            if (autoScroll) container2.scrollTop = container2.scrollHeight;
        } else {
            container.appendChild(div);
            scrollToBottom();
        }
    }

    // ── WebSocket 初始化 ─────────────────────────────
    document.addEventListener('DOMContentLoaded', () => {
        if (typeof window.initChat === 'function') {
            window.initChat(window.chatContext.roomId);
        }
    });

    // ── WebSocket 事件监听 ────────────────────────────
    window.addEventListener('chat:here', (e) => {
        const users = e.detail;
        onlineUsers = {};
        users.forEach(u => {
            onlineUsers[u.username] = u;
        });
        renderUserList();

        // 管理员自己进房时，在本地播放烟花（服务端广播可能在 WS 连上前已发出）
        const ctx = window.chatContext;
        if (ctx && ctx.userLevel >= ctx.superLevel && typeof EffectManager !== 'undefined') {
            // 延迟 800ms 确保页面渲染完成再播特效
            setTimeout(() => EffectManager.play('fireworks'), 800);
        }
    });

    window.addEventListener('chat:joining', (e) => {
        const user = e.detail;
        onlineUsers[user.username] = user;
        renderUserList();
    });

    window.addEventListener('chat:leaving', (e) => {
        const user = e.detail;
        delete onlineUsers[user.username];
        renderUserList();
    });

    window.addEventListener('chat:message', (e) => {
        const msg = e.detail;
        if (msg.is_secret && msg.from_user !== window.chatContext.username && msg.to_user !== window
            .chatContext.username) {
            return;
        }
        appendMessage(msg);
        // 若消息携带 toast_notification 字段且当前用户是接收者，弹右下角小卡片
        if (msg.toast_notification && msg.to_user === window.chatContext.username) {
            const t = msg.toast_notification;
            window.chatToast.show({
                title: t.title || '通知',
                message: t.message || '',
                icon: t.icon || '💬',
                color: t.color || '#336699',
                duration: t.duration ?? 8000,
            });
        }
    });

    window.addEventListener('chat:kicked', (e) => {
        if (e.detail.username === window.chatContext.username) {
            window.chatDialog.alert('您已被管理员踢出房间！' + (e.detail.reason ? ' 原因：' + e.detail.reason : ''), '系统通知',
                '#cc4444');
            window.location.href = "{{ route('rooms.index') }}";
        }
    });

    // ── 禁言状态（本地计时器） ──
    let isMutedUntil = 0;

    window.addEventListener('chat:muted', (e) => {
        const d = e.detail;
        const now = new Date();
        const timeStr = now.getHours().toString().padStart(2, '0') + ':' +
            now.getMinutes().toString().padStart(2, '0') + ':' +
            now.getSeconds().toString().padStart(2, '0');

        const isMe = d.username === window.chatContext.username;

        // 禁言通知：自己被禁言显示在包厢(say2)，其他人显示在公聊(say1)
        const div = document.createElement('div');
        div.className = 'msg-line';
        div.innerHTML =
            `<span style="color: #c00; font-weight: bold;">【系统】${d.message}</span><span class="msg-time">(${timeStr})</span>`;
        const targetContainer = isMe ? document.getElementById('say2') : container;
        if (targetContainer) {
            targetContainer.appendChild(div);
            targetContainer.scrollTop = targetContainer.scrollHeight;
        }

        // 如果是自己被禁言，设置本地禁言计时
        if (isMe && d.mute_time > 0) {
            isMutedUntil = Date.now() + d.mute_time * 60 * 1000;
            const contentInput = document.getElementById('content');
            const operatorName = d.operator || '管理员';
            if (contentInput) {
                contentInput.placeholder = `${operatorName} 已将您禁言 ${d.mute_time} 分钟，解禁后方可发言...`;
                contentInput.disabled = true;
                // 到期自动恢复
                setTimeout(() => {
                    isMutedUntil = 0;
                    contentInput.placeholder = '在这里输入聊天内容，按 Enter 发送...';
                    contentInput.disabled = false;
                    const unmuteDiv = document.createElement('div');
                    unmuteDiv.className = 'msg-line';
                    unmuteDiv.innerHTML =
                        '<span style="color: #16a34a; font-weight: bold;">【系统】您的禁言已解除，可以继续发言了。</span>';
                    // 解禁提示也显示在包厢窗口
                    const say2 = document.getElementById('say2');
                    if (say2) {
                        say2.appendChild(unmuteDiv);
                        say2.scrollTop = say2.scrollHeight;
                    }
                }, d.mute_time * 60 * 1000);
            }
        }
    });

    window.addEventListener('chat:title-updated', (e) => {
        document.getElementById('room-title-display').innerText = e.detail.title;
    });

    // ── 管理员全员清屏事件（等待 Echo 就绪后监听） ───────
    function setupScreenClearedListener() {
        if (!window.Echo || !window.chatContext) {
            // Echo 或 chatContext 还没就绪，延迟重试
            setTimeout(setupScreenClearedListener, 500);
            return;
        }
        window.Echo.join(`room.${window.chatContext.roomId}`)
            .listen('ScreenCleared', (e) => {
                console.log('收到全员清屏事件:', e);
                const operator = e.operator;

                // 清除公聊窗口所有消息
                const say1 = document.getElementById('chat-messages-container');
                if (say1) say1.innerHTML = '';

                // 清除包厢窗口中非悄悄话的消息
                const say2 = document.getElementById('chat-messages-container2');
                if (say2) {
                    const items = say2.querySelectorAll('.msg-line');
                    items.forEach(item => {
                        // 保留悄悄话消息（含 msg-secret 类）
                        if (!item.querySelector('.msg-secret')) {
                            item.remove();
                        }
                    });
                }

                // 显示清屏提示
                const sysDiv = document.createElement('div');
                sysDiv.className = 'msg-line';
                const now = new Date();
                const timeStr = now.getHours().toString().padStart(2, '0') + ':' +
                    now.getMinutes().toString().padStart(2, '0') + ':' +
                    now.getSeconds().toString().padStart(2, '0');
                sysDiv.innerHTML =
                    `<span style="color: #dc2626; font-weight: bold;">🧹 管理员 <b>${operator}</b> 已执行全员清屏</span><span class="msg-time">(${timeStr})</span>`;
                if (say1) {
                    say1.appendChild(sysDiv);
                    say1.scrollTop = say1.scrollHeight;
                }
            });
        console.log('ScreenCleared 监听器已注册');
    }
    // DOMContentLoaded 后启动，确保 Vite 编译的 JS 已加载
    document.addEventListener('DOMContentLoaded', setupScreenClearedListener);

    // ── 开发日志发布通知（仅 Room 1 大厅可见）────────────
    /**
     * 监听 ChangelogPublished 事件，在大厅聊天区展示系统通知
     * 通知包含版本号、标题和可点击的查看链接
     * 复用「系统传音」样式（金色左边框，不喧宾夺主）
     */
    function setupChangelogPublishedListener() {
        if (!window.Echo || !window.chatContext) {
            setTimeout(setupChangelogPublishedListener, 500);
            return;
        }
        // 仅在 Room 1（星光大厅）时监听
        if (window.chatContext.roomId !== 1) {
            return;
        }
        window.Echo.join('room.1')
            .listen('.ChangelogPublished', (e) => {
                const now = new Date();
                const timeStr = now.getHours().toString().padStart(2, '0') + ':' +
                    now.getMinutes().toString().padStart(2, '0') + ':' +
                    now.getSeconds().toString().padStart(2, '0');

                const sysDiv = document.createElement('div');
                sysDiv.className = 'msg-line';
                // 金色左边框通知样式（复用「系统传音」风格）
                sysDiv.style.cssText =
                    'background: #fffbeb; border-left: 3px solid #d97706; border-radius: 4px; padding: 5px 10px; margin: 3px 0;';
                sysDiv.innerHTML = `<span style="color: #b45309; font-weight: bold;">
                    📋 【版本更新】v${e.version} · ${e.title}
                    <a href="${e.url}" target="_blank" rel="noopener"
                        style="color: #7c3aed; text-decoration: underline; margin-left: 8px; font-size: 0.85em;">
                        查看详情 →
                    </a>
                </span><span class="msg-time">(${timeStr})</span>`;

                const say1 = document.getElementById('chat-messages-container');
                if (say1) {
                    say1.appendChild(sysDiv);
                    say1.scrollTop = say1.scrollHeight;
                }
            });
        console.log('ChangelogPublished 监听器已注册（Room 1 专属）');
    }
    document.addEventListener('DOMContentLoaded', setupChangelogPublishedListener);

    // ── 五子棋 PvP 邀请通知（聊天室内显示「接受挑战」按钮）───────
    /**
     * 监听 .gomoku.invite 事件，在聊天窗口追加邀请消息行。
     * 发起者收到的邀请（自己发出的）不显示接受按钮。
     */
    function setupGomokuInviteListener() {
        if (!window.Echo || !window.chatContext) {
            setTimeout(setupGomokuInviteListener, 500);
            return;
        }
        window.Echo.join(`room.${window.chatContext.roomId}`)
            .listen('.gomoku.invite', (e) => {
                const now = new Date();
                const timeStr = now.getHours().toString().padStart(2, '0') + ':' +
                    now.getMinutes().toString().padStart(2, '0') + ':' +
                    now.getSeconds().toString().padStart(2, '0');

                const isSelf = (e.inviter_name === window.chatContext.username);
                const div = document.createElement('div');
                div.className = 'msg-line';
                div.style.cssText =
                    'background:linear-gradient(135deg,#e8eef8,#f0f4fc); ' +
                    'border-left:3px solid #336699; border-radius:4px; padding:6px 10px; margin:3px 0;';

                const acceptBtn = isSelf ?
                    // 自己的邀请：只显示打开面板按钮，方便被关掉后重新进入
                    `<button onclick="document.querySelector('[x-data=\"gomokuPanel()\"]').__x.$data.open()" 
                           style="margin-left:10px; padding:3px 12px; border:1.5px solid #2d6096;
                                  border-radius:12px; background:#f0f6ff; color:#2d6096; font-size:12px;
                                  cursor:pointer; font-family:inherit; transition:all .15s;"
                           onmouseover="this.style.background='#ddeeff'" onmouseout="this.style.background='#f0f6ff'">
                           ⤴️ 打开面板
                       </button>` :
                    // 别人的邀请：显示接受挑战按钮
                    `<button onclick="acceptGomokuInvite(${e.game_id})" id="gomoku-accept-${e.game_id}"
                           style="margin-left:10px; padding:3px 12px; border:1.5px solid #336699;
                                  border-radius:12px; background:#336699; color:#fff; font-size:12px;
                                  cursor:pointer; font-family:inherit; transition:all .15s;"
                           onmouseover="this.style.opacity='.8'" onmouseout="this.style.opacity='1'">
                           ⚔️ 接受挑战
                       </button>`;

                div.innerHTML = `<span style="color:#1e3a5f; font-weight:bold;">
                        ♟️ 【五子棋】<b>${e.inviter_name}</b> 发起了随机对战！${isSelf ? '（等待中）' : ''}
                    </span>${acceptBtn}
                    <span class="msg-time">(${timeStr})</span>`;

                // 追加到公聊窗口
                const say1 = document.getElementById('chat-messages-container');
                if (say1) {
                    say1.appendChild(div);
                    say1.scrollTop = say1.scrollHeight;
                }

                // 60 秒后移除接受按钮（邀请超时）
                if (!isSelf) {
                    setTimeout(() => {
                        const btn = document.getElementById(`gomoku-accept-${e.game_id}`);
                        if (btn) {
                            btn.textContent = '已超时';
                            btn.disabled = true;
                            btn.style.opacity = '.5';
                            btn.style.cursor = 'not-allowed';
                        }
                    }, 60000);
                }
            })
            .listen('.gomoku.finished', (e) => {
                // 对局结束：在公聊展示战报（仅 PvP 有战报意义）
                if (e.mode !== 'pvp') return;
                const now = new Date();
                const timeStr = now.getHours().toString().padStart(2, '0') + ':' +
                    now.getMinutes().toString().padStart(2, '0') + ':' +
                    now.getSeconds().toString().padStart(2, '0');

                const div = document.createElement('div');
                div.className = 'msg-line';
                div.style.cssText =
                    'background:#fffae8; border-left:3px solid #d97706; border-radius:4px; padding:5px 10px; margin:2px 0;';

                const reason = {
                    win: '获胜',
                    draw: '平局',
                    resign: '认输',
                    timeout: '超时'
                } [e.reason] || '结束';
                let text = '';
                if (e.winner === 0) {
                    text = `♟️ 五子棋对局以<b>平局</b>结束！`;
                } else {
                    text =
                        `♟️ <b>${e.winner_name}</b> 击败 <b>${e.loser_name}</b>（${reason}）获得 <b style="color:#b45309;">${e.reward_gold}</b> 金币！`;
                }

                div.innerHTML =
                    `<span style="color:#92400e;">${text}</span><span class="msg-time">(${timeStr})</span>`;
                const say1 = document.getElementById('chat-messages-container');
                if (say1) {
                    say1.appendChild(div);
                    say1.scrollTop = say1.scrollHeight;
                }
            });
        console.log('[五子棋] 邀请监听器已注册');
    }
    document.addEventListener('DOMContentLoaded', setupGomokuInviteListener);

    // ── 全屏特效事件监听（烟花/下雨/雷电/下雪）─────────
    window.addEventListener('chat:effect', (e) => {
        const type = e.detail?.type;
        const target = e.detail?.target_username; // null = 全员，otherwise 指定昵称
        const myName = window.chatContext?.username;

        // null 表示全员，或者 target 匹配自己才播放
        if (type && typeof EffectManager !== 'undefined') {
            if (!target || target === myName) {
                EffectManager.play(type);
            }
        }
    });

    /**
     * 管理员点击特效按钮，向后端 POST /command/effect
     *
     * @param {string} type  特效类型：fireworks / rain / lightning
     */
    function triggerEffect(type) {
        const roomId = window.chatContext?.roomId;
        if (!roomId) return;
        fetch('/command/effect', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content ?? '',
            },
            body: JSON.stringify({
                room_id: roomId,
                type
            }),
        }).then(r => r.json()).then(data => {
            if (data.status !== 'success') window.chatDialog.alert(data.message, '操作失败', '#cc4444');
        }).catch(err => console.error('特效触发失败:', err));
    }
    window.triggerEffect = triggerEffect;

    // ── 字号设置（持久化到 localStorage）─────────────────
    /**
     * 应用字号到聊天消息窗口，并保存到 localStorage
     *
     * @param {string|number} size  字号大小（px 数字）
     */
    function applyFontSize(size) {
        const px = parseInt(size, 10);
        if (isNaN(px) || px < 10 || px > 30) return;

        // 同时应用到公聊窗和包厢窗
        const c1 = document.getElementById('chat-messages-container');
        const c2 = document.getElementById('chat-messages-container2');
        if (c1) c1.style.fontSize = px + 'px';
        if (c2) c2.style.fontSize = px + 'px';

        // 持久化（key 带房间 ID，不同房间各自记住）
        const key = 'chat_font_size';
        localStorage.setItem(key, px);

        // 同步 select 显示
        const sel = document.getElementById('font_size_select');
        if (sel) sel.value = String(px);
    }
    window.applyFontSize = applyFontSize;

    // 页面加载后从 localStorage 恢复之前保存的字号
    document.addEventListener('DOMContentLoaded', () => {
        const saved = localStorage.getItem('chat_font_size');
        if (saved) {
            applyFontSize(saved);
        }
        // 恢复禁音复选框状态
        const muted = localStorage.getItem('chat_sound_muted') === '1';
        const muteChk = document.getElementById('sound_muted');
        if (muteChk) muteChk.checked = muted;
    });

    // ── 特效禁音开关 ─────────────────────────────────────────────────
    /**
     * 切换特效音效的静音状态，持久化到 localStorage。
     * 开启禁音后立即停止当前正在播放的音效。
     *
     * @param {boolean} muted  true = 禁音，false = 开启声音
     */
    function toggleSoundMute(muted) {
        localStorage.setItem('chat_sound_muted', muted ? '1' : '0');
        if (muted && typeof EffectSounds !== 'undefined') {
            EffectSounds.stop(); // 立即停止当前音效
        }
    }
    window.toggleSoundMute = toggleSoundMute;


    // ── 发送消息（Enter 发送，防 IME 输入法重复触发）────────
    // 用 isComposing 标记中文输入法的组词状态，组词期间过滤掉 Enter
    let _imeComposing = false;
    const _contentInput = document.getElementById('content');

    // 中文/日文等 IME 组词开始
    _contentInput.addEventListener('compositionstart', () => {
        _imeComposing = true;
    });
    // 组词结束（确认候选词完成），给 10ms 缓冲让 keydown 先被过滤掉
    _contentInput.addEventListener('compositionend', () => {
        setTimeout(() => {
            _imeComposing = false;
        }, 10);
    });

    _contentInput.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            // IME 正在组词时（如选候选汉字），不触发发送
            if (_imeComposing) return;
            sendMessage(e);
        }
    });

    /**
     * 发送聊天消息（内带防重入锁，避免快速连按 Enter 重复提交）
     */
    let _isSending = false; // 发送中防重入标记
    async function sendMessage(e) {
        if (e) e.preventDefault();
        if (_isSending) return; // 上一次还没结束，忽略
        _isSending = true;

        // 前端禁言检查
        if (isMutedUntil > Date.now()) {
            const remaining = Math.ceil((isMutedUntil - Date.now()) / 1000);
            const remainMin = Math.ceil(remaining / 60);
            // 在聊天窗口显示持久提示，避免弹窗消失太快
            const muteDiv = document.createElement('div');
            muteDiv.className = 'msg-line';
            muteDiv.innerHTML =
                `<span style="color: #dc2626; font-weight: bold;">【提示】您正在禁言中，还需等待约 ${remainMin} 分钟（${remaining} 秒）后方可发言。</span>`;
            const container2 = document.getElementById('say2');
            if (container2) {
                container2.appendChild(muteDiv);
                container2.scrollTop = container2.scrollHeight;
            }
            _isSending = false;
            return;
        }

        const form = document.getElementById('chat-form');
        const formData = new FormData(form);
        const contentInput = document.getElementById('content');
        const submitBtn = document.getElementById('send-btn');

        const content = formData.get('content').trim();
        if (!content) {
            contentInput.focus();
            _isSending = false;
            return;
        }

        // 如果发言对象是 AI 小助手，走专用机器人 API
        const toUser = formData.get('to_user');
        if (toUser === 'AI小班长') {
            contentInput.value = '';
            contentInput.focus();
            _isSending = false;
            sendToChatBot(content); // 异步调用，不阻塞全局发送
            return;
        }

        // ── 神秘箱子暗号拦截 ────────────────────────────────────
        // 当用户输入内容符合暗号格式（4-8位大写字母/数字）时，主动尝试领取
        // 不依赖轮询标志，服务端负责校验是否真有活跃箱子及暗号是否匹配
        const passcodePattern = /^[A-Z0-9]{4,8}$/;
        if (passcodePattern.test(content.trim())) {
            _isSending = false;

            try {
                const claimRes = await fetch('/mystery-box/claim', {
                    method: 'POST',
                    headers: {
                        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
                        'Content-Type': 'application/json',
                        'Accept': 'application/json',
                    },
                    body: JSON.stringify({
                        passcode: content.trim()
                    }),
                });
                const claimData = await claimRes.json();

                if (claimData.ok) {
                    // ✅ 领取成功：清空输入框，不发送普通消息
                    contentInput.value = '';
                    contentInput.focus();

                    // 清除活跃箱子全局标志
                    window._mysteryBoxActive = false;
                    window._mysteryBoxPasscode = null;

                    // 弹出开箱结果卡片
                    const isPositive = (claimData.reward ?? 1) >= 0;
                    window.chatDialog?.alert(
                        claimData.message || '开箱成功！',
                        isPositive ? '🎉 恭喜！' : '☠️ 中了陷阱！',
                        isPositive ? '#10b981' : '#ef4444',
                    );

                    // 更新全局金币余额显示
                    if (window.__chatUser && claimData.balance !== undefined) {
                        window.__chatUser.jjb = claimData.balance;
                    }

                    return;
                }

                // ❌ 领取失败（暗号错误 / 无活跃箱子 / 已被领走）
                // 静默回退到正常发送——不弹错误提示，让消息正常发出
            } catch (_) {
                // 网络错误时同样静默回退正常发送
            }
        }

        submitBtn.disabled = true;

        try {
            const response = await fetch(window.chatContext.sendUrl, {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
                        'content'),
                    'Accept': 'application/json'
                },
                body: formData
            });

            const data = await response.json();
            if (response.ok && data.status === 'success') {
                contentInput.value = '';
                contentInput.focus();
            } else {
                window.chatDialog.alert('发送失败: ' + (data.message || JSON.stringify(data.errors)), '操作失败',
                    '#cc4444');
            }
        } catch (error) {
            window.chatDialog.alert('网络连接错误，消息发送失败！', '网络错误', '#cc4444');
            console.error(error);
        } finally {
            submitBtn.disabled = false;
            _isSending = false; // 释放发送锁，允许下次发送
        }
    }


    // ── 设置房间公告 ─────────────────────────────────────
    function promptAnnouncement() {
        // 从 marquee 读取当前公告全文，剥离末尾的「——发送者 日期」元信息，仅预填纯内容
        const fullText = document.getElementById('announcement-text')?.textContent?.trim() || '';
        const pureText = fullText.replace(/ ——\S+ \d{2}-\d{2} \d{2}:\d{2}$/, '').trim();
        // 使用全局弹窗替代原生 prompt()，通过 .then() 注册回调确保事件正确触发
        window.chatDialog.prompt('请输入新的房间公告/祝福语：', pureText, '设置公告', '#336699').then(newText => {
            if (newText === null || newText.trim() === '') return;

            fetch(`/room/${window.chatContext.roomId}/announcement`, {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
                        'content'),
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                body: JSON.stringify({
                    announcement: newText.trim()
                })
            }).then(res => res.json()).then(data => {
                if (data.status === 'success') {
                    // 用后端返回的含发送者+时间的完整公告文本更新滚动条
                    const marquee = document.getElementById('announcement-text');
                    if (marquee) marquee.textContent = data.announcement;
                    window.chatDialog.alert('公告已更新！', '提示', '#16a34a');
                } else {
                    window.chatDialog.alert(data.message || '更新失败', '操作失败', '#cc4444');
                }
            }).catch(e => {
                window.chatDialog.alert('设置公告失败：' + e.message, '操作失败', '#cc4444');
            });
        });
    }

    // ── 站长公屏讲话 ─────────────────────────────────────
    function promptAnnounceMessage() {
        // 使用全局弹窗替代原生 prompt()，通过 .then() 注册回调确保事件正确触发
        window.chatDialog.prompt('请输入公屏讲话内容：', '', '📢 公屏讲话', '#7c3aed').then(content => {
            if (!content || !content.trim()) return;

            fetch('/command/announce', {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
                        'content'),
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                body: JSON.stringify({
                    content: content.trim(),
                    room_id: window.chatContext.roomId,
                })
            }).then(res => res.json()).then(data => {
                if (data.status !== 'success') {
                    window.chatDialog.alert(data.message || '发送失败', '操作失败', '#cc4444');
                }
            }).catch(e => {
                window.chatDialog.alert('发送失败：' + e.message, '操作失败', '#cc4444');
            });
        });
    }

    // ── 管理员全员清屏 ─────────────────────────────────────
    function adminClearScreen() {
        // 使用全局弹窗替代原生 confirm()，通过 .then() 注册回调确保事件正确触发
        window.chatDialog.confirm('确定要清除所有人的聊天记录吗？（悄悄话将保留）', '全员清屏', '#dc2626').then(ok => {
            if (!ok) return;

            fetch('/command/clear-screen', {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
                        'content'),
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                body: JSON.stringify({
                    room_id: window.chatContext.roomId,
                })
            }).then(res => res.json()).then(data => {
                if (data.status !== 'success') {
                    window.chatDialog.alert(data.message || '清屏失败', '操作失败', '#cc4444');
                }
            }).catch(e => {
                window.chatDialog.alert('清屏失败：' + e.message, '操作失败', '#cc4444');
            });
        });
    }

    // ── 本地清屏（仅限自己的屏幕）───────────────────────────

    function localClearScreen() {
        // 清理公聊窗口
        const say1 = document.getElementById('chat-messages-container');
        if (say1) say1.innerHTML = '';

        // 清理包厢窗口
        const say2 = document.getElementById('chat-messages-container2');
        if (say2) say2.innerHTML = '';

        // 将当前最大消息 ID 保存至本地，刷新时只显示大于此 ID 的历史记录
        localStorage.setItem(`local_clear_msg_id_${window.chatContext.roomId}`, _maxMsgId);

        // 插入清屏提示
        const sysDiv = document.createElement('div');
        sysDiv.className = 'msg-line';
        const now = new Date();
        const timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2,
            '0') + ':' + now.getSeconds().toString().padStart(2, '0');
        sysDiv.innerHTML =
            `<span style="color: #64748b; font-weight: bold;">🧹 您已执行本地清屏</span><span class="msg-time">(${timeStr})</span>`;
        if (say1) {
            say1.appendChild(sysDiv);
            say1.scrollTop = say1.scrollHeight;
        }
    }

    // ── 滚屏开关 ─────────────────────────────────────
    function toggleAutoScroll() {
        autoScroll = !autoScroll;
        const cb = document.getElementById('auto_scroll');
        if (cb) cb.checked = autoScroll;
        const statusEl = document.getElementById('scroll-status');
        if (statusEl) statusEl.textContent = autoScroll ? '开' : '关';
    }

    // ── 退出房间 ─────────────────────────────────────
    async function leaveRoom() {
        // 标记主动离开，pagehide 里不重复发 beacon
        window._manualLeave = true;
        clearTimeout(visibilityTimer);

        try {
            await fetch(window.chatContext.leaveUrl, {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
                        'content'),
                    'Accept': 'application/json'
                }
            });
        } catch (e) {
            console.error(e);
        }
        // 弹出窗口直接关闭，如果不是弹出窗口则跳回首页
        window.close();
        setTimeout(() => {
            window.location.href = '/';
        }, 500);
    }



    // ── 关闭/离开页面时自动调用 leave，结算勤务时长 ──────────────────────
    // 使用 sendBeacon 确保浏览器关闭时请求也能发出（比 fetch 更可靠）
    // 注意：用 pagehide 而非 beforeunload，避免 Chrome 触发原生「离开网站」确认框
    function sendLeaveBeacon() {
        if (window._manualLeave) {
            return;
        } // 主动调用 leaveRoom() 时不重复发
        const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
        if (!csrfToken || !window.chatContext?.leaveUrl) {
            return;
        }
        const data = new FormData();
        data.append('_token', csrfToken);
        navigator.sendBeacon(window.chatContext.leaveUrl, data);
    }

    // pagehide：页面关闭/浏览器关闭/刷新均触发，且不会弹原生确认框
    window.addEventListener('pagehide', sendLeaveBeacon);

    // visibilitychange：切换到后台标签超过30秒也结算（防止长期挂机不算时长）
    let visibilityTimer = null;
    document.addEventListener('visibilitychange', () => {
        if (document.hidden) {
            // 切到后台，30秒后结算
            visibilityTimer = setTimeout(sendLeaveBeacon, 30 * 1000);
        } else {
            // 切回来，取消结算
            clearTimeout(visibilityTimer);
            visibilityTimer = null;
        }
    });




    // ── 掉线检测计数器 ──
    let heartbeatFailCount = 0;
    const MAX_HEARTBEAT_FAILS = 3;

    // ── 存点功能（手动 + 自动）─────────────────────
    async function saveExp(silent = false) {
        if (!window.chatContext || !window.chatContext.heartbeatUrl) return;
        try {
            const response = await fetch(window.chatContext.heartbeatUrl, {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')
                        .getAttribute('content'),
                    'Accept': 'application/json'
                }
            });

            // 检测登录态失效
            if (response.status === 401 || response.status === 419) {
                window.chatDialog.alert('⚠️ 您的登录已失效（可能超时或在其他设备登录），请重新登录。', '连接警告', '#b45309');
                window.location.href = '/';
                return;
            }

            const data = await response.json();
            if (response.ok && data.status === 'success') {
                heartbeatFailCount = 0;
                const now = new Date();
                const timeStr = now.getHours().toString().padStart(2, '0') + ':' +
                    now.getMinutes().toString().padStart(2, '0') + ':' +
                    now.getSeconds().toString().padStart(2, '0');
                const d = data.data;
                const levelTitle = d.title || '普通会员';

                let levelInfo = '';
                if (d.is_max_level) {
                    levelInfo = `级别(${d.user_level});经验(${d.exp_num});金币(${d.jjb}枚);已满级。`;
                } else {
                    levelInfo = `级别(${d.user_level});经验(${d.exp_num});金币(${d.jjb}枚)。`;
                }

                // 本次获得的奖励提示
                let gainInfo = '';
                if (d.exp_gain > 0 || d.jjb_gain > 0) {
                    const parts = [];
                    if (d.exp_gain > 0) parts.push(`经验+${d.exp_gain}`);
                    if (d.jjb_gain > 0) parts.push(`金币+${d.jjb_gain}`);
                    gainInfo = `(本次: ${parts.join(', ')})`;
                }

                if (data.data.leveled_up) {
                    const upDiv = document.createElement('div');
                    upDiv.className = 'msg-line';
                    upDiv.innerHTML =
                        `<span style="color: #d97706; font-weight: bold;">【系统】恭喜！你的经验值已达 ${d.exp_num}，等级突破至 LV.${d.user_level}！🌟</span><span class="msg-time">(${timeStr})</span>`;
                    container2.appendChild(upDiv);
                    if (autoScroll) container2.scrollTop = container2.scrollHeight;
                }

                if (!silent) {
                    const detailDiv = document.createElement('div');
                    detailDiv.className = 'msg-line';
                    detailDiv.innerHTML =
                        `<span style="color: green;">【${levelTitle}存点】您的最新情况:${levelInfo} ${gainInfo}</span><span class="msg-time">(${timeStr})</span>`;
                    container2.appendChild(detailDiv);
                    if (autoScroll) container2.scrollTop = container2.scrollHeight;
                } else {
                    return;
                }
            }
        } catch (e) {
            console.error('存点失败', e);
            heartbeatFailCount++;

            if (heartbeatFailCount >= MAX_HEARTBEAT_FAILS) {
                window.chatDialog.alert('⚠️ 与服务器的连接已断开，请检查网络后重新登录。', '连接警告', '#b45309');
                window.location.href = '/';
                return;
            }

            if (!silent) {
                const sysDiv = document.createElement('div');
                sysDiv.className = 'msg-line';
                sysDiv.innerHTML = `<span style="color: red;">【系统】存点失败，请稍后重试</span>`;
                container2.appendChild(sysDiv);
            }
        }
    }

    // ── 自动存点心跳（每60秒自动存一次）───────────
    const HEARTBEAT_INTERVAL = 60 * 1000;
    setInterval(() => saveExp(true), HEARTBEAT_INTERVAL);
    setTimeout(() => saveExp(true), 10000);


    /**
     * HTML 转义函数，防止 XSS
     */
    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
</script>
