#!/usr/bin/env bash
# ============================================================
#  chat_sh.sh  -  聊天系统交互式客户端 v1.4
#  修复：Enter 键无响应 —— read -n 会把回车(\n)当行分隔符吞掉导致 KEY 为空，
#        改用 read -N 精确读 N 个字符（不受换行符影响）
# ============================================================

SERVER="${CHAT_SERVER:-http://jh.mdyym.j3.ink}"
CFG_DIR="${HOME:-/tmp}/.chat_system"
CFG_FILE="$CFG_DIR/config"
mkdir -p "$CFG_DIR" 2>/dev/null

# ─────────────── UTF-8 locale ───────────────
if [ -z "$LANG" ] || ! printf '%s' "$LANG" | grep -qi utf; then
    for L in zh_CN.UTF-8 en_US.UTF-8 C.UTF-8; do
        if locale -a 2>/dev/null | grep -qi "^${L}$"; then
            export LANG="$L"; export LC_ALL="$L"; break
        fi
    done
fi

# ─────────────── 依赖 ───────────────
command -v curl    >/dev/null 2>&1 || { echo "需要 curl：pkg install curl"; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "需要 python3：pkg install python"; exit 1; }

# ─────────────── 颜色 ───────────────
if [ -t 1 ]; then
    E=$'\033'
    RESET="${E}[0m"; BOLD="${E}[1m"; DIM="${E}[2m"
    RED="${E}[31m"; GREEN="${E}[32m"; YELLOW="${E}[33m"
    BLUE="${E}[34m"; MAGENTA="${E}[35m"; CYAN="${E}[36m"; WHITE="${E}[37m"
    BG_BLUE="${E}[44m"; BG_CYAN="${E}[46m"
    CLEAR="${E}[2J${E}[H"
    HIDE_CURSOR="${E}[?25l"; SHOW_CURSOR="${E}[?25h"
else
    RESET=""; BOLD=""; DIM=""; RED=""; GREEN=""; YELLOW=""
    BLUE=""; MAGENTA=""; CYAN=""; WHITE=""; BG_BLUE=""; BG_CYAN=""
    CLEAR=""; HIDE_CURSOR=""; SHOW_CURSOR=""
fi

# ─────────────── 全局状态 ───────────────
TOKEN=""; USERNAME=""; USER_ID=""
CURRENT_ROOM=""; CURRENT_ROOM_NAME=""
POLLER_PID=""
SELECTED_INDEX=-1
PROMPT_RESULT=""

# ★ 按键分类结果
KEY=""
KEY_TYPE=""        # ENTER / UP / DOWN / LEFT / RIGHT / ESC / BACKSPACE / CTRL_C / CHAR / UNKNOWN
KEY_CHAR=""        # 当 KEY_TYPE=CHAR 时的字符

if [ -f "$CFG_FILE" ]; then source "$CFG_FILE" 2>/dev/null; fi

# ─────────────── 终端模式 ───────────────
STTY_SAVED=""
enter_raw() {
    [ -z "$STTY_SAVED" ] && STTY_SAVED=$(stty -g 2>/dev/null)
    stty -icanon -echo 2>/dev/null
}
leave_raw() {
    if [ -n "$STTY_SAVED" ]; then
        stty "$STTY_SAVED" 2>/dev/null
        STTY_SAVED=""
    else
        stty icanon echo 2>/dev/null
    fi
}
cleanup() {
    printf '%s' "$SHOW_CURSOR" 2>/dev/null
    leave_raw
    [ -n "$POLLER_PID" ] && kill "$POLLER_PID" 2>/dev/null
    return 0
}
trap cleanup EXIT INT TERM HUP

save_login() {
    cat > "$CFG_FILE" <<EOF
TOKEN="$TOKEN"
USERNAME="$USERNAME"
USER_ID="$USER_ID"
EOF
}

