//! WebSocket handler for channel presence.
//!
//! Handles real-time position updates, emotion changes, and member synchronization.

use axum::{
    extract::{
        FromRef, Path, State,
        ws::{CloseFrame, Message, WebSocket, WebSocketUpgrade},
    },
    response::IntoResponse,
};
use dashmap::DashMap;
use futures::{SinkExt, StreamExt};
use sqlx::PgPool;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{broadcast, mpsc};
use uuid::Uuid;

use chattyness_db::{
    models::{ActionType, AvatarRenderData, ChannelMemberWithAvatar, EmotionState, ForcedAvatarReason, User},
    queries::{avatars, channel_members, inventory, loose_props, memberships, moderation, realm_avatars, realms, scenes, server_avatars, users},
    ws_messages::{close_codes, ClientMessage, DisconnectReason, ServerMessage, WsConfig},
};
use chattyness_error::AppError;
use chattyness_shared::WebSocketConfig;

use crate::auth::AuthUser;
use chrono::Duration as ChronoDuration;

/// Parse a duration string like "30m", "2h", "7d" into a chrono::Duration.
fn parse_duration(s: &str) -> Option<ChronoDuration> {
    let s = s.trim().to_lowercase();
    if s.is_empty() {
        return None;
    }

    let (num_str, unit) = s.split_at(s.len() - 1);
    let num: i64 = num_str.parse().ok()?;

    match unit {
        "m" => Some(ChronoDuration::minutes(num)),
        "h" => Some(ChronoDuration::hours(num)),
        "d" => Some(ChronoDuration::days(num)),
        _ => None,
    }
}

/// Channel state for broadcasting updates.
pub struct ChannelState {
    /// Broadcast sender for this channel.
    tx: broadcast::Sender<ServerMessage>,
}

/// Connection info for a connected user.
#[derive(Clone)]
pub struct UserConnection {
    /// Direct message sender for this user.
    pub direct_tx: mpsc::Sender<ServerMessage>,
    /// Realm the user is in.
    pub realm_id: Uuid,
    /// Channel (scene) the user is in.
    pub channel_id: Uuid,
    /// User's display name.
    pub display_name: String,
}

/// Global state for all WebSocket connections.
pub struct WebSocketState {
    /// Map of channel_id -> ChannelState.
    channels: DashMap<Uuid, Arc<ChannelState>>,
    /// Map of user_id -> UserConnection for direct message routing.
    users: DashMap<Uuid, UserConnection>,
}

impl Default for WebSocketState {
    fn default() -> Self {
        Self::new()
    }
}

impl WebSocketState {
    /// Create a new WebSocket state.
    pub fn new() -> Self {
        Self {
            channels: DashMap::new(),
            users: DashMap::new(),
        }
    }

    /// Get or create a channel state.
    fn get_or_create_channel(&self, channel_id: Uuid) -> Arc<ChannelState> {
        self.channels
            .entry(channel_id)
            .or_insert_with(|| {
                let (tx, _) = broadcast::channel(256);
                Arc::new(ChannelState { tx })
            })
            .clone()
    }

    /// Register a user connection for direct messaging.
    pub fn register_user(
        &self,
        user_id: Uuid,
        direct_tx: mpsc::Sender<ServerMessage>,
        realm_id: Uuid,
        channel_id: Uuid,
        display_name: String,
    ) {
        self.users.insert(
            user_id,
            UserConnection {
                direct_tx,
                realm_id,
                channel_id,
                display_name,
            },
        );
    }

    /// Unregister a user connection.
    pub fn unregister_user(&self, user_id: Uuid) {
        self.users.remove(&user_id);
    }

    /// Find a user by display name within a realm.
    pub fn find_user_by_display_name(
        &self,
        realm_id: Uuid,
        display_name: &str,
    ) -> Option<(Uuid, UserConnection)> {
        for entry in self.users.iter() {
            let (user_id, conn) = entry.pair();
            if conn.realm_id == realm_id && conn.display_name.eq_ignore_ascii_case(display_name) {
                return Some((*user_id, conn.clone()));
            }
        }
        None
    }

    /// Get a user's connection info.
    pub fn get_user(&self, user_id: Uuid) -> Option<UserConnection> {
        self.users.get(&user_id).map(|r| r.clone())
    }
}

/// WebSocket upgrade handler.
///
/// GET /api/realms/{slug}/channels/{channel_id}/ws
pub async fn ws_handler<S>(
    Path((slug, channel_id)): Path<(String, Uuid)>,
    auth_result: Result<AuthUser, crate::auth::AuthError>,
    State(pool): State<PgPool>,
    State(ws_state): State<Arc<WebSocketState>>,
    State(ws_config): State<WebSocketConfig>,
    ws: WebSocketUpgrade,
) -> Result<impl IntoResponse, AppError>
where
    S: Send + Sync,
    PgPool: FromRef<S>,
    Arc<WebSocketState>: FromRef<S>,
    WebSocketConfig: FromRef<S>,
{
    // Log auth result before checking
    #[cfg(debug_assertions)]
    tracing::debug!(
        "[WS] Connection attempt to {}/channels/{} - auth: {:?}",
        slug,
        channel_id,
        auth_result
            .as_ref()
            .map(|a| a.0.id)
            .map_err(|e| format!("{:?}", e))
    );

    let AuthUser(user) = auth_result.map_err(|e| {
        tracing::warn!(
            "[WS] Auth failed for {}/channels/{}: {:?}",
            slug,
            channel_id,
            e
        );
        AppError::from(e)
    })?;

    // Verify realm exists
    let realm = realms::get_realm_by_slug(&pool, &slug)
        .await?
        .ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", slug)))?;

    // Verify scene exists and belongs to this realm
    // Note: Using scene_id as channel_id since channel_members uses scenes directly
    let scene = scenes::get_scene_by_id(&pool, channel_id)
        .await?
        .ok_or_else(|| AppError::NotFound("Scene not found".to_string()))?;

    if scene.realm_id != realm.id {
        return Err(AppError::NotFound(
            "Scene not found in this realm".to_string(),
        ));
    }

    #[cfg(debug_assertions)]
    tracing::debug!(
        "[WS] Upgrading connection for user {} to channel {}",
        user.id,
        channel_id
    );

    Ok(ws.on_upgrade(move |socket| {
        handle_socket(
            socket, user, channel_id, realm.id, pool, ws_state, ws_config,
        )
    }))
}

/// Set RLS context on a database connection.
async fn set_rls_user_id(
    conn: &mut sqlx::pool::PoolConnection<sqlx::Postgres>,
    user_id: Uuid,
) -> Result<(), sqlx::Error> {
    sqlx::query("SELECT public.set_current_user_id($1)")
        .bind(user_id)
        .execute(&mut **conn)
        .await?;
    Ok(())
}

