//! WebSocket message protocol for channel presence.
//!
//! Shared message types used by both server and WASM client.

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::models::{AvatarRenderData, ChannelMemberInfo, ChannelMemberWithAvatar, ForcedAvatarReason, LooseProp};

/// Default function for serde that returns true (for is_same_scene field).
/// Must be pub for serde derive macro to access via full path.
pub fn default_is_same_scene() -> bool {
    true
}

/// WebSocket configuration sent to client on connect.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsConfig {
    /// Interval for client to send ping to keep connection alive (seconds).
    pub ping_interval_secs: u64,
}

/// WebSocket close codes (custom range: 4000-4999).
pub mod close_codes {
    /// Scene change (user navigating to different scene).
    pub const SCENE_CHANGE: u16 = 4000;
    /// Server timeout (no message received within timeout period).
    pub const SERVER_TIMEOUT: u16 = 4001;
    /// User explicitly logged out.
    pub const LOGOUT: u16 = 4003;
}

/// Reason for member disconnect.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DisconnectReason {
    /// Graceful disconnect (browser close, normal WebSocket close).
    Graceful,
    /// Scene navigation (custom close code 4000).
    SceneChange,
    /// Timeout (connection lost, no ping response).
    Timeout,
}

/// Client-to-server WebSocket messages.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ClientMessage {
    /// Update position in the channel.
    UpdatePosition {
        /// X coordinate in scene space.
        x: f64,
        /// Y coordinate in scene space.
        y: f64,
    },

    /// Update emotion by name.
    UpdateEmotion {
        /// Emotion name (e.g., "happy", "sad", "neutral").
        emotion: String,
    },

    /// Ping to keep connection alive.
    Ping,

    /// Send a chat message to the channel or directly to a user.
    SendChatMessage {
        /// Message content (max 500 chars).
        content: String,
        /// Target display name for direct whisper. None = broadcast to scene.
        #[serde(default)]
        target_display_name: Option<String>,
    },

    /// Drop a prop from inventory to the canvas.
    DropProp {
        /// Inventory item ID to drop.
        inventory_item_id: Uuid,
    },

    /// Pick up a loose prop from the canvas.
    PickUpProp {
        /// Loose prop ID to pick up.
        loose_prop_id: Uuid,
    },

    /// Request to broadcast avatar appearance to other users.
    SyncAvatar,

    /// Request to teleport to a different scene.
    Teleport {
        /// Scene ID to teleport to.
        scene_id: Uuid,
    },

    /// Moderator command (only processed if sender is a moderator).
    ModCommand {
        /// Subcommand name ("summon", "avatar", "teleport", "ban", etc.).
        subcommand: String,
        /// Arguments for the subcommand.
        args: Vec<String>,
    },

    /// Request to refresh identity after registration (guest → user conversion).
    /// Server will fetch updated user data and broadcast to all members.
    RefreshIdentity,
}

/// Server-to-client WebSocket messages.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ServerMessage {
    /// Welcome message with initial state after connection.
    Welcome {
        /// This user's member info.
        member: ChannelMemberInfo,
        /// All current members with avatars.
        members: Vec<ChannelMemberWithAvatar>,
        /// WebSocket configuration for the client.
        config: WsConfig,
    },

    /// A member joined the channel.
    MemberJoined {
        /// The member that joined.
        member: ChannelMemberWithAvatar,
    },

    /// A member left the channel.
    MemberLeft {
        /// User ID of the member who left.
        user_id: Uuid,
        /// Reason for disconnect.
        reason: DisconnectReason,
    },

    /// A member updated their position.
    PositionUpdated {
        /// User ID of the member.
        user_id: Uuid,
        /// New X coordinate.
        x: f64,
        /// New Y coordinate.
        y: f64,
    },

    /// A member changed their emotion.
    EmotionUpdated {
        /// User ID of the member.
        user_id: Uuid,
        /// Emotion name (e.g., "happy", "sad", "neutral").
        emotion: String,
        /// Asset paths for all 9 positions of the new emotion layer.
        emotion_layer: [Option<String>; 9],
    },

    /// Pong response to client ping.
    Pong,

    /// Error message.
    Error {
        /// Error code.
        code: String,
        /// Error message.
        message: String,
    },

    /// A chat message was received.
    ChatMessageReceived {
        /// Unique message ID.
        message_id: Uuid,
        /// User ID of sender.
        user_id: Uuid,
        /// Display name of sender.
        display_name: String,
        /// Message content.
        content: String,
        /// Emotion name for bubble styling (e.g., "happy", "sad", "neutral").
        emotion: String,
        /// Sender's X position at time of message.
        x: f64,
        /// Sender's Y position at time of message.
        y: f64,
        /// Server timestamp (milliseconds since epoch).
        timestamp: i64,
        /// Whether this is a whisper (direct message).
        /// Default: false (broadcast message).
        #[serde(default)]
        is_whisper: bool,
        /// Whether sender is in the same scene as recipient.
        /// For whispers: true = same scene (show as bubble), false = cross-scene (show as notification).
        /// For broadcasts: always true.
        /// Default: true (same scene).
        #[serde(default = "self::default_is_same_scene")]
        is_same_scene: bool,
    },

    /// Initial list of loose props when joining channel.
    LoosePropsSync {
        /// All current loose props in the channel.
        props: Vec<LooseProp>,
    },

    /// A prop was dropped on the canvas.
    PropDropped {
        /// The dropped prop.
        prop: LooseProp,
    },

    /// A prop was picked up from the canvas.
    PropPickedUp {
        /// ID of the prop that was picked up.
        prop_id: Uuid,
        /// User ID who picked it up.
        picked_up_by_user_id: Uuid,
    },

    /// A prop expired and was removed.
    PropExpired {
        /// ID of the expired prop.
        prop_id: Uuid,
    },

    /// A member updated their avatar appearance.
    AvatarUpdated {
        /// User ID of the member.
        user_id: Uuid,
        /// Updated avatar render data.
        avatar: AvatarRenderData,
    },

    /// Teleport approved - client should disconnect and reconnect to new scene.
    TeleportApproved {
        /// Scene ID to navigate to.
        scene_id: Uuid,
        /// Scene slug for URL.
        scene_slug: String,
    },

    /// User has been summoned by a moderator - triggers teleport.
    Summoned {
        /// Scene ID to teleport to.
        scene_id: Uuid,
        /// Scene slug for URL.
        scene_slug: String,
        /// Display name of the moderator who summoned.
        summoned_by: String,
    },

    /// Result of a moderator command.
    ModCommandResult {
        /// Whether the command succeeded.
        success: bool,
        /// Human-readable result message.
        message: String,
    },

    /// A member's identity was updated (e.g., guest registered as user).
    MemberIdentityUpdated {
        /// User ID of the member.
        user_id: Uuid,
        /// New display name.
        display_name: String,
        /// Whether the member is still a guest.
        is_guest: bool,
    },

    /// A user's avatar was forcibly changed (by moderator or scene entry).
    AvatarForced {
        /// User ID whose avatar was forced.
        user_id: Uuid,
        /// The forced avatar render data.
        avatar: AvatarRenderData,
        /// Why the avatar was forced.
        reason: ForcedAvatarReason,
        /// Display name of who forced the avatar (if mod command).
        forced_by: Option<String>,
    },

    /// A user's forced avatar was cleared (returned to their chosen avatar).
    AvatarCleared {
        /// User ID whose forced avatar was cleared.
        user_id: Uuid,
        /// The user's original avatar render data (restored).
        avatar: AvatarRenderData,
        /// Display name of who cleared the forced avatar (if mod command).
        cleared_by: Option<String>,
    },
}