# ═══════════════════════════════════════════════════════════════
#  ★★★ 读键并分类（核心修复）★★★
#  返回 0 = 有按键；返回 1 = 超时
#  结果放在全局变量：KEY_TYPE / KEY / KEY_CHAR
# ═══════════════════════════════════════════════════════════════
read_key() {
    local timeout="${1:-0.5}"
    KEY=""
    KEY_TYPE=""
    KEY_CHAR=""

    if ! IFS= read -rsN1 -t "$timeout" KEY 2>/dev/null; then
        KEY=""
        KEY_TYPE="TIMEOUT"
        return 1
    fi

    # ───── 情况 1：以 ESC 开头（可能是方向键序列）─────
    if [ "$KEY" = $'\x1b' ]; then
        local c1="" c2=""
        IFS= read -rsN1 -t 0.05 c1 2>/dev/null
        if [ -n "$c1" ]; then
            IFS= read -rsN1 -t 0.05 c2 2>/dev/null
        fi
        KEY="${KEY}${c1}${c2}"

        case "$KEY" in
            $'\x1b[A'|$'\x1bOA') KEY_TYPE="UP" ;;
            $'\x1b[B'|$'\x1bOB') KEY_TYPE="DOWN" ;;
            $'\x1b[C'|$'\x1bOC') KEY_TYPE="RIGHT" ;;
            $'\x1b[D'|$'\x1bOD') KEY_TYPE="LEFT" ;;
            $'\x1b')              KEY_TYPE="ESC" ;;
            *)                    KEY_TYPE="UNKNOWN" ;;
        esac
        return 0
    fi

    # ───── 情况 2：单字节字符 → 用 ASCII 码判断 ─────
    if [ ${#KEY} -eq 1 ]; then
        local code=""
        code=$(LC_ALL=C printf '%d' "'$KEY" 2>/dev/null)

        case "$code" in
            10|13)  KEY_TYPE="ENTER" ;;        # LF / CR
            27)     KEY_TYPE="ESC" ;;
            127|8)  KEY_TYPE="BACKSPACE" ;;    # DEL / BS
            3)      KEY_TYPE="CTRL_C" ;;       # Ctrl+C
            9)      KEY_TYPE="TAB" ;;
            *)
                # 可打印字符（ASCII 32-126）或 UTF-8 首字节
                if [ "$code" -ge 32 ]; then
                    KEY_TYPE="CHAR"
                    KEY_CHAR="$KEY"
                else
                    KEY_TYPE="UNKNOWN"
                fi
                ;;
        esac
        return 0
    fi

    # ───── 情况 3：多字节 UTF-8 字符 ─────
    KEY_TYPE="CHAR"
    KEY_CHAR="$KEY"
    return 0
}

# ═══════════════════════════════════════════════════════════════
#  终端工具
# ═══════════════════════════════════════════════════════════════
term_width()  { tput cols  2>/dev/null || echo 60; }
term_height() { tput lines 2>/dev/null || echo 24; }
clear_screen(){ printf '%s' "$CLEAR"; }

repeat_ch() {
    local ch="$1" n="$2" i=0
    while [ "$i" -lt "$n" ]; do printf '%s' "$ch"; i=$((i+1)); done
}

# 删除最后一个完整字符（兼容中文）
remove_last_char() {
    python3 -c "import sys; print(sys.argv[1][:-1] if sys.argv[1] else '')" "$1"
}

# 按显示宽度截断（CJK/全角算 2 宽），避免长消息换行破坏布局
fit_width() {
    python3 - "$1" "$2" <<'PYEOF'
import sys
try:
    from unicodedata import east_asian_width
    s = sys.argv[1]
    maxw = int(sys.argv[2])
    w = 0
    out = []
    for ch in s:
        cw = 2 if east_asian_width(ch) in ('W', 'F') else 1
        if w + cw > maxw:
            break
        w += cw
        out.append(ch)
    print(''.join(out))
except Exception:
    print(sys.argv[1][:int(sys.argv[2])])
PYEOF
}

# ═══════════════════════════════════════════════════════════════
#  菜单绘制（只有变化时才调用）
# ═══════════════════════════════════════════════════════════════
draw_menu() {
    local title="$1" subtitle="$2" cur="$3"
    shift 3
    local -a items=("$@")

    clear_screen
    local w=$(term_width)

    printf "${CYAN}"; repeat_ch '═' "$w"; printf "${RESET}\n"
    printf "  ${BOLD}${WHITE}%s${RESET}\n" "$title"
    [ -n "$subtitle" ] && printf "  ${DIM}%s${RESET}\n" "$subtitle"
    printf "${CYAN}"; repeat_ch '═' "$w"; printf "${RESET}\n\n"

    local i=0
    for item in "${items[@]}"; do
        if [ "$i" -eq "$cur" ]; then
            printf "  ${BG_CYAN}${BOLD}${WHITE}  >  %s  ${RESET}\n" "$item"
        else
            printf "     ${WHITE}%s${RESET}\n" "$item"
        fi
        printf "\n"
        i=$((i + 1))
    done

    printf "\n  ${DIM}[↑/↓] 选择    [Enter] 确认    [q] 返回${RESET}\n"
}