/// Handle an active WebSocket connection.
async fn handle_socket(
    socket: WebSocket,
    user: User,
    channel_id: Uuid,
    realm_id: Uuid,
    pool: PgPool,
    ws_state: Arc<WebSocketState>,
    ws_config: WebSocketConfig,
) {
    tracing::info!(
        "[WS] handle_socket started for user {} channel {} realm {}",
        user.id,
        channel_id,
        realm_id
    );

    // Acquire a dedicated connection for setup operations
    let mut conn = match pool.acquire().await {
        Ok(conn) => conn,
        Err(e) => {
            tracing::error!("[WS] Failed to acquire DB connection: {:?}", e);
            return;
        }
    };

    // Set RLS context on this dedicated connection
    if let Err(e) = set_rls_user_id(&mut conn, user.id).await {
        tracing::error!(
            "[WS] Failed to set RLS context for user {}: {:?}",
            user.id,
            e
        );
        return;
    }
    tracing::info!("[WS] RLS context set on dedicated connection");

    let channel_state = ws_state.get_or_create_channel(channel_id);
    let mut rx = channel_state.tx.subscribe();

    let (mut sender, mut receiver) = socket.split();

    // Ensure active avatar
    tracing::info!("[WS] Ensuring active avatar...");
    if let Err(e) = channel_members::ensure_active_avatar(&mut *conn, user.id, realm_id).await {
        tracing::error!("[WS] Failed to ensure avatar for user {}: {:?}", user.id, e);
        return;
    }
    tracing::info!("[WS] Avatar ensured");

    // Join the channel
    tracing::info!("[WS] Joining channel...");
    if let Err(e) = channel_members::join_channel(&mut *conn, channel_id, user.id).await {
        tracing::error!(
            "[WS] Failed to join channel {} for user {}: {:?}",
            channel_id,
            user.id,
            e
        );
        return;
    }
    tracing::info!("[WS] Channel joined");

    // Check if scene has forced avatar
    if let Ok(Some(scene_forced)) = realm_avatars::get_scene_forced_avatar(&pool, channel_id).await {
        tracing::info!("[WS] Scene has forced avatar: {:?}", scene_forced.forced_avatar_id);
        // Apply scene-forced avatar to user
        if let Err(e) = realm_avatars::apply_scene_forced_avatar(
            &pool,
            user.id,
            realm_id,
            scene_forced.forced_avatar_id,
        ).await {
            tracing::warn!("[WS] Failed to apply scene forced avatar: {:?}", e);
        }
    }

    // Get initial state
    let members = match get_members_with_avatars(&mut conn, &pool, channel_id, realm_id).await {
        Ok(m) => m,
        Err(e) => {
            tracing::error!("[WS] Failed to get members: {:?}", e);
            let _ = channel_members::leave_channel(&mut *conn, channel_id, user.id).await;
            return;
        }
    };

    let member = match channel_members::get_channel_member(
        &mut *conn, channel_id, user.id, realm_id,
    )
    .await
    {
        Ok(Some(m)) => m,
        Ok(None) => {
            tracing::error!("[WS] Failed to get member info for user {}", user.id);
            let _ = channel_members::leave_channel(&mut *conn, channel_id, user.id).await;
            return;
        }
        Err(e) => {
            tracing::error!("[WS] Error getting member info: {:?}", e);
            let _ = channel_members::leave_channel(&mut *conn, channel_id, user.id).await;
            return;
        }
    };

    // Send welcome message with config
    let welcome = ServerMessage::Welcome {
        member: member.clone(),
        members,
        config: WsConfig {
            ping_interval_secs: ws_config.client_ping_interval_secs,
        },
    };
    if let Ok(json) = serde_json::to_string(&welcome) {
        #[cfg(debug_assertions)]
        tracing::debug!("[WS->Client] {}", json);
        if sender.send(Message::Text(json.into())).await.is_err() {
            let _ = channel_members::leave_channel(&mut *conn, channel_id, user.id).await;
            return;
        }
    }

    // Send loose props sync
    match loose_props::list_channel_loose_props(&mut *conn, channel_id).await {
        Ok(props) => {
            let props_sync = ServerMessage::LoosePropsSync { props };
            if let Ok(json) = serde_json::to_string(&props_sync) {
                #[cfg(debug_assertions)]
                tracing::debug!("[WS->Client] {}", json);
                if sender.send(Message::Text(json.into())).await.is_err() {
                    let _ = channel_members::leave_channel(&mut *conn, channel_id, user.id).await;
                    return;
                }
            }
        }
        Err(e) => {
            tracing::warn!("[WS] Failed to get loose props: {:?}", e);
        }
    }

    // Save member display_name for user registration (before member is moved)
    let member_display_name = member.display_name.clone();

    // Broadcast join to others
    // Use effective avatar resolution to handle priority chain:
    // forced > custom > selected realm > selected server > realm default > server default
    let avatar = avatars::get_effective_avatar_render_data(&pool, user.id, realm_id)
        .await
        .ok()
        .flatten()
        .map(|(render_data, _source)| render_data)
        .unwrap_or_default();
    let join_msg = ServerMessage::MemberJoined {
        member: ChannelMemberWithAvatar { member, avatar },
    };
    let _ = channel_state.tx.send(join_msg);

    let user_id = user.id;
    let mut is_guest = user.is_guest();
    let tx = channel_state.tx.clone();

    // Acquire a second dedicated connection for the receive task
    // This connection needs its own RLS context
    let mut recv_conn = match pool.acquire().await {
        Ok(c) => c,
        Err(e) => {
            tracing::error!("[WS] Failed to acquire recv connection: {:?}", e);
            let _ = channel_members::leave_channel(&mut *conn, channel_id, user_id).await;
            return;
        }
    };
    if let Err(e) = set_rls_user_id(&mut recv_conn, user_id).await {
        tracing::error!("[WS] Failed to set RLS on recv connection: {:?}", e);
        let _ = channel_members::leave_channel(&mut *conn, channel_id, user_id).await;
        return;
    }

    // Drop the setup connection - we'll use recv_conn for the receive task
    // and pool for cleanup (leave_channel needs user_id match anyway)
    drop(conn);

    // Channel for sending direct messages (Pong, whispers) to client
    let (direct_tx, mut direct_rx) = mpsc::channel::<ServerMessage>(16);

    // Register user for direct message routing
    ws_state.register_user(
        user_id,
        direct_tx.clone(),
        realm_id,
        channel_id,
        member_display_name,
    );

    // Clone ws_state for use in recv_task
    let ws_state_for_recv = ws_state.clone();

    // Clone pool for use in recv_task (for teleport queries)
    let pool_for_recv = pool.clone();

    // Create recv timeout from config
    let recv_timeout = Duration::from_secs(ws_config.recv_timeout_secs);

    // Channel for sending close frame requests from recv_task to send_task
    let (close_tx, mut close_rx) = mpsc::channel::<(u16, String)>(1);

    // Spawn task to handle incoming messages from client
    let close_tx_for_recv = close_tx.clone();
    let recv_task = tokio::spawn(async move {
        let close_tx = close_tx_for_recv;
        let pool = pool_for_recv;
        let ws_state = ws_state_for_recv;
        let mut disconnect_reason = DisconnectReason::Graceful;

        loop {
            // Use timeout to detect connection loss
            let msg_result = tokio::time::timeout(recv_timeout, receiver.next()).await;

            match msg_result {
                Ok(Some(Ok(msg))) => {
                    match msg {
                        Message::Text(text) => {
                            #[cfg(debug_assertions)]
                            tracing::debug!("[WS<-Client] {}", text);

                            let Ok(client_msg) = serde_json::from_str::<ClientMessage>(&text)
                            else {
                                continue;
                            };

                            match client_msg {
                                ClientMessage::UpdatePosition { x, y } => {
                                    if let Err(e) = channel_members::update_position(
                                        &mut *recv_conn,
                                        channel_id,
                                        user_id,
                                        x,
                                        y,
                                    )
                                    .await
                                    {
                                        #[cfg(debug_assertions)]
                                        tracing::error!("[WS] Position update failed: {:?}", e);
                                        continue;
                                    }
                                    let _ = tx.send(ServerMessage::PositionUpdated {
                                        user_id,
                                        x,
                                        y,
                                    });
                                }
                                ClientMessage::UpdateEmotion { emotion } => {
                                    // Parse emotion name to EmotionState
                                    let emotion_state = match emotion.parse::<EmotionState>() {
                                        Ok(e) => e,
                                        Err(_) => {
                                            #[cfg(debug_assertions)]
                                            tracing::warn!(
                                                "[WS] Invalid emotion name: {}",
                                                emotion
                                            );
                                            continue;
                                        }
                                    };
                                    let emotion_layer = match avatars::set_emotion(
                                        &mut *recv_conn,
                                        &pool,
                                        user_id,
                                        realm_id,
                                        emotion_state,
                                    )
                                    .await
                                    {
                                        Ok(layer) => layer,
                                        Err(e) => {
                                            #[cfg(debug_assertions)]
                                            tracing::error!("[WS] Emotion update failed: {:?}", e);
                                            continue;
                                        }
                                    };
                                    let _ = tx.send(ServerMessage::EmotionUpdated {
                                        user_id,
                                        emotion,
                                        emotion_layer,
                                    });
                                }
                                ClientMessage::Ping => {
                                    // Update last_moved_at to keep member alive for cleanup
                                    let _ = channel_members::touch_member(
                                        &mut *recv_conn,
                                        channel_id,
                                        user_id,
                                    )
                                    .await;
                                    // Respond with pong directly (not broadcast)
                                    let _ = direct_tx.send(ServerMessage::Pong).await;
                                }
                                ClientMessage::SendChatMessage {
                                    content,
                                    target_display_name,
                                } => {
                                    // Validate message
                                    if content.is_empty() || content.len() > 500 {
                                        continue;
                                    }

                                    // Get member's current position and emotion
                                    let member_info = channel_members::get_channel_member(
                                        &mut *recv_conn,
                                        channel_id,
                                        user_id,
                                        realm_id,
                                    )
                                    .await;

                                    if let Ok(Some(member)) = member_info {
                                        // Convert emotion index to name
                                        let emotion_name =
                                            EmotionState::from_index(member.current_emotion as u8)
                                                .map(|e| e.to_string())
                                                .unwrap_or_else(|| "neutral".to_string());

                                        // Handle whisper (direct message) vs broadcast
                                        if let Some(target_name) = target_display_name {
                                            // Check if guest is trying to whisper
                                            if is_guest {
                                                let _ = direct_tx
                                                    .send(ServerMessage::Error {
                                                        code: "GUEST_FEATURE_DISABLED".to_string(),
                                                        message: "Private messaging is disabled for guests, please register first.".to_string(),
                                                    })
                                                    .await;
                                                continue;
                                            }

                                            // Whisper: send directly to target user
                                            if let Some((_target_user_id, target_conn)) = ws_state
                                                .find_user_by_display_name(realm_id, &target_name)
                                            {
                                                // Determine if same scene
                                                let is_same_scene =
                                                    target_conn.channel_id == channel_id;

                                                let msg = ServerMessage::ChatMessageReceived {
                                                    message_id: Uuid::new_v4(),
                                                    user_id,
                                                    display_name: member.display_name.clone(),
                                                    content: content.clone(),
                                                    emotion: emotion_name.clone(),
                                                    x: member.position_x,
                                                    y: member.position_y,
                                                    timestamp: chrono::Utc::now()
                                                        .timestamp_millis(),
                                                    is_whisper: true,
                                                    is_same_scene,
                                                };

                                                // Send to target user
                                                let _ =
                                                    target_conn.direct_tx.send(msg.clone()).await;

                                                // Also send back to sender (so they see their own whisper)
                                                // For sender, is_same_scene is always true (they see it as a bubble)
                                                let sender_msg =
                                                    ServerMessage::ChatMessageReceived {
                                                        message_id: Uuid::new_v4(),
                                                        user_id,
                                                        display_name: member.display_name.clone(),
                                                        content,
                                                        emotion: emotion_name,
                                                        x: member.position_x,
                                                        y: member.position_y,
                                                        timestamp: chrono::Utc::now()
                                                            .timestamp_millis(),
                                                        is_whisper: true,
                                                        is_same_scene: true, // Sender always sees as bubble
                                                    };
                                                let _ = direct_tx.send(sender_msg).await;

                                                #[cfg(debug_assertions)]
                                                tracing::debug!(
                                                    "[WS] Whisper from {} to {} (same_scene={})",
                                                    member.display_name,
                                                    target_name,
                                                    is_same_scene
                                                );
                                            } else {
                                                // Target user not found - send error
                                                let _ = direct_tx.send(ServerMessage::Error {
                                                    code: "WHISPER_TARGET_NOT_FOUND".to_string(),
                                                    message: format!("User '{}' is not online or not in this realm", target_name),
                                                }).await;
                                            }
                                        } else {
                                            // Broadcast: send to all users in the channel
                                            let msg = ServerMessage::ChatMessageReceived {
                                                message_id: Uuid::new_v4(),
                                                user_id,
                                                display_name: member.display_name.clone(),
                                                content,
                                                emotion: emotion_name,
                                                x: member.position_x,
                                                y: member.position_y,
                                                timestamp: chrono::Utc::now().timestamp_millis(),
                                                is_whisper: false,
                                                is_same_scene: true,
                                            };
                                            let _ = tx.send(msg);
                                        }
                                    }
                                }
                                ClientMessage::DropProp { inventory_item_id } => {
                                    // Ensure instance exists for this scene (required for loose_props FK)
                                    // In this system, channel_id = scene_id
                                    if let Err(e) = loose_props::ensure_scene_instance(
                                        &mut *recv_conn,
                                        channel_id,
                                    )
                                    .await
                                    {
                                        tracing::error!(
                                            "[WS] Failed to ensure scene instance: {:?}",
                                            e
                                        );
                                    }

                                    // Get user's current position for random offset
                                    let member_info = channel_members::get_channel_member(
                                        &mut *recv_conn,
                                        channel_id,
                                        user_id,
                                        realm_id,
                                    )
                                    .await;

                                    if let Ok(Some(member)) = member_info {
                                        // Generate random offset (within ~50 pixels)
                                        let offset_x = (rand::random::<f64>() - 0.5) * 100.0;
                                        let offset_y = (rand::random::<f64>() - 0.5) * 100.0;
                                        let pos_x = member.position_x + offset_x;
                                        let pos_y = member.position_y + offset_y;

                                        match loose_props::drop_prop_to_canvas(
                                            &mut *recv_conn,
                                            inventory_item_id,
                                            user_id,
                                            channel_id,
                                            pos_x,
                                            pos_y,
                                        )
                                        .await
                                        {
                                            Ok(prop) => {
                                                #[cfg(debug_assertions)]
                                                tracing::debug!(
                                                    "[WS] User {} dropped prop {} at ({}, {})",
                                                    user_id,
                                                    prop.id,
                                                    pos_x,
                                                    pos_y
                                                );
                                                let _ =
                                                    tx.send(ServerMessage::PropDropped { prop });
                                            }
                                            Err(e) => {
                                                tracing::error!("[WS] Drop prop failed: {:?}", e);
                                                let (code, message) = match &e {
                                                    chattyness_error::AppError::Forbidden(msg) => (
                                                        "PROP_NOT_DROPPABLE".to_string(),
                                                        msg.clone(),
                                                    ),
                                                    chattyness_error::AppError::NotFound(msg) => {
                                                        ("PROP_NOT_FOUND".to_string(), msg.clone())
                                                    }
                                                    _ => (
                                                        "DROP_FAILED".to_string(),
                                                        format!("{:?}", e),
                                                    ),
                                                };
                                                let _ =
                                                    tx.send(ServerMessage::Error { code, message });
                                            }
                                        }
                                    }
                                }
                                ClientMessage::DeleteProp { inventory_item_id } => {
                                    match inventory::drop_inventory_item(
                                        &mut *recv_conn,
                                        user_id,
                                        inventory_item_id,
                                    )
                                    .await
                                    {
                                        Ok(()) => {
                                            let _ = direct_tx.send(ServerMessage::PropDeleted {
                                                inventory_item_id,
                                            }).await;
                                        }
                                        Err(e) => {
                                            let (code, message) = match &e {
                                                chattyness_error::AppError::Forbidden(msg) => (
                                                    "PROP_NOT_DELETABLE".to_string(),
                                                    msg.clone(),
                                                ),
                                                chattyness_error::AppError::NotFound(msg) => {
                                                    ("PROP_NOT_FOUND".to_string(), msg.clone())
                                                }
                                                _ => (
                                                    "DELETE_FAILED".to_string(),
                                                    format!("{:?}", e),
                                                ),
                                            };
                                            let _ = direct_tx.send(ServerMessage::Error { code, message }).await;
                                        }
                                    }
                                }
                                ClientMessage::PickUpProp { loose_prop_id } => {
                                    // Check if prop is locked
                                    let is_mod = match memberships::is_moderator(&pool, user_id, realm_id).await {
                                        Ok(result) => result,
                                        Err(e) => {
                                            tracing::error!("[WS] Failed to check moderator status: {:?}", e);
                                            false
                                        }
                                    };

                                    if let Ok(Some(prop)) = loose_props::get_loose_prop_by_id(&mut *recv_conn, loose_prop_id).await {
                                        if prop.is_locked && !is_mod {
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "PROP_LOCKED".to_string(),
                                                message: "This prop is locked and cannot be picked up".to_string(),
                                            }).await;
                                            continue;
                                        }
                                    }

                                    match loose_props::pick_up_loose_prop(
                                        &mut *recv_conn,
                                        loose_prop_id,
                                        user_id,
                                    )
                                    .await
                                    {
                                        Ok(_inventory_item) => {
                                            #[cfg(debug_assertions)]
                                            tracing::debug!(
                                                "[WS] User {} picked up prop {}",
                                                user_id,
                                                loose_prop_id
                                            );
                                            let _ = tx.send(ServerMessage::PropPickedUp {
                                                prop_id: loose_prop_id,
                                                picked_up_by_user_id: user_id,
                                            });
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] Pick up prop failed: {:?}", e);
                                            let _ = tx.send(ServerMessage::Error {
                                                code: "PICKUP_FAILED".to_string(),
                                                message: format!("{:?}", e),
                                            });
                                        }
                                    }
                                }
                                ClientMessage::SyncAvatar => {
                                    // Fetch current effective avatar from database and broadcast to channel
                                    // Uses the priority chain: forced > custom > selected realm > selected server > realm default > server default
                                    match avatars::get_effective_avatar_render_data(
                                        &pool,
                                        user_id,
                                        realm_id,
                                    )
                                    .await
                                    {
                                        Ok(Some((render_data, _source))) => {
                                            #[cfg(debug_assertions)]
                                            tracing::debug!(
                                                "[WS] User {} syncing avatar to channel",
                                                user_id
                                            );
                                            let _ = tx.send(ServerMessage::AvatarUpdated {
                                                user_id,
                                                avatar: render_data,
                                            });
                                        }
                                        Ok(None) => {
                                            #[cfg(debug_assertions)]
                                            tracing::warn!(
                                                "[WS] No avatar found for user {} to sync",
                                                user_id
                                            );
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] Avatar sync failed: {:?}", e);
                                        }
                                    }
                                }
                                ClientMessage::Teleport { scene_id } => {
                                    // Validate teleport permission and scene
                                    // 1. Check realm allows user teleport
                                    let realm = match realms::get_realm_by_id(
                                        &pool,
                                        realm_id,
                                    )
                                    .await
                                    {
                                        Ok(Some(r)) => r,
                                        Ok(None) => {
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "REALM_NOT_FOUND".to_string(),
                                                message: "Realm not found".to_string(),
                                            }).await;
                                            continue;
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] Teleport realm lookup failed: {:?}", e);
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "TELEPORT_FAILED".to_string(),
                                                message: "Failed to verify teleport permission".to_string(),
                                            }).await;
                                            continue;
                                        }
                                    };

                                    if !realm.allow_user_teleport {
                                        let _ = direct_tx.send(ServerMessage::Error {
                                            code: "TELEPORT_DISABLED".to_string(),
                                            message: "Teleporting is not enabled for this realm".to_string(),
                                        }).await;
                                        continue;
                                    }

                                    // 2. Validate scene exists, belongs to realm, and is not hidden
                                    let scene = match scenes::get_scene_by_id(&pool, scene_id).await {
                                        Ok(Some(s)) => s,
                                        Ok(None) => {
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "SCENE_NOT_FOUND".to_string(),
                                                message: "Scene not found".to_string(),
                                            }).await;
                                            continue;
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] Teleport scene lookup failed: {:?}", e);
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "TELEPORT_FAILED".to_string(),
                                                message: "Failed to verify scene".to_string(),
                                            }).await;
                                            continue;
                                        }
                                    };

                                    if scene.realm_id != realm_id {
                                        let _ = direct_tx.send(ServerMessage::Error {
                                            code: "SCENE_NOT_IN_REALM".to_string(),
                                            message: "Scene does not belong to this realm".to_string(),
                                        }).await;
                                        continue;
                                    }

                                    if scene.is_hidden {
                                        let _ = direct_tx.send(ServerMessage::Error {
                                            code: "SCENE_HIDDEN".to_string(),
                                            message: "Cannot teleport to a hidden scene".to_string(),
                                        }).await;
                                        continue;
                                    }

                                    // 3. Send approval - client will disconnect and reconnect
                                    #[cfg(debug_assertions)]
                                    tracing::debug!(
                                        "[WS] User {} teleporting to scene {} ({})",
                                        user_id,
                                        scene.name,
                                        scene.slug
                                    );
                                    let _ = direct_tx.send(ServerMessage::TeleportApproved {
                                        scene_id: scene.id,
                                        scene_slug: scene.slug,
                                    }).await;
                                }
                                ClientMessage::ModCommand { subcommand, args } => {
                                    // Check if user is a moderator
                                    let is_mod = match memberships::is_moderator(&pool, user_id, realm_id).await {
                                        Ok(result) => result,
                                        Err(e) => {
                                            tracing::error!("[WS] Failed to check moderator status: {:?}", e);
                                            false
                                        }
                                    };

                                    if !is_mod {
                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                            success: false,
                                            message: "You do not have moderator permissions".to_string(),
                                        }).await;
                                        continue;
                                    }

                                    // Get moderator's current scene info and display name
                                    let mod_member = match channel_members::get_channel_member(
                                        &mut *recv_conn,
                                        channel_id,
                                        user_id,
                                        realm_id,
                                    ).await {
                                        Ok(Some(m)) => m,
                                        Ok(None) | Err(_) => {
                                            let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                success: false,
                                                message: "Failed to get moderator info".to_string(),
                                            }).await;
                                            continue;
                                        }
                                    };

                                    // Get moderator's current scene details
                                    let mod_scene = match scenes::get_scene_by_id(&pool, channel_id).await {
                                        Ok(Some(s)) => s,
                                        Ok(None) | Err(_) => {
                                            let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                success: false,
                                                message: "Failed to get scene info".to_string(),
                                            }).await;
                                            continue;
                                        }
                                    };

                                    match subcommand.as_str() {
                                        "summon" => {
                                            if args.is_empty() {
                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: false,
                                                    message: "Usage: /mod summon [nick|*]".to_string(),
                                                }).await;
                                                continue;
                                            }

                                            let target = &args[0];

                                            if target == "*" {
                                                // Summon all users in the realm
                                                let mut summoned_count = 0;
                                                let mut target_ids = Vec::new();

                                                // Iterate all connected users in this realm
                                                for entry in ws_state.users.iter() {
                                                    let (target_user_id, target_conn) = entry.pair();
                                                    if target_conn.realm_id == realm_id && *target_user_id != user_id {
                                                        // Send Summoned message to each user
                                                        let summon_msg = ServerMessage::Summoned {
                                                            scene_id: mod_scene.id,
                                                            scene_slug: mod_scene.slug.clone(),
                                                            summoned_by: mod_member.display_name.clone(),
                                                        };
                                                        if target_conn.direct_tx.send(summon_msg).await.is_ok() {
                                                            summoned_count += 1;
                                                            target_ids.push(*target_user_id);
                                                        }
                                                    }
                                                }

                                                // Log the action
                                                let metadata = serde_json::json!({
                                                    "scene_id": mod_scene.id,
                                                    "scene_slug": mod_scene.slug,
                                                    "summoned_count": summoned_count,
                                                });
                                                let _ = moderation::log_moderation_action(
                                                    &pool,
                                                    realm_id,
                                                    user_id,
                                                    ActionType::SummonAll,
                                                    None,
                                                    &format!("Summoned {} users to scene {}", summoned_count, mod_scene.name),
                                                    metadata,
                                                ).await;

                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: true,
                                                    message: format!("Summoned {} users to {}", summoned_count, mod_scene.name),
                                                }).await;
                                            } else {
                                                // Summon specific user by display name
                                                if let Some((target_user_id, target_conn)) = ws_state
                                                    .find_user_by_display_name(realm_id, target)
                                                {
                                                    if target_user_id == user_id {
                                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                            success: false,
                                                            message: "You cannot summon yourself".to_string(),
                                                        }).await;
                                                        continue;
                                                    }

                                                    // Send Summoned message to target
                                                    let summon_msg = ServerMessage::Summoned {
                                                        scene_id: mod_scene.id,
                                                        scene_slug: mod_scene.slug.clone(),
                                                        summoned_by: mod_member.display_name.clone(),
                                                    };
                                                    if target_conn.direct_tx.send(summon_msg).await.is_ok() {
                                                        // Log the action
                                                        let metadata = serde_json::json!({
                                                            "scene_id": mod_scene.id,
                                                            "scene_slug": mod_scene.slug,
                                                            "target_display_name": target,
                                                        });
                                                        let _ = moderation::log_moderation_action(
                                                            &pool,
                                                            realm_id,
                                                            user_id,
                                                            ActionType::Summon,
                                                            Some(target_user_id),
                                                            &format!("Summoned {} to scene {}", target, mod_scene.name),
                                                            metadata,
                                                        ).await;

                                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                            success: true,
                                                            message: format!("Summoned {} to {}", target, mod_scene.name),
                                                        }).await;
                                                    } else {
                                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                            success: false,
                                                            message: format!("Failed to send summon to {}", target),
                                                        }).await;
                                                    }
                                                } else {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: format!("User '{}' is not online in this realm", target),
                                                    }).await;
                                                }
                                            }
                                        }
                                        "teleport" => {
                                            if args.len() < 2 {
                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: false,
                                                    message: "Usage: /mod teleport [nick] [slug]".to_string(),
                                                }).await;
                                                continue;
                                            }

                                            let target_nick = &args[0];
                                            let target_slug = &args[1];

                                            // Look up the target scene by slug
                                            let target_scene = match scenes::get_scene_by_slug(&pool, realm_id, target_slug).await {
                                                Ok(Some(s)) => s,
                                                Ok(None) => {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: format!("Scene '{}' not found", target_slug),
                                                    }).await;
                                                    continue;
                                                }
                                                Err(_) => {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: "Failed to look up scene".to_string(),
                                                    }).await;
                                                    continue;
                                                }
                                            };

                                            // Find target user by display name
                                            if let Some((target_user_id, target_conn)) = ws_state
                                                .find_user_by_display_name(realm_id, target_nick)
                                            {
                                                if target_user_id == user_id {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: "You cannot teleport yourself".to_string(),
                                                    }).await;
                                                    continue;
                                                }

                                                // Send Summoned message to target user with the specified scene
                                                let teleport_msg = ServerMessage::Summoned {
                                                    scene_id: target_scene.id,
                                                    scene_slug: target_scene.slug.clone(),
                                                    summoned_by: mod_member.display_name.clone(),
                                                };
                                                if target_conn.direct_tx.send(teleport_msg).await.is_ok() {
                                                    // Log the action
                                                    let metadata = serde_json::json!({
                                                        "scene_id": target_scene.id,
                                                        "scene_slug": target_scene.slug,
                                                        "target_display_name": target_nick,
                                                    });
                                                    let _ = moderation::log_moderation_action(
                                                        &pool,
                                                        realm_id,
                                                        user_id,
                                                        ActionType::Teleport,
                                                        Some(target_user_id),
                                                        &format!("Teleported {} to scene {}", target_nick, target_scene.name),
                                                        metadata,
                                                    ).await;

                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: true,
                                                        message: format!("Teleported {} to {}", target_nick, target_scene.name),
                                                    }).await;
                                                } else {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: format!("Failed to send teleport to {}", target_nick),
                                                    }).await;
                                                }
                                            } else {
                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: false,
                                                    message: format!("User '{}' is not online in this realm", target_nick),
                                                }).await;
                                            }
                                        }
                                        "dress" => {
                                            // /mod dress [nick] [avatar-slug] [duration?]
                                            // Duration format: 30m, 2h, 7d (minutes/hours/days)
                                            if args.len() < 2 {
                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: false,
                                                    message: "Usage: /mod dress [nick] [avatar-slug] [duration?]".to_string(),
                                                }).await;
                                                continue;
                                            }

                                            let target_nick = &args[0];
                                            let avatar_slug = &args[1];
                                            let duration_str = args.get(2).map(|s| s.as_str());

                                            // Parse duration if provided
                                            let duration = if let Some(dur_str) = duration_str {
                                                match parse_duration(dur_str) {
                                                    Some(d) => Some(d),
                                                    None => {
                                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                            success: false,
                                                            message: "Invalid duration format. Use: 30m, 2h, 7d".to_string(),
                                                        }).await;
                                                        continue;
                                                    }
                                                }
                                            } else {
                                                None // Permanent until undressed
                                            };

                                            // Find target user
                                            if let Some((target_user_id, target_conn)) = ws_state
                                                .find_user_by_display_name(realm_id, target_nick)
                                            {
                                                // Try realm avatars first, then server avatars
                                                let avatar_result = realm_avatars::get_realm_avatar_by_slug(&pool, realm_id, avatar_slug).await;
                                                let (avatar_render_data, avatar_id, source) = match avatar_result {
                                                    Ok(Some(realm_avatar)) => {
                                                        // Resolve realm avatar to render data
                                                        match realm_avatars::resolve_realm_avatar_to_render_data(&pool, &realm_avatar, EmotionState::default()).await {
                                                            Ok(render_data) => (render_data, realm_avatar.id, "realm"),
                                                            Err(e) => {
                                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                                    success: false,
                                                                    message: format!("Failed to resolve avatar: {:?}", e),
                                                                }).await;
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    Ok(None) => {
                                                        // Try server avatars
                                                        match server_avatars::get_server_avatar_by_slug(&pool, avatar_slug).await {
                                                            Ok(Some(server_avatar)) => {
                                                                match server_avatars::resolve_server_avatar_to_render_data(&pool, &server_avatar, EmotionState::default()).await {
                                                                    Ok(render_data) => (render_data, server_avatar.id, "server"),
                                                                    Err(e) => {
                                                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                                            success: false,
                                                                            message: format!("Failed to resolve avatar: {:?}", e),
                                                                        }).await;
                                                                        continue;
                                                                    }
                                                                }
                                                            }
                                                            Ok(None) => {
                                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                                    success: false,
                                                                    message: format!("Avatar '{}' not found", avatar_slug),
                                                                }).await;
                                                                continue;
                                                            }
                                                            Err(e) => {
                                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                                    success: false,
                                                                    message: format!("Failed to lookup avatar: {:?}", e),
                                                                }).await;
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    Err(e) => {
                                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                            success: false,
                                                            message: format!("Failed to lookup avatar: {:?}", e),
                                                        }).await;
                                                        continue;
                                                    }
                                                };

                                                // Acquire connection and set RLS context for the update
                                                let mut rls_conn = match pool.acquire().await {
                                                    Ok(c) => c,
                                                    Err(e) => {
                                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                            success: false,
                                                            message: format!("Database connection error: {:?}", e),
                                                        }).await;
                                                        continue;
                                                    }
                                                };
                                                if let Err(e) = set_rls_user_id(&mut rls_conn, user_id).await {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: format!("Failed to set RLS context: {:?}", e),
                                                    }).await;
                                                    continue;
                                                }

                                                // Apply forced avatar using connection with RLS context
                                                let apply_result = if source == "server" {
                                                    server_avatars::apply_forced_server_avatar(
                                                        &mut *rls_conn,
                                                        target_user_id,
                                                        realm_id,
                                                        avatar_id,
                                                        Some(user_id),
                                                        duration,
                                                    ).await
                                                } else {
                                                    realm_avatars::apply_forced_realm_avatar(
                                                        &mut *rls_conn,
                                                        target_user_id,
                                                        realm_id,
                                                        avatar_id,
                                                        Some(user_id),
                                                        duration,
                                                    ).await
                                                };

                                                if let Err(e) = apply_result {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: format!("Failed to apply forced avatar: {:?}", e),
                                                    }).await;
                                                    continue;
                                                }

                                                // Log moderation action
                                                let metadata = serde_json::json!({
                                                    "avatar_slug": avatar_slug,
                                                    "avatar_id": avatar_id.to_string(),
                                                    "source": source,
                                                    "duration": duration_str,
                                                });
                                                let _ = moderation::log_moderation_action(
                                                    &pool,
                                                    realm_id,
                                                    user_id,
                                                    ActionType::DressUser,
                                                    Some(target_user_id),
                                                    &format!("Forced {} to wear avatar '{}'", target_nick, avatar_slug),
                                                    metadata,
                                                ).await;

                                                // Send AvatarForced to target user
                                                let _ = target_conn.direct_tx.send(ServerMessage::AvatarForced {
                                                    user_id: target_user_id,
                                                    avatar: avatar_render_data.clone(),
                                                    reason: ForcedAvatarReason::ModCommand,
                                                    forced_by: Some(mod_member.display_name.clone()),
                                                }).await;

                                                // Broadcast avatar update to channel
                                                let _ = tx.send(ServerMessage::AvatarUpdated {
                                                    user_id: target_user_id,
                                                    avatar: avatar_render_data,
                                                });

                                                let duration_msg = match duration_str {
                                                    Some(d) => format!(" for {}", d),
                                                    None => " permanently".to_string(),
                                                };
                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: true,
                                                    message: format!("Forced {} to wear '{}'{}", target_nick, avatar_slug, duration_msg),
                                                }).await;
                                            } else {
                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: false,
                                                    message: format!("User '{}' is not online in this realm", target_nick),
                                                }).await;
                                            }
                                        }
                                        "undress" => {
                                            // /mod undress [nick]
                                            if args.is_empty() {
                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: false,
                                                    message: "Usage: /mod undress [nick]".to_string(),
                                                }).await;
                                                continue;
                                            }

                                            let target_nick = &args[0];

                                            // Find target user
                                            if let Some((target_user_id, target_conn)) = ws_state
                                                .find_user_by_display_name(realm_id, target_nick)
                                            {
                                                // Acquire connection and set RLS context for the update
                                                let mut rls_conn = match pool.acquire().await {
                                                    Ok(c) => c,
                                                    Err(e) => {
                                                        let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                            success: false,
                                                            message: format!("Database connection error: {:?}", e),
                                                        }).await;
                                                        continue;
                                                    }
                                                };
                                                if let Err(e) = set_rls_user_id(&mut rls_conn, user_id).await {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: format!("Failed to set RLS context: {:?}", e),
                                                    }).await;
                                                    continue;
                                                }

                                                // Clear forced avatar using connection with RLS context
                                                if let Err(e) = server_avatars::clear_forced_avatar(&mut *rls_conn, target_user_id, realm_id).await {
                                                    let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                        success: false,
                                                        message: format!("Failed to clear forced avatar: {:?}", e),
                                                    }).await;
                                                    continue;
                                                }

                                                // Get the user's original avatar
                                                let original_avatar = match avatars::get_avatar_with_paths(&pool, target_user_id, realm_id).await {
                                                    Ok(Some(avatar)) => avatar.to_render_data(),
                                                    Ok(None) => AvatarRenderData::default(),
                                                    Err(_) => AvatarRenderData::default(),
                                                };

                                                // Log moderation action
                                                let metadata = serde_json::json!({});
                                                let _ = moderation::log_moderation_action(
                                                    &pool,
                                                    realm_id,
                                                    user_id,
                                                    ActionType::UndressUser,
                                                    Some(target_user_id),
                                                    &format!("Cleared forced avatar for {}", target_nick),
                                                    metadata,
                                                ).await;

                                                // Send AvatarCleared to target user
                                                let _ = target_conn.direct_tx.send(ServerMessage::AvatarCleared {
                                                    user_id: target_user_id,
                                                    avatar: original_avatar.clone(),
                                                    cleared_by: Some(mod_member.display_name.clone()),
                                                }).await;

                                                // Broadcast avatar update to channel
                                                let _ = tx.send(ServerMessage::AvatarUpdated {
                                                    user_id: target_user_id,
                                                    avatar: original_avatar,
                                                });

                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: true,
                                                    message: format!("Cleared forced avatar for {}", target_nick),
                                                }).await;
                                            } else {
                                                let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                    success: false,
                                                    message: format!("User '{}' is not online in this realm", target_nick),
                                                }).await;
                                            }
                                        }
                                        _ => {
                                            let _ = direct_tx.send(ServerMessage::ModCommandResult {
                                                success: false,
                                                message: format!("Unknown mod command: {}", subcommand),
                                            }).await;
                                        }
                                    }
                                }
                                ClientMessage::RefreshIdentity => {
                                    // Fetch updated user info from database
                                    match users::get_user_by_id(&pool, user_id).await {
                                        Ok(Some(updated_user)) => {
                                            // Update the is_guest flag - critical for allowing
                                            // newly registered users to send whispers
                                            is_guest = updated_user.is_guest();
                                            let display_name = updated_user.display_name.clone();

                                            tracing::info!(
                                                "[WS] User {} refreshed identity: display_name={}, is_guest={}",
                                                user_id,
                                                display_name,
                                                is_guest
                                            );

                                            // Update WebSocket state with the new display name
                                            // This is critical for whispers and mod commands to find
                                            // the user by their new name after registration
                                            if let Some(conn) = ws_state.get_user(user_id) {
                                                ws_state.register_user(
                                                    user_id,
                                                    conn.direct_tx.clone(),
                                                    conn.realm_id,
                                                    conn.channel_id,
                                                    display_name.clone(),
                                                );
                                            }

                                            // Broadcast identity update to all channel members
                                            let _ = tx.send(ServerMessage::MemberIdentityUpdated {
                                                user_id,
                                                display_name,
                                                is_guest,
                                            });
                                        }
                                        Ok(None) => {
                                            tracing::warn!("[WS] RefreshIdentity: user {} not found", user_id);
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] RefreshIdentity failed for user {}: {:?}", user_id, e);
                                        }
                                    }
                                }
                                ClientMessage::UpdateProp { loose_prop_id, scale } => {
                                    // Check if user is a moderator
                                    let is_mod = match memberships::is_moderator(&pool, user_id, realm_id).await {
                                        Ok(result) => result,
                                        Err(e) => {
                                            tracing::error!("[WS] Failed to check moderator status: {:?}", e);
                                            false
                                        }
                                    };

                                    if !is_mod {
                                        let _ = direct_tx.send(ServerMessage::Error {
                                            code: "NOT_MODERATOR".to_string(),
                                            message: "You do not have permission to update props".to_string(),
                                        }).await;
                                        continue;
                                    }

                                    // Check if prop is locked (for non-mods)
                                    if let Ok(Some(prop)) = loose_props::get_loose_prop_by_id(&mut *recv_conn, loose_prop_id).await {
                                        if prop.is_locked && !is_mod {
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "PROP_LOCKED".to_string(),
                                                message: "This prop is locked and cannot be modified".to_string(),
                                            }).await;
                                            continue;
                                        }
                                    }

                                    // Update the prop scale
                                    match loose_props::update_loose_prop_scale(
                                        &mut *recv_conn,
                                        loose_prop_id,
                                        scale,
                                    ).await {
                                        Ok(updated_prop) => {
                                            #[cfg(debug_assertions)]
                                            tracing::debug!(
                                                "[WS] User {} updated prop {} scale to {}",
                                                user_id,
                                                loose_prop_id,
                                                scale
                                            );
                                            // Broadcast the updated prop to all users in the channel
                                            let _ = tx.send(ServerMessage::PropRefresh { prop: updated_prop });
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] Update prop failed: {:?}", e);
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "UPDATE_PROP_FAILED".to_string(),
                                                message: format!("{:?}", e),
                                            }).await;
                                        }
                                    }
                                }
                                ClientMessage::MoveProp { loose_prop_id, x, y } => {
                                    // Check if user is a moderator (needed for locked props)
                                    let is_mod = match memberships::is_moderator(&pool, user_id, realm_id).await {
                                        Ok(result) => result,
                                        Err(e) => {
                                            tracing::error!("[WS] Failed to check moderator status: {:?}", e);
                                            false
                                        }
                                    };

                                    // Check if prop is locked
                                    if let Ok(Some(prop)) = loose_props::get_loose_prop_by_id(&mut *recv_conn, loose_prop_id).await {
                                        if prop.is_locked && !is_mod {
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "PROP_LOCKED".to_string(),
                                                message: "This prop is locked and cannot be moved".to_string(),
                                            }).await;
                                            continue;
                                        }
                                    }

                                    // Move the prop
                                    match loose_props::move_loose_prop(
                                        &mut *recv_conn,
                                        loose_prop_id,
                                        x,
                                        y,
                                    ).await {
                                        Ok(updated_prop) => {
                                            #[cfg(debug_assertions)]
                                            tracing::debug!(
                                                "[WS] User {} moved prop {} to ({}, {})",
                                                user_id,
                                                loose_prop_id,
                                                x,
                                                y
                                            );
                                            // Broadcast the updated prop to all users in the channel
                                            let _ = tx.send(ServerMessage::PropRefresh { prop: updated_prop });
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] Move prop failed: {:?}", e);
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "MOVE_PROP_FAILED".to_string(),
                                                message: format!("{:?}", e),
                                            }).await;
                                        }
                                    }
                                }
                                ClientMessage::LockProp { loose_prop_id } => {
                                    // Check if user is a moderator
                                    let is_mod = match memberships::is_moderator(&pool, user_id, realm_id).await {
                                        Ok(result) => result,
                                        Err(e) => {
                                            tracing::error!("[WS] Failed to check moderator status: {:?}", e);
                                            false
                                        }
                                    };

                                    if !is_mod {
                                        let _ = direct_tx.send(ServerMessage::Error {
                                            code: "NOT_MODERATOR".to_string(),
                                            message: "You do not have permission to lock props".to_string(),
                                        }).await;
                                        continue;
                                    }

                                    // Lock the prop
                                    match loose_props::lock_loose_prop(
                                        &mut *recv_conn,
                                        loose_prop_id,
                                        user_id,
                                    ).await {
                                        Ok(updated_prop) => {
                                            #[cfg(debug_assertions)]
                                            tracing::debug!(
                                                "[WS] User {} locked prop {}",
                                                user_id,
                                                loose_prop_id
                                            );
                                            // Broadcast the updated prop to all users in the channel
                                            let _ = tx.send(ServerMessage::PropRefresh { prop: updated_prop });
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] Lock prop failed: {:?}", e);
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "LOCK_PROP_FAILED".to_string(),
                                                message: format!("{:?}", e),
                                            }).await;
                                        }
                                    }
                                }
                                ClientMessage::UnlockProp { loose_prop_id } => {
                                    // Check if user is a moderator
                                    let is_mod = match memberships::is_moderator(&pool, user_id, realm_id).await {
                                        Ok(result) => result,
                                        Err(e) => {
                                            tracing::error!("[WS] Failed to check moderator status: {:?}", e);
                                            false
                                        }
                                    };

                                    if !is_mod {
                                        let _ = direct_tx.send(ServerMessage::Error {
                                            code: "NOT_MODERATOR".to_string(),
                                            message: "You do not have permission to unlock props".to_string(),
                                        }).await;
                                        continue;
                                    }

                                    // Unlock the prop
                                    match loose_props::unlock_loose_prop(
                                        &mut *recv_conn,
                                        loose_prop_id,
                                    ).await {
                                        Ok(updated_prop) => {
                                            #[cfg(debug_assertions)]
                                            tracing::debug!(
                                                "[WS] User {} unlocked prop {}",
                                                user_id,
                                                loose_prop_id
                                            );
                                            // Broadcast the updated prop to all users in the channel
                                            let _ = tx.send(ServerMessage::PropRefresh { prop: updated_prop });
                                        }
                                        Err(e) => {
                                            tracing::error!("[WS] Unlock prop failed: {:?}", e);
                                            let _ = direct_tx.send(ServerMessage::Error {
                                                code: "UNLOCK_PROP_FAILED".to_string(),
                                                message: format!("{:?}", e),
                                            }).await;
                                        }
                                    }
                                }
                            }
                        }
                        Message::Close(close_frame) => {
                            // Check close code for scene change or logout
                            if let Some(CloseFrame { code, .. }) = close_frame {
                                if code == close_codes::SCENE_CHANGE {
                                    disconnect_reason = DisconnectReason::SceneChange;
                                } else if code == close_codes::LOGOUT {
                                    // Explicit logout - treat as graceful disconnect
                                    #[cfg(debug_assertions)]
                                    tracing::debug!("[WS] User {} logged out", user_id);
                                    disconnect_reason = DisconnectReason::Graceful;
                                } else {
                                    disconnect_reason = DisconnectReason::Graceful;
                                }
                            }
                            break;
                        }
                        _ => {
                            // Ignore binary, ping, pong messages
                        }
                    }
                }
                Ok(Some(Err(e))) => {
                    // WebSocket error
                    tracing::warn!("[WS] Connection error: {:?}", e);
                    disconnect_reason = DisconnectReason::Timeout;
                    break;
                }
                Ok(None) => {
                    // Stream ended gracefully
                    break;
                }
                Err(_) => {
                    // Timeout elapsed - connection likely lost
                    tracing::info!("[WS] Connection timeout for user {}", user_id);
                    // Send close frame with timeout code so client can attempt silent reconnection
                    let _ = close_tx
                        .send((close_codes::SERVER_TIMEOUT, "timeout".to_string()))
                        .await;
                    // Brief delay to allow close frame to be sent
                    tokio::time::sleep(Duration::from_millis(100)).await;
                    disconnect_reason = DisconnectReason::Timeout;
                    break;
                }
            }
        }
        // Return the connection and disconnect reason for cleanup
        (recv_conn, disconnect_reason)
    });

    // Spawn task to forward broadcasts, direct messages, and close frames to this client
    let send_task = tokio::spawn(async move {
        loop {
            tokio::select! {
                // Handle close frame requests (from timeout)
                Some((code, reason)) = close_rx.recv() => {
                    #[cfg(debug_assertions)]
                    tracing::debug!("[WS->Client] Sending close frame: code={}, reason={}", code, reason);
                    let close_frame = CloseFrame {
                        code,
                        reason: reason.into(),
                    };
                    let _ = sender.send(Message::Close(Some(close_frame))).await;
                    break;
                }
                // Handle broadcast messages
                Ok(msg) = rx.recv() => {
                    if let Ok(json) = serde_json::to_string(&msg) {
                        #[cfg(debug_assertions)]
                        tracing::debug!("[WS->Client] {}", json);
                        if sender.send(Message::Text(json.into())).await.is_err() {
                            break;
                        }
                    }
                }
                // Handle direct messages (Pong)
                Some(msg) = direct_rx.recv() => {
                    if let Ok(json) = serde_json::to_string(&msg) {
                        #[cfg(debug_assertions)]
                        tracing::debug!("[WS->Client] {}", json);
                        if sender.send(Message::Text(json.into())).await.is_err() {
                            break;
                        }
                    }
                }
                else => break
            }
        }
    });

    // Wait for either task to complete, track disconnect reason
    let disconnect_reason = tokio::select! {
        recv_result = recv_task => {
            // recv_task finished, get connection and reason back for cleanup
            if let Ok((mut cleanup_conn, reason)) = recv_result {
                let _ = channel_members::leave_channel(&mut *cleanup_conn, channel_id, user_id).await;
                reason
            } else {
                // Task panicked, use pool (RLS may fail but try anyway)
                let _ = channel_members::leave_channel(&pool, channel_id, user_id).await;
                DisconnectReason::Timeout
            }
        }
        _ = send_task => {
            // send_task finished first (likely client disconnect), need to acquire a new connection for cleanup
            if let Ok(mut cleanup_conn) = pool.acquire().await {
                let _ = set_rls_user_id(&mut cleanup_conn, user_id).await;
                let _ = channel_members::leave_channel(&mut *cleanup_conn, channel_id, user_id).await;
            }
            DisconnectReason::Graceful
        }
    };

    // Unregister user from direct message routing
    ws_state.unregister_user(user_id);

    tracing::info!(
        "[WS] User {} disconnected from channel {} (reason: {:?})",
        user_id,
        channel_id,
        disconnect_reason
    );

    // Broadcast departure with reason
    let _ = channel_state.tx.send(ServerMessage::MemberLeft {
        user_id,
        reason: disconnect_reason,
    });
}

/// Helper: Get all channel members with their avatar render data.
async fn get_members_with_avatars(
    conn: &mut sqlx::pool::PoolConnection<sqlx::Postgres>,
    pool: &PgPool,
    channel_id: Uuid,
    realm_id: Uuid,
) -> Result<Vec<ChannelMemberWithAvatar>, AppError> {
    // Get members first
    let members = channel_members::get_channel_members(&mut **conn, channel_id, realm_id).await?;

    // Fetch avatar data for each member using the effective avatar resolution
    // This handles the priority chain: forced > custom > selected realm > selected server > realm default > server default
    let mut result = Vec::with_capacity(members.len());
    for member in members {
        // All members now have a user_id (guests are regular users with the 'guest' tag)
        // Use the effective avatar resolution which handles all priority levels
        let avatar = avatars::get_effective_avatar_render_data(pool, member.user_id, realm_id)
            .await
            .ok()
            .flatten()
            .map(|(render_data, _source)| render_data)
            .unwrap_or_default();
        result.push(ChannelMemberWithAvatar { member, avatar });
    }

    Ok(result)
}