# ═══════════════════════════════════════════════════════════════
#  ★ 方向键菜单（用 KEY_TYPE 判断，绝不做字符串比较）
# ═══════════════════════════════════════════════════════════════
menu_select() {
    local title="$1"; shift
    local subtitle="$1"; shift
    local -a items=("$@")
    local n=${#items[@]}
    [ "$n" -eq 0 ] && return 1
    local cur=0

    enter_raw
    printf '%s' "$HIDE_CURSOR"
    draw_menu "$title" "$subtitle" "$cur" "${items[@]}"

    while true; do
        # 超时：什么都不做
        if ! read_key 0.5; then
            continue
        fi

        case "$KEY_TYPE" in
            UP)
                if [ "$cur" -gt 0 ]; then
                    cur=$((cur - 1))
                    draw_menu "$title" "$subtitle" "$cur" "${items[@]}"
                fi
                ;;
            DOWN)
                if [ "$cur" -lt $((n - 1)) ]; then
                    cur=$((cur + 1))
                    draw_menu "$title" "$subtitle" "$cur" "${items[@]}"
                fi
                ;;
            ENTER)
                printf '%s' "$SHOW_CURSOR"
                leave_raw
                SELECTED_INDEX=$cur
                return 0
                ;;
            CHAR)
                if [ "$KEY_CHAR" = "q" ] || [ "$KEY_CHAR" = "Q" ]; then
                    printf '%s' "$SHOW_CURSOR"
                    leave_raw
                    SELECTED_INDEX=-1
                    return 1
                fi
                ;;
        esac
        # 其它键忽略
    done
}

# ═══════════════════════════════════════════════════════════════
#  文本输入
# ═══════════════════════════════════════════════════════════════
prompt_input() {
    local title="$1"
    local default="${2:-}"
    local password="${3:-false}"
    local value=""

    clear_screen
    local w=$(term_width)
    printf "${CYAN}"; repeat_ch '═' "$w"; printf "${RESET}\n\n"
    printf "  ${BOLD}%s${RESET}\n\n" "$title"
    [ -n "$default" ] && printf "  ${DIM}默认：%s（回车保持不变）${RESET}\n\n" "$default"
    printf "  ${GREEN}> ${RESET}"

    if [ "$password" = "true" ]; then
        stty -echo 2>/dev/null
        IFS= read -r value
        stty echo 2>/dev/null
        printf "\n"
    else
        IFS= read -r value
    fi

    [ -z "$value" ] && value="$default"
    PROMPT_RESULT="$value"
}

# ═══════════════════════════════════════════════════════════════
#  提示
# ═══════════════════════════════════════════════════════════════
msg_line() {
    case "$1" in
        ok)   printf "  ${GREEN}[OK]${RESET} %s\n" "$2" ;;
        err)  printf "  ${RED}[X]${RESET} %s\n" "$2" ;;
        info) printf "  ${BLUE}[i]${RESET} %s\n" "$2" ;;
        warn) printf "  ${YELLOW}[!]${RESET} %s\n" "$2" ;;
        *)    printf "  %s\n" "$2" ;;
    esac
}

msg_pause() {
    printf "\n  ${DIM}按任意键继续...${RESET}"
    enter_raw
    read_key 10 >/dev/null
    leave_raw
}

# ═══════════════════════════════════════════════════════════════
#  JSON 工具
# ═══════════════════════════════════════════════════════════════
json_path() {
    python3 - "$1" "$2" <<'PYEOF'
import sys, json
try:
    d = json.loads(sys.argv[1])
    for k in sys.argv[2].split('.'):
        if isinstance(d, dict): d = d.get(k)
        else: d = None; break
    if d is None: print("")
    elif isinstance(d, (dict, list)): print(json.dumps(d, ensure_ascii=False))
    elif isinstance(d, bool): print("true" if d else "false")
    else: print(d)
except Exception:
    print("")
PYEOF
}

json_array_iter() {
    python3 - "$1" "$2" <<'PYEOF'
import sys, json
try:
    d = json.loads(sys.argv[1])
    for k in sys.argv[2].split('.'):
        if isinstance(d, dict): d = d.get(k)
        else: d = []; break
    if not isinstance(d, list): sys.exit(0)
    for item in d:
        print(json.dumps(item, ensure_ascii=False))
except Exception:
    pass
PYEOF
}

json_ok() {
    python3 - "$1" <<'PYEOF'
import sys, json
try:
    d = json.loads(sys.argv[1])
    sys.exit(0 if d.get('success') else 1)
except Exception:
    sys.exit(1)
PYEOF
}

format_messages() {
    python3 - "$1" <<'PYEOF'
import sys, json
try:
    d = json.loads(sys.argv[1])
    if not d.get('success'): sys.exit(0)
    for m in d.get('data', {}).get('messages', []) or d.get('messages', []):
        user = str(m.get('username') or m.get('user') or m.get('name') or '系统')
        msg = str(m.get('message') or m.get('content') or m.get('text') or m.get('msg') or '')
        ts_raw = m.get('timestamp') or m.get('time') or m.get('created_at') or ''
        t = ''
        if ts_raw:
            parts = str(ts_raw).split(' ')
            if len(parts) >= 2: t = parts[-1].split('.')[0]
        perm = int(m.get('permission') or m.get('perm') or m.get('role') or 3)
        tag = ''
        if perm == 1: tag = '站长'
        elif perm == 2: tag = '管理'
        elif perm == 4: tag = '访客'
        user = user.replace('\t', ' ').replace('\n', ' ').replace('\x1f', ' ')
        msg = msg.replace('\t', ' ').replace('\r', '').replace('\x1f', ' ')
        msg = msg.replace('\n', ' | ')
        tag = tag.replace('\x1f', ' ')
        print(f"{user}\x1f{t}\x1f{tag}\x1f{msg}")
except Exception:
    pass
PYEOF
}

url_encode() {
    python3 -c "import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1], safe=''))" "$1"
}

# ═══════════════════════════════════════════════════════════════
#  API
# ═══════════════════════════════════════════════════════════════
api_post() {
    local action="$1" data="$2"
    curl -s -m 15 -X POST \
        -H "Authorization: Bearer $TOKEN" \
        -H "Content-Type: application/x-www-form-urlencoded" \
        --data "$data" \
        "$SERVER/api.php?action=$action" 2>/dev/null
}

api_get() {
    local qs="$1"
    curl -s -m 15 \
        -H "Authorization: Bearer $TOKEN" \
        "$SERVER/api.php?$qs" 2>/dev/null
}

# ═══════════════════════════════════════════════════════════════
#  启动画面
# ═══════════════════════════════════════════════════════════════
splash() {
    clear_screen
    printf "\n\n\n"
    printf "${CYAN}${BOLD}"
    printf "        ██████╗██╗  ██╗ █████╗ ████████╗\n"
    printf "       ██╔════╝██║  ██║██╔══██╗╚══██╔══╝\n"
    printf "       ██║     ███████║███████║   ██║   \n"
    printf "       ██║     ██╔══██║██╔══██║   ██║   \n"
    printf "       ╚██████╗██║  ██║██║  ██║   ██║   \n"
    printf "        ╚═════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   \n"
    printf "${RESET}\n"
    printf "       ${DIM}交互式聊天客户端 v1.4${RESET}\n"
    printf "       ${DIM}服务器：%s${RESET}\n" "$SERVER"
    sleep 1
}

# ═══════════════════════════════════════════════════════════════
#  登录
# ═══════════════════════════════════════════════════════════════
screen_login() {
    prompt_input "请输入用户名" "" "false"
    local user="$PROMPT_RESULT"
    [ -z "$user" ] && return 1

    prompt_input "请输入密码" "" "true"
    local pass="$PROMPT_RESULT"
    [ -z "$pass" ] && return 1

    clear_screen
    msg_line info "正在登录..."
    local resp
    resp=$(api_post "login" "username=$(url_encode "$user")&password=$(url_encode "$pass")")

    if json_ok "$resp"; then
        TOKEN=$(json_path "$resp" "data.token")
        USERNAME=$(json_path "$resp" "data.username")
        USER_ID=$(json_path "$resp" "data.id")
        save_login
        clear_screen
        msg_line ok "登录成功，欢迎 $USERNAME！"
        sleep 1
        return 0
    else
        local err
        err=$(json_path "$resp" "error")
        clear_screen
        msg_line err "登录失败：${err:-未知错误}"
        msg_pause
        return 1
    fi
}

# ═══════════════════════════════════════════════════════════════
#  注册
# ═══════════════════════════════════════════════════════════════
screen_register() {
    prompt_input "请输入用户名（3-20 字符）" "" "false"
    local user="$PROMPT_RESULT"
    [ -z "$user" ] && return 1
    if [ ${#user} -lt 3 ]; then
        clear_screen; msg_line err "用户名至少 3 位"; msg_pause; return 1
    fi

    prompt_input "请输入密码（至少 6 位）" "" "true"
    local pass="$PROMPT_RESULT"
    [ -z "$pass" ] && return 1
    if [ ${#pass} -lt 6 ]; then
        clear_screen; msg_line err "密码至少 6 位"; msg_pause; return 1
    fi

    prompt_input "请输入邮箱（可留空）" "" "false"
    local email="$PROMPT_RESULT"

    clear_screen
    msg_line info "正在注册..."
    local resp
    resp=$(api_post "register" \
        "username=$(url_encode "$user")&password=$(url_encode "$pass")&email=$(url_encode "$email")")

    if json_ok "$resp"; then
        clear_screen
        msg_line ok "注册成功！返回后请登录"
        msg_pause
        return 0
    else
        local err
        err=$(json_path "$resp" "error")
        clear_screen
        msg_line err "注册失败：${err:-未知错误}"
        msg_pause
        return 1
    fi
}

# ═══════════════════════════════════════════════════════════════
#  房间列表
# ═══════════════════════════════════════════════════════════════
ROOM_IDS=()
ROOM_NAMES=()
ROOM_DESCS=()

load_rooms() {
    ROOM_IDS=()
    ROOM_NAMES=()
    ROOM_DESCS=()

    local resp
    resp=$(api_get "action=list_servers&limit=50")
    json_ok "$resp" || return 1

    local line id name desc
    while IFS= read -r line; do
        [ -z "$line" ] && continue
        id=$(json_path "$line" "id")
        name=$(json_path "$line" "server_name")
        desc=$(json_path "$line" "description")
        [ -z "$id" ] && continue
        ROOM_IDS+=("$id")
        ROOM_NAMES+=("$name")
        ROOM_DESCS+=("$desc")
    done < <(json_array_iter "$resp" "data.servers")

    return 0
}

screen_join_room() {
    while true; do
        clear_screen
        msg_line info "正在获取房间列表..."
        if ! load_rooms; then
            clear_screen
            msg_line err "获取房间列表失败，请检查网络"
            msg_pause
            return 1
        fi

        if [ ${#ROOM_IDS[@]} -eq 0 ]; then
            clear_screen
            msg_line warn "当前没有任何房间"
            msg_line info "试试「创建房间」开一个吧"
            msg_pause
            return 1
        fi

        local -a items=()
        local i
        for i in "${!ROOM_NAMES[@]}"; do
            local d="${ROOM_DESCS[$i]}"
            [ ${#d} -gt 30 ] && d="${d:0:30}…"
            if [ -n "$d" ]; then
                items+=("${ROOM_NAMES[$i]}  —  $d")
            else
                items+=("${ROOM_NAMES[$i]}")
            fi
        done
        items+=("<-- 返回")

        if menu_select "选择一个房间加入" "共 ${#ROOM_IDS[@]} 个房间" "${items[@]}"; then
            local idx=$SELECTED_INDEX
            if [ "$idx" -eq "${#ROOM_NAMES[@]}" ]; then
                return 1
            fi
            CURRENT_ROOM="${ROOM_IDS[$idx]}"
            CURRENT_ROOM_NAME="${ROOM_NAMES[$idx]}"
            chat_room
        else
            return 1
        fi
    done
}

# ═══════════════════════════════════════════════════════════════
#  创建房间
# ═══════════════════════════════════════════════════════════════
screen_create_room() {
    prompt_input "请输入新房间的名称（1-50 字符）" "" "false"
    local name="$PROMPT_RESULT"
    [ -z "$name" ] && return 1

    prompt_input "请输入房间描述（可留空）" "" "false"
    local desc="$PROMPT_RESULT"

    clear_screen
    msg_line info "正在创建房间..."
    local resp
    resp=$(api_post "create_room" \
        "name=$(url_encode "$name")&description=$(url_encode "$desc")")

    if json_ok "$resp"; then
        local rid rname
        rid=$(json_path "$resp" "data.room_id")
        rname=$(json_path "$resp" "data.name")
        clear_screen
        msg_line ok "房间「$rname」创建成功！"
        sleep 1
        CURRENT_ROOM="$rid"
        CURRENT_ROOM_NAME="$rname"
        chat_room
    else
        local err
        err=$(json_path "$resp" "error")
        clear_screen
        msg_line err "创建失败：${err:-未知错误}"
        msg_pause
        return 1
    fi
}

# ═══════════════════════════════════════════════════════════════
#  聊天室
# ═══════════════════════════════════════════════════════════════
chat_room() {
    local room_id="$CURRENT_ROOM"
    local room_name="$CURRENT_ROOM_NAME"
    local msg_file="$CFG_DIR/msgs_${room_id}.log"
    local last_id_file="$CFG_DIR/lastid_${room_id}.txt"

    : > "$msg_file"
    printf '0' > "$last_id_file"

    local hist
    hist=$(api_get "action=get_messages&server_id=$(url_encode "$room_id")&limit=50")
    if json_ok "$hist"; then
        format_messages "$hist" >> "$msg_file"
        local max_id
        max_id=$(printf '%s' "$hist" | python3 -c '
import sys, json
try:
    d = json.load(sys.stdin)
    ids = [int(m.get("id") or m.get("_id") or m.get("mid") or 0) for m in (d.get("data", {}).get("messages", []) or d.get("messages", []))]
    print(max(ids) if ids else 0)
except Exception:
    print(0)
')
        printf '%s' "$max_id" > "$last_id_file"
    fi

    poll_messages "$room_id" "$last_id_file" "$msg_file" &
    POLLER_PID=$!

    chat_loop "$room_id" "$room_name" "$msg_file" "$last_id_file"

    kill "$POLLER_PID" 2>/dev/null
    wait "$POLLER_PID" 2>/dev/null
    POLLER_PID=""
}

poll_messages() {
    local room_id="$1"
    local last_id_file="$2"
    local msg_file="$3"

    while true; do
        local lid
        lid=$(cat "$last_id_file" 2>/dev/null)
        [ -z "$lid" ] && lid=0

        local resp
        resp=$(curl -s -m 8 \
            -H "Authorization: Bearer $TOKEN" \
            "$SERVER/api.php?action=get_messages&server_id=$(url_encode "$room_id")&last_id=$lid&limit=50" \
            2>/dev/null)

        if [ -n "$resp" ] && json_ok "$resp"; then
            local new_max
            new_max=$(printf '%s' "$resp" | python3 -c '
import sys, json
try:
    d = json.load(sys.stdin)
    ids = [int(m.get("id") or m.get("_id") or m.get("mid") or 0) for m in (d.get("data", {}).get("messages", []) or d.get("messages", []))]
    print(max(ids) if ids else 0)
except Exception:
    print(0)
')
            if [ -n "$new_max" ] && [ "$new_max" -gt "$lid" ] 2>/dev/null; then
                format_messages "$resp" >> "$msg_file"
                printf '%s' "$new_max" > "$last_id_file"
            fi
        fi
        sleep 0.5
    done
}

draw_chat() {
    local room_name="$1"
    local input="$2"
    local msg_file="$3"

    local w=$(term_width)
    local h=$(term_height)
    [ "$h" -lt 10 ] && h=10

    clear_screen
    printf "${BG_BLUE}${WHITE}${BOLD} 房间：%s ${RESET}" "$room_name"
    local right="用户 $USERNAME "
    local pad=$((w - ${#room_name} - 8 - ${#right}))
    [ "$pad" -gt 0 ] && printf '%*s' "$pad" ""
    printf "${CYAN}%s${RESET}\n" "$right"
    printf "${CYAN}"; repeat_ch '─' "$w"; printf "${RESET}\n"

    local msg_h=$((h - 6))
    [ "$msg_h" -lt 3 ] && msg_h=3

    local total
    total=$(wc -l < "$msg_file" 2>/dev/null)
    total=${total:-0}
    local start=$((total - msg_h + 1))
    [ "$start" -lt 1 ] && start=1

    local mw=$((w - 14))
    [ "$mw" -lt 4 ] && mw=4

    local i=0
    while IFS=$'\x1f' read -r user time tag msg; do
        i=$((i + 1))
        [ "$i" -lt "$start" ] && continue
        local disp="$msg"
        if [ ${#msg} -gt "$mw" ]; then
            disp=$(fit_width "$msg" "$mw")
        fi
        if [ "$user" = "$USERNAME" ]; then
            printf " ${DIM}[%s]${RESET} ${GREEN}你${RESET}: %s\n" "$time" "$disp"
        elif [ "$tag" = "站长" ]; then
            printf " ${DIM}[%s]${RESET} ${RED}[站长]%s${RESET}: %s\n" "$time" "$user" "$disp"
        elif [ "$tag" = "管理" ]; then
            printf " ${DIM}[%s]${RESET} ${YELLOW}[管理]%s${RESET}: %s\n" "$time" "$user" "$disp"
        elif [ "$tag" = "访客" ]; then
            printf " ${DIM}[%s]${RESET} ${CYAN}[访客]%s${RESET}: %s\n" "$time" "$user" "$disp"
        else
            printf " ${DIM}[%s]${RESET} ${MAGENTA}%s${RESET}: %s\n" "$time" "$user" "$disp"
        fi
    done < "$msg_file"

    local shown=$((total - start + 1))
    while [ "$shown" -lt "$msg_h" ]; do
        printf "\n"
        shown=$((shown + 1))
    done

    printf "${CYAN}"; repeat_ch '─' "$w"; printf "${RESET}\n"
    printf "  ${DIM}[Esc] 退出聊天   输入后回车发送   (/exit 亦可)${RESET}\n"
    printf "${GREEN}> ${RESET}%s" "$input"
}

chat_loop() {
    local room_id="$1"
    local room_name="$2"
    local msg_file="$3"
    local last_id_file="$4"

    local input=""
    local last_total=-1
    local total=0

    enter_raw
    printf '%s' "$HIDE_CURSOR"
    draw_chat "$room_name" "$input" "$msg_file"
    printf '%s' "$SHOW_CURSOR"

    while true; do
        local need_redraw=0

        if read_key 0.5; then
            case "$KEY_TYPE" in
                UP|DOWN|LEFT|RIGHT|TAB|UNKNOWN)
                    : # 忽略
                    ;;
                ESC|CTRL_C)
                    break
                    ;;
                BACKSPACE)
                    if [ -n "$input" ]; then
                        input=$(remove_last_char "$input")
                        need_redraw=1
                    fi
                    ;;
                ENTER)
                    if [ "$input" = "/exit" ] || [ "$input" = "/q" ]; then
                        input=""
                        break
                    elif [ -n "$input" ]; then
                        local text="$input"
                        input=""
                        send_msg_now "$room_id" "$text" "$msg_file" "$last_id_file"
                        need_redraw=1
                    fi
                    ;;
                CHAR)
                    input="${input}${KEY_CHAR}"
                    need_redraw=1
                    ;;
            esac
        fi

        total=$(wc -l < "$msg_file" 2>/dev/null)
        total=${total:-0}
        if [ "$total" -ne "$last_total" ]; then
            last_total="$total"
            need_redraw=1
        fi

        if [ "$need_redraw" -eq 1 ]; then
            draw_chat "$room_name" "$input" "$msg_file"
            printf '%s' "$SHOW_CURSOR"
        fi
    done

    printf '%s' "$SHOW_CURSOR"
    leave_raw
    clear_screen
    msg_line info "已离开房间「$room_name」"
    sleep 0.6
}

send_msg_now() {
    local room_id="$1"
    local text="$2"
    local msg_file="$3"
    local last_id_file="$4"

    [ -z "$text" ] && return

    local resp
    resp=$(api_post "send_message" \
        "message=$(url_encode "$text")&server_id=$(url_encode "$room_id")")

    if json_ok "$resp"; then
        local new_id=""
        for p in "data.message.id" "data.id" "data.message_id" "data.msg_id"; do
            new_id=$(json_path "$resp" "$p")
            [ -n "$new_id" ] && break
        done
        [ -n "$new_id" ] && printf '%s' "$new_id" > "$last_id_file"
        local now
        now=$(date '+%H:%M:%S')
        printf '%s\x1f%s\x1f\x1f%s\n' "$USERNAME" "$now" "$text" >> "$msg_file"
    else
        local err
        err=$(json_path "$resp" "error")
        printf "\n${RED}[X] 发送失败：%s${RESET}\n" "${err:-未知错误}"
        sleep 1.2
    fi
}

# ═══════════════════════════════════════════════════════════════
#  我的资料
# ═══════════════════════════════════════════════════════════════
screen_profile() {
    clear_screen
    msg_line info "正在加载资料..."

    local resp
    resp=$(api_get "action=get_user_info")
    if ! json_ok "$resp"; then
        clear_screen
        msg_line err "获取资料失败"
        msg_pause
        return 1
    fi

    local name role email created last
    name=$(json_path "$resp" "data.user.username")
    role=$(json_path "$resp" "data.user.role_name")
    email=$(json_path "$resp" "data.user.email")
    created=$(json_path "$resp" "data.user.created_at")
    last=$(json_path "$resp" "data.user.last_login")

    while true; do
        clear_screen
        local w=$(term_width)
        printf "${CYAN}"; repeat_ch '═' "$w"; printf "${RESET}\n\n"
        printf "  ${BOLD}我的资料${RESET}\n\n"
        printf "  用户名：   ${GREEN}%s${RESET}\n" "$name"
        printf "  权限：     ${YELLOW}%s${RESET}\n" "${role:-未知}"
        printf "  邮箱：     %s\n" "${email:-未设置}"
        printf "  注册时间： %s\n" "${created:-—}"
        printf "  最后登录： %s\n" "${last:-—}"
        printf "\n"
        printf "${CYAN}"; repeat_ch '═' "$w"; printf "${RESET}\n"

        if menu_select "操作" "" "修改密码" "修改邮箱" "返回"; then
            case $SELECTED_INDEX in
                0)
                    prompt_input "请输入新密码（至少 6 位）" "" "true"
                    local np="$PROMPT_RESULT"
                    if [ -n "$np" ] && [ ${#np} -ge 6 ]; then
                        local r
                        r=$(api_post "update_profile" "password=$(url_encode "$np")")
                        clear_screen
                        if json_ok "$r"; then
                            msg_line ok "密码修改成功"
                        else
                            msg_line err "修改失败：$(json_path "$r" "error")"
                        fi
                        msg_pause
                    fi
                    ;;
                1)
                    prompt_input "请输入新邮箱" "$email" "false"
                    local ne="$PROMPT_RESULT"
                    local r
                    r=$(api_post "update_profile" "email=$(url_encode "$ne")")
                    clear_screen
                    if json_ok "$r"; then
                        msg_line ok "邮箱修改成功"
                    else
                        msg_line err "修改失败：$(json_path "$r" "error")"
                    fi
                    msg_pause
                    ;;
                2) return 0 ;;
            esac
        else
            return 0
        fi
    done
}

# ═══════════════════════════════════════════════════════════════
#  主菜单
# ═══════════════════════════════════════════════════════════════
main_menu() {
    while true; do
        local -a items=()
        local sub=""

        if [ -z "$TOKEN" ]; then
            items=("登录" "注册" "退出")
        else
            sub="已登录：$USERNAME"
            items=("创建房间" "加入房间" "我的资料" "退出登录" "退出程序")
        fi

        if menu_select "聊天系统" "$sub" "${items[@]}"; then
            local idx=$SELECTED_INDEX
            if [ -z "$TOKEN" ]; then
                case $idx in
                    0) screen_login ;;
                    1) screen_register ;;
                    2) clear_screen; exit 0 ;;
                esac
            else
                case $idx in
                    0) screen_create_room ;;
                    1) screen_join_room ;;
                    2) screen_profile ;;
                    3)
                        clear_screen
                        TOKEN=""; USERNAME=""; USER_ID=""
                        rm -f "$CFG_FILE"
                        msg_line ok "已退出登录"
                        sleep 0.8
                        ;;
                    4) clear_screen; exit 0 ;;
                esac
            fi
        else
            clear_screen
            exit 0
        fi
    done
}

# ═══════════════════════════════════════════════════════════════
#  入口
# ═══════════════════════════════════════════════════════════════
main() {
    if [ $# -gt 0 ]; then
        case "$1" in
            --server) SERVER="$2"; shift 2 ;;
        esac
    fi
    splash
    main_menu
}

main "$@"