//! Protocol Buffers wire format handling for terminal streaming
//!
//! This module provides binary serialization using Protocol Buffers with
//! optional zlib compression for large payloads.
//!
//! # Wire Format
//!
//! Each message is prefixed with a 1-byte header:
//! - `0x00`: Uncompressed protobuf payload
//! - `0x01`: Zlib-compressed protobuf payload
//!
//! Compression is applied automatically for payloads exceeding 1KB.

use crate::streaming::error::{Result, StreamingError};
use crate::streaming::protocol::{
    ClientMessage as AppClientMessage, CpuStats as AppCpuStats, DiskStats as AppDiskStats,
    EventType as AppEventType, LoadAverage as AppLoadAverage, MemoryStats as AppMemoryStats,
    NetworkInterfaceStats as AppNetworkInterfaceStats, ServerMessage as AppServerMessage,
    ThemeInfo as AppThemeInfo,
};
use flate2::read::ZlibDecoder;
use flate2::write::ZlibEncoder;
use flate2::Compression;
use prost::Message;
use std::io::{Read, Write};

/// Generated Protocol Buffer types
/// Pre-generated from proto/terminal.proto to avoid requiring protoc at build time.
/// To regenerate: run `cargo build --features streaming` with protoc installed,
/// then copy the output from target/debug/build/.../out/terminal.rs
#[path = "terminal.pb.rs"]
pub mod pb;

/// Compression threshold in bytes (256 bytes)
/// Lowered from 1KB to compress more messages - typical terminal output
/// (prompts, short commands) is 200-800 bytes
const COMPRESSION_THRESHOLD: usize = 256;

/// Wire format flags
const FLAG_UNCOMPRESSED: u8 = 0x00;
const FLAG_COMPRESSED: u8 = 0x01;

/// Encode a server message to binary format with optional compression
pub fn encode_server_message(msg: &AppServerMessage) -> Result<Vec<u8>> {
    let proto_msg: pb::ServerMessage = msg.into();
    let payload = proto_msg.encode_to_vec();

    encode_with_compression(&payload)
}

/// Encode a client message to binary format with optional compression
pub fn encode_client_message(msg: &AppClientMessage) -> Result<Vec<u8>> {
    let proto_msg: pb::ClientMessage = msg.into();
    let payload = proto_msg.encode_to_vec();

    encode_with_compression(&payload)
}

/// Decode a server message from binary format
pub fn decode_server_message(data: &[u8]) -> Result<AppServerMessage> {
    let payload = decode_with_decompression(data)?;
    let proto_msg = pb::ServerMessage::decode(&*payload)
        .map_err(|e| StreamingError::InvalidMessage(format!("Protobuf decode error: {}", e)))?;

    proto_msg.try_into()
}

/// Decode a client message from binary format
pub fn decode_client_message(data: &[u8]) -> Result<AppClientMessage> {
    let payload = decode_with_decompression(data)?;
    let proto_msg = pb::ClientMessage::decode(&*payload)
        .map_err(|e| StreamingError::InvalidMessage(format!("Protobuf decode error: {}", e)))?;

    proto_msg.try_into()
}

/// Internal: encode payload with optional compression
fn encode_with_compression(payload: &[u8]) -> Result<Vec<u8>> {
    if payload.len() > COMPRESSION_THRESHOLD {
        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
        encoder
            .write_all(payload)
            .map_err(|e| StreamingError::InvalidMessage(format!("Compression error: {}", e)))?;
        let compressed = encoder
            .finish()
            .map_err(|e| StreamingError::InvalidMessage(format!("Compression error: {}", e)))?;

        // Only use compression if it actually saves space
        if compressed.len() < payload.len() {
            let mut result = Vec::with_capacity(compressed.len() + 1);
            result.push(FLAG_COMPRESSED);
            result.extend(compressed);
            return Ok(result);
        }
    }

    // Uncompressed
    let mut result = Vec::with_capacity(payload.len() + 1);
    result.push(FLAG_UNCOMPRESSED);
    result.extend(payload);
    Ok(result)
}

/// Internal: decode payload with optional decompression
fn decode_with_decompression(data: &[u8]) -> Result<Vec<u8>> {
    if data.is_empty() {
        return Err(StreamingError::InvalidMessage("Empty message".into()));
    }

    let (flags, payload) = data.split_at(1);

    if flags[0] & FLAG_COMPRESSED != 0 {
        let mut decoder = ZlibDecoder::new(payload);
        let mut decompressed = Vec::new();
        decoder
            .read_to_end(&mut decompressed)
            .map_err(|e| StreamingError::InvalidMessage(format!("Decompression error: {}", e)))?;
        Ok(decompressed)
    } else {
        Ok(payload.to_vec())
    }
}

// =============================================================================
// Conversion: App types -> Proto types
// =============================================================================

impl From<&AppThemeInfo> for pb::ThemeInfo {
    fn from(theme: &AppThemeInfo) -> Self {
        pb::ThemeInfo {
            name: theme.name.clone(),
            background: Some(pb::Color {
                r: theme.background.0 as u32,
                g: theme.background.1 as u32,
                b: theme.background.2 as u32,
            }),
            foreground: Some(pb::Color {
                r: theme.foreground.0 as u32,
                g: theme.foreground.1 as u32,
                b: theme.foreground.2 as u32,
            }),
            normal: theme
                .normal
                .iter()
                .map(|c| pb::Color {
                    r: c.0 as u32,
                    g: c.1 as u32,
                    b: c.2 as u32,
                })
                .collect(),
            bright: theme
                .bright
                .iter()
                .map(|c| pb::Color {
                    r: c.0 as u32,
                    g: c.1 as u32,
                    b: c.2 as u32,
                })
                .collect(),
        }
    }
}

impl From<&AppServerMessage> for pb::ServerMessage {
    fn from(msg: &AppServerMessage) -> Self {
        use pb::server_message::Message;

        let message = match msg {
            AppServerMessage::Output { data, timestamp } => Some(Message::Output(pb::Output {
                data: data.as_bytes().to_vec(),
                timestamp: *timestamp,
            })),
            AppServerMessage::Resize { cols, rows } => Some(Message::Resize(pb::Resize {
                cols: *cols as u32,
                rows: *rows as u32,
            })),
            AppServerMessage::Title { title } => Some(Message::Title(pb::Title {
                title: title.clone(),
            })),
            AppServerMessage::Connected {
                cols,
                rows,
                initial_screen,
                session_id,
                theme,
                badge,
                faint_text_alpha,
                cwd,
                modify_other_keys,
                client_id,
                readonly,
            } => Some(Message::Connected(pb::Connected {
                cols: *cols as u32,
                rows: *rows as u32,
                initial_screen: initial_screen.as_ref().map(|s| s.as_bytes().to_vec()),
                session_id: session_id.clone(),
                theme: theme.as_ref().map(|t| t.into()),
                badge: badge.clone(),
                faint_text_alpha: *faint_text_alpha,
                cwd: cwd.clone(),
                modify_other_keys: *modify_other_keys,
                client_id: client_id.clone(),
                readonly: *readonly,
            })),
            AppServerMessage::Refresh {
                cols,
                rows,
                screen_content,
            } => Some(Message::Refresh(pb::Refresh {
                cols: *cols as u32,
                rows: *rows as u32,
                screen_content: screen_content.as_bytes().to_vec(),
            })),
            AppServerMessage::CursorPosition { col, row, visible } => {
                Some(Message::Cursor(pb::CursorPosition {
                    col: *col as u32,
                    row: *row as u32,
                    visible: *visible,
                }))
            }
            AppServerMessage::CwdChanged {
                old_cwd,
                new_cwd,
                hostname,
                username,
                timestamp,
            } => Some(Message::CwdChanged(pb::CwdChanged {
                old_cwd: old_cwd.clone(),
                new_cwd: new_cwd.clone(),
                hostname: hostname.clone(),
                username: username.clone(),
                timestamp: *timestamp,
            })),
            AppServerMessage::TriggerMatched {
                trigger_id,
                row,
                col,
                end_col,
                text,
                captures,
                timestamp,
            } => Some(Message::TriggerMatched(pb::TriggerMatched {
                trigger_id: *trigger_id,
                row: *row as u32,
                col: *col as u32,
                end_col: *end_col as u32,
                text: text.clone(),
                captures: captures.clone(),
                timestamp: *timestamp,
            })),
            AppServerMessage::ActionNotify {
                trigger_id,
                title,
                message,
            } => Some(Message::ActionNotify(pb::ActionNotify {
                trigger_id: *trigger_id,
                title: title.clone(),
                message: message.clone(),
            })),
            AppServerMessage::ActionMarkLine {
                trigger_id,
                row,
                label,
                color,
            } => Some(Message::ActionMarkLine(pb::ActionMarkLine {
                trigger_id: *trigger_id,
                row: *row as u32,
                label: label.clone(),
                color: color.map(|(r, g, b)| pb::Color {
                    r: r as u32,
                    g: g as u32,
                    b: b as u32,
                }),
            })),
            AppServerMessage::Bell => Some(Message::Bell(pb::Bell {})),
            AppServerMessage::Error { message, code } => Some(Message::Error(pb::Error {
                message: message.clone(),
                code: code.clone(),
            })),
            AppServerMessage::Shutdown { reason } => Some(Message::Shutdown(pb::Shutdown {
                reason: reason.clone(),
            })),
            AppServerMessage::Pong => Some(Message::Pong(pb::Pong {})),
            AppServerMessage::ModeChanged { mode, enabled } => {
                Some(Message::ModeChanged(pb::ModeChanged {
                    mode: mode.clone(),
                    enabled: *enabled,
                }))
            }
            AppServerMessage::GraphicsAdded { row, format } => {
                Some(Message::GraphicsAdded(pb::GraphicsAdded {
                    row: *row as u32,
                    format: format.clone(),
                }))
            }
            AppServerMessage::HyperlinkAdded { url, row, col, id } => {
                Some(Message::HyperlinkAdded(pb::HyperlinkAdded {
                    url: url.clone(),
                    row: *row as u32,
                    col: *col as u32,
                    id: id.clone(),
                }))
            }
            AppServerMessage::UserVarChanged {
                name,
                value,
                old_value,
            } => Some(Message::UserVarChanged(pb::UserVarChanged {
                name: name.clone(),
                value: value.clone(),
                old_value: old_value.clone(),
            })),
            AppServerMessage::ProgressBarChanged {
                action,
                id,
                state,
                percent,
                label,
            } => Some(Message::ProgressBarChanged(pb::ProgressBarChanged {
                action: action.clone(),
                id: id.clone(),
                state: state.clone(),
                percent: percent.map(|p| p as u32),
                label: label.clone(),
            })),
            AppServerMessage::BadgeChanged { badge } => {
                Some(Message::BadgeChanged(pb::BadgeChanged {
                    badge: badge.clone(),
                }))
            }
            AppServerMessage::SelectionChanged {
                start_col,
                start_row,
                end_col,
                end_row,
                text,
                mode,
                cleared,
            } => Some(Message::SelectionChanged(pb::SelectionChanged {
                start_col: start_col.map(|c| c as u32),
                start_row: start_row.map(|r| r as u32),
                end_col: end_col.map(|c| c as u32),
                end_row: end_row.map(|r| r as u32),
                text: text.clone(),
                mode: mode.clone(),
                cleared: *cleared,
            })),
            AppServerMessage::ClipboardSync {
                operation,
                content,
                target,
            } => Some(Message::ClipboardSync(pb::ClipboardSync {
                operation: operation.clone(),
                content: content.clone(),
                target: target.clone(),
            })),
            AppServerMessage::ShellIntegrationEvent {
                event_type,
                command,
                exit_code,
                timestamp,
                cursor_line,
            } => Some(Message::ShellIntegrationEvent(pb::ShellIntegrationEvent {
                event_type: event_type.clone(),
                command: command.clone(),
                exit_code: *exit_code,
                timestamp: *timestamp,
                cursor_line: *cursor_line,
            })),
            AppServerMessage::SystemStats {
                cpu,
                memory,
                disks,
                networks,
                load_average,
                hostname,
                os_name,
                os_version,
                kernel_version,
                uptime_secs,
                timestamp,
            } => Some(Message::SystemStats(pb::SystemStats {
                cpu: cpu.as_ref().map(|c| pb::CpuStats {
                    overall_usage_percent: c.overall_usage_percent,
                    physical_core_count: c.physical_core_count,
                    per_core_usage_percent: c.per_core_usage_percent.clone(),
                    brand: c.brand.clone(),
                    frequency_mhz: c.frequency_mhz,
                }),
                memory: memory.as_ref().map(|m| pb::MemoryStats {
                    total_bytes: m.total_bytes,
                    used_bytes: m.used_bytes,
                    available_bytes: m.available_bytes,
                    swap_total_bytes: m.swap_total_bytes,
                    swap_used_bytes: m.swap_used_bytes,
                }),
                disks: disks
                    .iter()
                    .map(|d| pb::DiskStats {
                        name: d.name.clone(),
                        mount_point: d.mount_point.clone(),
                        total_bytes: d.total_bytes,
                        available_bytes: d.available_bytes,
                        kind: d.kind.clone(),
                        file_system: d.file_system.clone(),
                        is_removable: d.is_removable,
                    })
                    .collect(),
                networks: networks
                    .iter()
                    .map(|n| pb::NetworkInterfaceStats {
                        name: n.name.clone(),
                        received_bytes: n.received_bytes,
                        transmitted_bytes: n.transmitted_bytes,
                        total_received_bytes: n.total_received_bytes,
                        total_transmitted_bytes: n.total_transmitted_bytes,
                        packets_received: n.packets_received,
                        packets_transmitted: n.packets_transmitted,
                        errors_received: n.errors_received,
                        errors_transmitted: n.errors_transmitted,
                    })
                    .collect(),
                load_average: load_average.as_ref().map(|la| pb::LoadAverage {
                    one_minute: la.one_minute,
                    five_minutes: la.five_minutes,
                    fifteen_minutes: la.fifteen_minutes,
                }),
                hostname: hostname.clone(),
                os_name: os_name.clone(),
                os_version: os_version.clone(),
                kernel_version: kernel_version.clone(),
                uptime_secs: *uptime_secs,
                timestamp: *timestamp,
            })),
            AppServerMessage::ZoneOpened {
                zone_id,
                zone_type,
                abs_row_start,
            } => Some(Message::ZoneOpened(pb::ZoneOpened {
                zone_id: *zone_id,
                zone_type: zone_type.clone(),
                abs_row_start: *abs_row_start,
            })),
            AppServerMessage::ZoneClosed {
                zone_id,
                zone_type,
                abs_row_start,
                abs_row_end,
                exit_code,
            } => Some(Message::ZoneClosed(pb::ZoneClosed {
                zone_id: *zone_id,
                zone_type: zone_type.clone(),
                abs_row_start: *abs_row_start,
                abs_row_end: *abs_row_end,
                exit_code: *exit_code,
            })),
            AppServerMessage::ZoneScrolledOut { zone_id, zone_type } => {
                Some(Message::ZoneScrolledOut(pb::ZoneScrolledOut {
                    zone_id: *zone_id,
                    zone_type: zone_type.clone(),
                }))
            }
            AppServerMessage::EnvironmentChanged {
                key,
                value,
                old_value,
            } => Some(Message::EnvironmentChanged(pb::EnvironmentChanged {
                key: key.clone(),
                value: value.clone(),
                old_value: old_value.clone(),
            })),
            AppServerMessage::RemoteHostTransition {
                hostname,
                username,
                old_hostname,
                old_username,
            } => Some(Message::RemoteHostTransition(pb::RemoteHostTransition {
                hostname: hostname.clone(),
                username: username.clone(),
                old_hostname: old_hostname.clone(),
                old_username: old_username.clone(),
            })),
            AppServerMessage::SubShellDetected { depth, shell_type } => {
                Some(Message::SubShellDetected(pb::SubShellDetected {
                    depth: *depth,
                    shell_type: shell_type.clone(),
                }))
            }
            AppServerMessage::SemanticSnapshot { snapshot_json } => {
                Some(Message::SemanticSnapshot(pb::SemanticSnapshotData {
                    snapshot_json: snapshot_json.clone(),
                }))
            }
            AppServerMessage::FileTransferStarted {
                id,
                direction,
                filename,
                total_bytes,
            } => Some(Message::FileTransferStarted(pb::FileTransferStarted {
                id: *id,
                direction: direction.clone(),
                filename: filename.clone(),
                total_bytes: *total_bytes,
            })),
            AppServerMessage::FileTransferProgress {
                id,
                bytes_transferred,
                total_bytes,
            } => Some(Message::FileTransferProgress(pb::FileTransferProgress {
                id: *id,
                bytes_transferred: *bytes_transferred,
                total_bytes: *total_bytes,
            })),
            AppServerMessage::FileTransferCompleted { id, filename, size } => {
                Some(Message::FileTransferCompleted(pb::FileTransferCompleted {
                    id: *id,
                    filename: filename.clone(),
                    size: *size,
                }))
            }
            AppServerMessage::FileTransferFailed { id, reason } => {
                Some(Message::FileTransferFailed(pb::FileTransferFailed {
                    id: *id,
                    reason: reason.clone(),
                }))
            }
            AppServerMessage::UploadRequested { format } => {
                Some(Message::UploadRequested(pb::UploadRequested {
                    format: format.clone(),
                }))
            }
            AppServerMessage::ScreenCleared { include_scrollback } => {
                Some(Message::ScreenCleared(pb::ScreenCleared {
                    include_scrollback: *include_scrollback,
                }))
            }
        };

        pb::ServerMessage { message }
    }
}

impl From<&AppClientMessage> for pb::ClientMessage {
    fn from(msg: &AppClientMessage) -> Self {
        use pb::client_message::Message;

        let message = match msg {
            AppClientMessage::Input { data } => Some(Message::Input(pb::Input {
                data: data.as_bytes().to_vec(),
            })),
            AppClientMessage::Resize { cols, rows } => Some(Message::Resize(pb::ClientResize {
                cols: *cols as u32,
                rows: *rows as u32,
            })),
            AppClientMessage::Ping => Some(Message::Ping(pb::Ping {})),
            AppClientMessage::RequestRefresh => Some(Message::Refresh(pb::RequestRefresh {})),
            AppClientMessage::Subscribe { events } => Some(Message::Subscribe(pb::Subscribe {
                events: events.iter().map(|e| e.clone().into()).collect(),
            })),
            AppClientMessage::Mouse {
                col,
                row,
                button,
                shift,
                ctrl,
                alt,
                event_type,
            } => Some(Message::Mouse(pb::MouseInput {
                col: *col as u32,
                row: *row as u32,
                button: *button as u32,
                shift: *shift,
                ctrl: *ctrl,
                alt: *alt,
                event_type: event_type.clone(),
            })),
            AppClientMessage::FocusChange { focused } => {
                Some(Message::Focus(pb::FocusChange { focused: *focused }))
            }
            AppClientMessage::Paste { content } => Some(Message::Paste(pb::PasteInput {
                content: content.clone(),
            })),
            AppClientMessage::SelectionRequest {
                start_col,
                start_row,
                end_col,
                end_row,
                mode,
            } => Some(Message::Selection(pb::SelectionRequest {
                start_col: *start_col as u32,
                start_row: *start_row as u32,
                end_col: *end_col as u32,
                end_row: *end_row as u32,
                mode: mode.clone(),
            })),
            AppClientMessage::ClipboardRequest {
                operation,
                content,
                target,
            } => Some(Message::Clipboard(pb::ClipboardRequest {
                operation: operation.clone(),
                content: content.clone(),
                target: target.clone(),
            })),
            AppClientMessage::SnapshotRequest {
                scope,
                max_commands,
            } => Some(Message::SnapshotRequest(pb::SnapshotRequest {
                scope: scope.clone(),
                max_commands: *max_commands,
            })),
        };

        pb::ClientMessage { message }
    }
}

impl From<AppEventType> for i32 {
    fn from(event: AppEventType) -> Self {
        match event {
            AppEventType::Output => pb::EventType::Output as i32,
            AppEventType::Cursor => pb::EventType::Cursor as i32,
            AppEventType::Bell => pb::EventType::Bell as i32,
            AppEventType::Title => pb::EventType::Title as i32,
            AppEventType::Resize => pb::EventType::Resize as i32,
            AppEventType::Cwd => pb::EventType::Cwd as i32,
            AppEventType::Trigger => pb::EventType::Trigger as i32,
            AppEventType::Action => pb::EventType::Action as i32,
            AppEventType::Mode => pb::EventType::Mode as i32,
            AppEventType::Graphics => pb::EventType::Graphics as i32,
            AppEventType::Hyperlink => pb::EventType::Hyperlink as i32,
            AppEventType::UserVar => pb::EventType::UserVar as i32,
            AppEventType::ProgressBar => pb::EventType::ProgressBar as i32,
            AppEventType::Badge => pb::EventType::Badge as i32,
            AppEventType::Selection => pb::EventType::Selection as i32,
            AppEventType::Clipboard => pb::EventType::Clipboard as i32,
            AppEventType::Shell => pb::EventType::Shell as i32,
            AppEventType::SystemStats => pb::EventType::SystemStats as i32,
            AppEventType::Zone => pb::EventType::Zone as i32,
            AppEventType::Environment => pb::EventType::Environment as i32,
            AppEventType::RemoteHost => pb::EventType::RemoteHost as i32,
            AppEventType::SubShell => pb::EventType::SubShell as i32,
            AppEventType::Snapshot => pb::EventType::Snapshot as i32,
            AppEventType::FileTransfer => pb::EventType::FileTransfer as i32,
            AppEventType::UploadRequest => pb::EventType::UploadRequest as i32,
            AppEventType::ScreenCleared => pb::EventType::ScreenCleared as i32,
        }
    }
}

// =============================================================================
// Conversion: Proto types -> App types
// =============================================================================

impl TryFrom<pb::ThemeInfo> for AppThemeInfo {
    type Error = StreamingError;

    fn try_from(theme: pb::ThemeInfo) -> Result<Self> {
        let bg = theme
            .background
            .ok_or_else(|| StreamingError::InvalidMessage("Missing background color".into()))?;
        let fg = theme
            .foreground
            .ok_or_else(|| StreamingError::InvalidMessage("Missing foreground color".into()))?;

        if theme.normal.len() != 8 {
            return Err(StreamingError::InvalidMessage(format!(
                "Expected 8 normal colors, got {}",
                theme.normal.len()
            )));
        }
        if theme.bright.len() != 8 {
            return Err(StreamingError::InvalidMessage(format!(
                "Expected 8 bright colors, got {}",
                theme.bright.len()
            )));
        }

        let mut normal = [(0u8, 0u8, 0u8); 8];
        for (i, c) in theme.normal.iter().enumerate() {
            normal[i] = (c.r as u8, c.g as u8, c.b as u8);
        }

        let mut bright = [(0u8, 0u8, 0u8); 8];
        for (i, c) in theme.bright.iter().enumerate() {
            bright[i] = (c.r as u8, c.g as u8, c.b as u8);
        }

        Ok(AppThemeInfo {
            name: theme.name,
            background: (bg.r as u8, bg.g as u8, bg.b as u8),
            foreground: (fg.r as u8, fg.g as u8, fg.b as u8),
            normal,
            bright,
        })
    }
}

impl TryFrom<pb::ServerMessage> for AppServerMessage {
    type Error = StreamingError;

    fn try_from(msg: pb::ServerMessage) -> Result<Self> {
        use pb::server_message::Message;

        match msg.message {
            Some(Message::Output(output)) => Ok(AppServerMessage::Output {
                data: String::from_utf8_lossy(&output.data).into_owned(),
                timestamp: output.timestamp,
            }),
            Some(Message::Resize(resize)) => Ok(AppServerMessage::Resize {
                cols: resize.cols as u16,
                rows: resize.rows as u16,
            }),
            Some(Message::Title(title)) => Ok(AppServerMessage::Title { title: title.title }),
            Some(Message::Connected(connected)) => Ok(AppServerMessage::Connected {
                cols: connected.cols as u16,
                rows: connected.rows as u16,
                initial_screen: connected
                    .initial_screen
                    .map(|s| String::from_utf8_lossy(&s).into_owned()),
                session_id: connected.session_id,
                theme: connected.theme.map(|t| t.try_into()).transpose()?,
                badge: connected.badge,
                faint_text_alpha: connected.faint_text_alpha,
                cwd: connected.cwd,
                modify_other_keys: connected.modify_other_keys,
                client_id: connected.client_id,
                readonly: connected.readonly,
            }),
            Some(Message::Refresh(refresh)) => Ok(AppServerMessage::Refresh {
                cols: refresh.cols as u16,
                rows: refresh.rows as u16,
                screen_content: String::from_utf8_lossy(&refresh.screen_content).into_owned(),
            }),
            Some(Message::Cursor(cursor)) => Ok(AppServerMessage::CursorPosition {
                col: cursor.col as u16,
                row: cursor.row as u16,
                visible: cursor.visible,
            }),
            Some(Message::CwdChanged(cwd)) => Ok(AppServerMessage::CwdChanged {
                old_cwd: cwd.old_cwd,
                new_cwd: cwd.new_cwd,
                hostname: cwd.hostname,
                username: cwd.username,
                timestamp: cwd.timestamp,
            }),
            Some(Message::TriggerMatched(tm)) => Ok(AppServerMessage::TriggerMatched {
                trigger_id: tm.trigger_id,
                row: tm.row as u16,
                col: tm.col as u16,
                end_col: tm.end_col as u16,
                text: tm.text,
                captures: tm.captures,
                timestamp: tm.timestamp,
            }),
            Some(Message::ActionNotify(an)) => Ok(AppServerMessage::ActionNotify {
                trigger_id: an.trigger_id,
                title: an.title,
                message: an.message,
            }),
            Some(Message::ActionMarkLine(aml)) => Ok(AppServerMessage::ActionMarkLine {
                trigger_id: aml.trigger_id,
                row: aml.row as u16,
                label: aml.label,
                color: aml.color.map(|c| (c.r as u8, c.g as u8, c.b as u8)),
            }),
            Some(Message::Bell(_)) => Ok(AppServerMessage::Bell),
            Some(Message::Error(error)) => Ok(AppServerMessage::Error {
                message: error.message,
                code: error.code,
            }),
            Some(Message::Shutdown(shutdown)) => Ok(AppServerMessage::Shutdown {
                reason: shutdown.reason,
            }),
            Some(Message::Pong(_)) => Ok(AppServerMessage::Pong),
            Some(Message::ModeChanged(mc)) => Ok(AppServerMessage::ModeChanged {
                mode: mc.mode,
                enabled: mc.enabled,
            }),
            Some(Message::GraphicsAdded(ga)) => Ok(AppServerMessage::GraphicsAdded {
                row: ga.row as u16,
                format: ga.format,
            }),
            Some(Message::HyperlinkAdded(ha)) => Ok(AppServerMessage::HyperlinkAdded {
                url: ha.url,
                row: ha.row as u16,
                col: ha.col as u16,
                id: ha.id,
            }),
            Some(Message::UserVarChanged(uv)) => Ok(AppServerMessage::UserVarChanged {
                name: uv.name,
                value: uv.value,
                old_value: uv.old_value,
            }),
            Some(Message::ProgressBarChanged(pbc)) => Ok(AppServerMessage::ProgressBarChanged {
                action: pbc.action,
                id: pbc.id,
                state: pbc.state,
                percent: pbc.percent.map(|p| p.min(100) as u8),
                label: pbc.label,
            }),
            Some(Message::BadgeChanged(bc)) => {
                Ok(AppServerMessage::BadgeChanged { badge: bc.badge })
            }
            Some(Message::SelectionChanged(sc)) => Ok(AppServerMessage::SelectionChanged {
                start_col: sc.start_col.map(|c| c as u16),
                start_row: sc.start_row.map(|r| r as u16),
                end_col: sc.end_col.map(|c| c as u16),
                end_row: sc.end_row.map(|r| r as u16),
                text: sc.text,
                mode: sc.mode,
                cleared: sc.cleared,
            }),
            Some(Message::ClipboardSync(cs)) => Ok(AppServerMessage::ClipboardSync {
                operation: cs.operation,
                content: cs.content,
                target: cs.target,
            }),
            Some(Message::ShellIntegrationEvent(sie)) => {
                Ok(AppServerMessage::ShellIntegrationEvent {
                    event_type: sie.event_type,
                    command: sie.command,
                    exit_code: sie.exit_code,
                    timestamp: sie.timestamp,
                    cursor_line: sie.cursor_line,
                })
            }
            Some(Message::SystemStats(ss)) => Ok(AppServerMessage::SystemStats {
                cpu: ss.cpu.map(|c| AppCpuStats {
                    overall_usage_percent: c.overall_usage_percent,
                    physical_core_count: c.physical_core_count,
                    per_core_usage_percent: c.per_core_usage_percent,
                    brand: c.brand,
                    frequency_mhz: c.frequency_mhz,
                }),
                memory: ss.memory.map(|m| AppMemoryStats {
                    total_bytes: m.total_bytes,
                    used_bytes: m.used_bytes,
                    available_bytes: m.available_bytes,
                    swap_total_bytes: m.swap_total_bytes,
                    swap_used_bytes: m.swap_used_bytes,
                }),
                disks: ss
                    .disks
                    .into_iter()
                    .map(|d| AppDiskStats {
                        name: d.name,
                        mount_point: d.mount_point,
                        total_bytes: d.total_bytes,
                        available_bytes: d.available_bytes,
                        kind: d.kind,
                        file_system: d.file_system,
                        is_removable: d.is_removable,
                    })
                    .collect(),
                networks: ss
                    .networks
                    .into_iter()
                    .map(|n| AppNetworkInterfaceStats {
                        name: n.name,
                        received_bytes: n.received_bytes,
                        transmitted_bytes: n.transmitted_bytes,
                        total_received_bytes: n.total_received_bytes,
                        total_transmitted_bytes: n.total_transmitted_bytes,
                        packets_received: n.packets_received,
                        packets_transmitted: n.packets_transmitted,
                        errors_received: n.errors_received,
                        errors_transmitted: n.errors_transmitted,
                    })
                    .collect(),
                load_average: ss.load_average.map(|la| AppLoadAverage {
                    one_minute: la.one_minute,
                    five_minutes: la.five_minutes,
                    fifteen_minutes: la.fifteen_minutes,
                }),
                hostname: ss.hostname,
                os_name: ss.os_name,
                os_version: ss.os_version,
                kernel_version: ss.kernel_version,
                uptime_secs: ss.uptime_secs,
                timestamp: ss.timestamp,
            }),
            Some(Message::ZoneOpened(zo)) => Ok(AppServerMessage::ZoneOpened {
                zone_id: zo.zone_id,
                zone_type: zo.zone_type,
                abs_row_start: zo.abs_row_start,
            }),
            Some(Message::ZoneClosed(zc)) => Ok(AppServerMessage::ZoneClosed {
                zone_id: zc.zone_id,
                zone_type: zc.zone_type,
                abs_row_start: zc.abs_row_start,
                abs_row_end: zc.abs_row_end,
                exit_code: zc.exit_code,
            }),
            Some(Message::ZoneScrolledOut(zso)) => Ok(AppServerMessage::ZoneScrolledOut {
                zone_id: zso.zone_id,
                zone_type: zso.zone_type,
            }),
            Some(Message::EnvironmentChanged(ec)) => Ok(AppServerMessage::EnvironmentChanged {
                key: ec.key,
                value: ec.value,
                old_value: ec.old_value,
            }),
            Some(Message::RemoteHostTransition(rht)) => {
                Ok(AppServerMessage::RemoteHostTransition {
                    hostname: rht.hostname,
                    username: rht.username,
                    old_hostname: rht.old_hostname,
                    old_username: rht.old_username,
                })
            }
            Some(Message::SubShellDetected(ssd)) => Ok(AppServerMessage::SubShellDetected {
                depth: ssd.depth,
                shell_type: ssd.shell_type,
            }),
            Some(Message::SemanticSnapshot(snap)) => Ok(AppServerMessage::SemanticSnapshot {
                snapshot_json: snap.snapshot_json,
            }),
            Some(Message::FileTransferStarted(fts)) => Ok(AppServerMessage::FileTransferStarted {
                id: fts.id,
                direction: fts.direction,
                filename: fts.filename,
                total_bytes: fts.total_bytes,
            }),
            Some(Message::FileTransferProgress(ftp)) => {
                Ok(AppServerMessage::FileTransferProgress {
                    id: ftp.id,
                    bytes_transferred: ftp.bytes_transferred,
                    total_bytes: ftp.total_bytes,
                })
            }
            Some(Message::FileTransferCompleted(ftc)) => {
                Ok(AppServerMessage::FileTransferCompleted {
                    id: ftc.id,
                    filename: ftc.filename,
                    size: ftc.size,
                })
            }
            Some(Message::FileTransferFailed(ftf)) => Ok(AppServerMessage::FileTransferFailed {
                id: ftf.id,
                reason: ftf.reason,
            }),
            Some(Message::UploadRequested(ur)) => {
                Ok(AppServerMessage::UploadRequested { format: ur.format })
            }
            Some(Message::ScreenCleared(sc)) => Ok(AppServerMessage::ScreenCleared {
                include_scrollback: sc.include_scrollback,
            }),
            None => Err(StreamingError::InvalidMessage(
                "Empty server message".into(),
            )),
        }
    }
}

impl TryFrom<pb::ClientMessage> for AppClientMessage {
    type Error = StreamingError;

    fn try_from(msg: pb::ClientMessage) -> Result<Self> {
        use pb::client_message::Message;

        match msg.message {
            Some(Message::Input(input)) => Ok(AppClientMessage::Input {
                data: String::from_utf8_lossy(&input.data).into_owned(),
            }),
            Some(Message::Resize(resize)) => Ok(AppClientMessage::Resize {
                cols: resize.cols as u16,
                rows: resize.rows as u16,
            }),
            Some(Message::Ping(_)) => Ok(AppClientMessage::Ping),
            Some(Message::Refresh(_)) => Ok(AppClientMessage::RequestRefresh),
            Some(Message::Subscribe(subscribe)) => Ok(AppClientMessage::Subscribe {
                events: subscribe
                    .events
                    .iter()
                    .filter_map(|e| pb::EventType::try_from(*e).ok())
                    .map(|e| e.into())
                    .collect(),
            }),
            Some(Message::Mouse(mouse)) => Ok(AppClientMessage::Mouse {
                col: mouse.col as u16,
                row: mouse.row as u16,
                button: mouse.button.min(255) as u8,
                shift: mouse.shift,
                ctrl: mouse.ctrl,
                alt: mouse.alt,
                event_type: mouse.event_type,
            }),
            Some(Message::Focus(focus)) => Ok(AppClientMessage::FocusChange {
                focused: focus.focused,
            }),
            Some(Message::Paste(paste)) => Ok(AppClientMessage::Paste {
                content: paste.content,
            }),
            Some(Message::Selection(sel)) => Ok(AppClientMessage::SelectionRequest {
                start_col: sel.start_col as u16,
                start_row: sel.start_row as u16,
                end_col: sel.end_col as u16,
                end_row: sel.end_row as u16,
                mode: sel.mode,
            }),
            Some(Message::Clipboard(clip)) => Ok(AppClientMessage::ClipboardRequest {
                operation: clip.operation,
                content: clip.content,
                target: clip.target,
            }),
            Some(Message::SnapshotRequest(req)) => Ok(AppClientMessage::SnapshotRequest {
                scope: req.scope,
                max_commands: req.max_commands,
            }),
            None => Err(StreamingError::InvalidMessage(
                "Empty client message".into(),
            )),
        }
    }
}

impl From<pb::EventType> for AppEventType {
    fn from(event: pb::EventType) -> Self {
        match event {
            pb::EventType::Unspecified => AppEventType::Output, // Default fallback
            pb::EventType::Output => AppEventType::Output,
            pb::EventType::Cursor => AppEventType::Cursor,
            pb::EventType::Bell => AppEventType::Bell,
            pb::EventType::Title => AppEventType::Title,
            pb::EventType::Resize => AppEventType::Resize,
            pb::EventType::Cwd => AppEventType::Cwd,
            pb::EventType::Trigger => AppEventType::Trigger,
            pb::EventType::Action => AppEventType::Action,
            pb::EventType::Mode => AppEventType::Mode,
            pb::EventType::Graphics => AppEventType::Graphics,
            pb::EventType::Hyperlink => AppEventType::Hyperlink,
            pb::EventType::UserVar => AppEventType::UserVar,
            pb::EventType::ProgressBar => AppEventType::ProgressBar,
            pb::EventType::Badge => AppEventType::Badge,
            pb::EventType::Selection => AppEventType::Selection,
            pb::EventType::Clipboard => AppEventType::Clipboard,
            pb::EventType::Shell => AppEventType::Shell,
            pb::EventType::SystemStats => AppEventType::SystemStats,
            pb::EventType::Zone => AppEventType::Zone,
            pb::EventType::Environment => AppEventType::Environment,
            pb::EventType::RemoteHost => AppEventType::RemoteHost,
            pb::EventType::SubShell => AppEventType::SubShell,
            pb::EventType::Snapshot => AppEventType::Snapshot,
            pb::EventType::FileTransfer => AppEventType::FileTransfer,
            pb::EventType::UploadRequest => AppEventType::UploadRequest,
            pb::EventType::ScreenCleared => AppEventType::ScreenCleared,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encode_decode_output() {
        let msg = AppServerMessage::output("Hello, World!".to_string());
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();

        match decoded {
            AppServerMessage::Output { data, timestamp } => {
                assert_eq!(data, "Hello, World!");
                assert_eq!(timestamp, None);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_resize() {
        let msg = AppServerMessage::resize(80, 24);
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();

        match decoded {
            AppServerMessage::Resize { cols, rows } => {
                assert_eq!(cols, 80);
                assert_eq!(rows, 24);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_client_input() {
        let msg = AppClientMessage::input("ls\n".to_string());
        let encoded = encode_client_message(&msg).unwrap();
        let decoded = decode_client_message(&encoded).unwrap();

        match decoded {
            AppClientMessage::Input { data } => {
                assert_eq!(data, "ls\n");
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_compression_for_large_payload() {
        // Create a message that exceeds COMPRESSION_THRESHOLD (256 bytes)
        let large_data = "A".repeat(500);
        let msg = AppServerMessage::output(large_data.clone());
        let encoded = encode_server_message(&msg).unwrap();

        // First byte should indicate compression
        assert_eq!(encoded[0], FLAG_COMPRESSED);

        // Verify it decodes correctly
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Output { data, .. } => {
                assert_eq!(data, large_data);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_no_compression_for_small_payload() {
        // Message below COMPRESSION_THRESHOLD (256 bytes)
        let msg = AppServerMessage::output("small".to_string());
        let encoded = encode_server_message(&msg).unwrap();

        // First byte should indicate no compression
        assert_eq!(encoded[0], FLAG_UNCOMPRESSED);
    }

    #[test]
    fn test_compression_boundary() {
        // Test right at the threshold - 256 bytes of payload should not trigger compression
        // (threshold is >256, not >=256)
        let boundary_data = "X".repeat(200); // Will be ~200 bytes in protobuf
        let msg = AppServerMessage::output(boundary_data);
        let encoded = encode_server_message(&msg).unwrap();
        // Should NOT be compressed (at or below threshold)
        assert_eq!(encoded[0], FLAG_UNCOMPRESSED);

        // Test just above threshold
        let above_data = "Y".repeat(300); // Will be ~300 bytes in protobuf
        let msg2 = AppServerMessage::output(above_data.clone());
        let encoded2 = encode_server_message(&msg2).unwrap();
        // Should be compressed (above threshold)
        assert_eq!(encoded2[0], FLAG_COMPRESSED);

        // Verify it decodes correctly
        let decoded = decode_server_message(&encoded2).unwrap();
        match decoded {
            AppServerMessage::Output { data, .. } => {
                assert_eq!(data, above_data);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_theme_roundtrip() {
        let theme = AppThemeInfo {
            name: "test-theme".to_string(),
            background: (0, 0, 0),
            foreground: (255, 255, 255),
            normal: [
                (0, 0, 0),
                (255, 0, 0),
                (0, 255, 0),
                (255, 255, 0),
                (0, 0, 255),
                (255, 0, 255),
                (0, 255, 255),
                (255, 255, 255),
            ],
            bright: [
                (128, 128, 128),
                (255, 128, 128),
                (128, 255, 128),
                (255, 255, 128),
                (128, 128, 255),
                (255, 128, 255),
                (128, 255, 255),
                (255, 255, 255),
            ],
        };

        let msg = AppServerMessage::connected_with_theme(80, 24, "session-123".to_string(), theme);
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();

        match decoded {
            AppServerMessage::Connected {
                cols,
                rows,
                session_id,
                theme,
                ..
            } => {
                assert_eq!(cols, 80);
                assert_eq!(rows, 24);
                assert_eq!(session_id, "session-123");
                assert!(theme.is_some());
                let t = theme.unwrap();
                assert_eq!(t.name, "test-theme");
                assert_eq!(t.background, (0, 0, 0));
                assert_eq!(t.foreground, (255, 255, 255));
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_empty_message_error() {
        let result = decode_client_message(&[]);
        assert!(result.is_err());
    }

    #[test]
    fn test_encode_decode_bell() {
        let msg = AppServerMessage::Bell;
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        assert!(matches!(decoded, AppServerMessage::Bell));
    }

    #[test]
    fn test_encode_decode_shutdown() {
        let msg = AppServerMessage::Shutdown {
            reason: "Server maintenance".to_string(),
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Shutdown { reason } => {
                assert_eq!(reason, "Server maintenance");
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_pong() {
        let msg = AppServerMessage::Pong;
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        assert!(matches!(decoded, AppServerMessage::Pong));
    }

    #[test]
    fn test_encode_decode_error_message() {
        let msg = AppServerMessage::Error {
            message: "Something went wrong".to_string(),
            code: Some("E500".to_string()),
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Error { message, code } => {
                assert_eq!(message, "Something went wrong");
                assert_eq!(code, Some("E500".to_string()));
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_error_without_code() {
        let msg = AppServerMessage::Error {
            message: "Error occurred".to_string(),
            code: None,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Error { message, code } => {
                assert_eq!(message, "Error occurred");
                assert_eq!(code, None);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_cursor_position() {
        let msg = AppServerMessage::CursorPosition {
            col: 42,
            row: 10,
            visible: true,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::CursorPosition { col, row, visible } => {
                assert_eq!(col, 42);
                assert_eq!(row, 10);
                assert!(visible);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_cursor_hidden() {
        let msg = AppServerMessage::CursorPosition {
            col: 0,
            row: 0,
            visible: false,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::CursorPosition { col, row, visible } => {
                assert_eq!(col, 0);
                assert_eq!(row, 0);
                assert!(!visible);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_title() {
        let msg = AppServerMessage::Title {
            title: "My Terminal Window".to_string(),
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Title { title } => {
                assert_eq!(title, "My Terminal Window");
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_refresh() {
        let msg = AppServerMessage::Refresh {
            cols: 120,
            rows: 40,
            screen_content: "Full screen content here".to_string(),
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Refresh {
                cols,
                rows,
                screen_content,
            } => {
                assert_eq!(cols, 120);
                assert_eq!(rows, 40);
                assert_eq!(screen_content, "Full screen content here");
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_connected_with_screen() {
        let msg = AppServerMessage::Connected {
            cols: 80,
            rows: 24,
            initial_screen: Some("initial content".to_string()),
            session_id: "sess-abc".to_string(),
            theme: None,
            badge: None,
            faint_text_alpha: None,
            cwd: None,
            modify_other_keys: None,
            client_id: None,
            readonly: None,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Connected {
                cols,
                rows,
                initial_screen,
                session_id,
                theme,
                ..
            } => {
                assert_eq!(cols, 80);
                assert_eq!(rows, 24);
                assert_eq!(initial_screen, Some("initial content".to_string()));
                assert_eq!(session_id, "sess-abc");
                assert!(theme.is_none());
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_client_ping() {
        let msg = AppClientMessage::Ping;
        let encoded = encode_client_message(&msg).unwrap();
        let decoded = decode_client_message(&encoded).unwrap();
        assert!(matches!(decoded, AppClientMessage::Ping));
    }

    #[test]
    fn test_encode_decode_client_refresh() {
        let msg = AppClientMessage::RequestRefresh;
        let encoded = encode_client_message(&msg).unwrap();
        let decoded = decode_client_message(&encoded).unwrap();
        assert!(matches!(decoded, AppClientMessage::RequestRefresh));
    }

    #[test]
    fn test_encode_decode_client_subscribe() {
        let msg = AppClientMessage::Subscribe {
            events: vec![
                AppEventType::Output,
                AppEventType::Cursor,
                AppEventType::Bell,
                AppEventType::Title,
                AppEventType::Resize,
            ],
        };
        let encoded = encode_client_message(&msg).unwrap();
        let decoded = decode_client_message(&encoded).unwrap();
        match decoded {
            AppClientMessage::Subscribe { events } => {
                assert_eq!(events.len(), 5);
                assert!(events.contains(&AppEventType::Output));
                assert!(events.contains(&AppEventType::Cursor));
                assert!(events.contains(&AppEventType::Bell));
                assert!(events.contains(&AppEventType::Title));
                assert!(events.contains(&AppEventType::Resize));
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_unicode_content() {
        let unicode_content = "Hello 世界 🌍 مرحبا Привет 日本語";
        let msg = AppServerMessage::Output {
            data: unicode_content.to_string(),
            timestamp: None,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Output { data, .. } => {
                assert_eq!(data, unicode_content);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_ansi_escape_sequences() {
        let ansi_data = "\x1b[31mRed\x1b[0m \x1b[32mGreen\x1b[0m \x1b[1;34mBold Blue\x1b[0m";
        let msg = AppServerMessage::Output {
            data: ansi_data.to_string(),
            timestamp: None,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Output { data, .. } => {
                assert_eq!(data, ansi_data);
                assert!(data.contains("\x1b[31m"));
                assert!(data.contains("\x1b[0m"));
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_with_timestamp() {
        let msg = AppServerMessage::Output {
            data: "test".to_string(),
            timestamp: Some(1234567890123),
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Output { data, timestamp } => {
                assert_eq!(data, "test");
                assert_eq!(timestamp, Some(1234567890123));
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_decode_only_flag_byte_error() {
        // Only has the flag byte, no actual payload
        let result = decode_server_message(&[0x00]);
        // This should either succeed with an empty/default message or fail
        // depending on protobuf handling of empty data
        // The behavior depends on the protobuf schema
        assert!(result.is_err() || result.is_ok());
    }

    #[test]
    fn test_encode_empty_string() {
        let msg = AppServerMessage::Output {
            data: String::new(),
            timestamp: None,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Output { data, .. } => {
                assert!(data.is_empty());
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_event_type_conversions() {
        // Test all event type conversions
        let event_types = vec![
            (AppEventType::Output, pb::EventType::Output),
            (AppEventType::Cursor, pb::EventType::Cursor),
            (AppEventType::Bell, pb::EventType::Bell),
            (AppEventType::Title, pb::EventType::Title),
            (AppEventType::Resize, pb::EventType::Resize),
        ];

        for (app_type, _pb_type) in event_types {
            let i32_val: i32 = app_type.clone().into();
            // Verify conversion is deterministic
            let i32_val2: i32 = app_type.into();
            assert_eq!(i32_val, i32_val2);
        }
    }

    #[test]
    fn test_pb_event_type_to_app_event_type() {
        assert!(matches!(
            AppEventType::from(pb::EventType::Output),
            AppEventType::Output
        ));
        assert!(matches!(
            AppEventType::from(pb::EventType::Cursor),
            AppEventType::Cursor
        ));
        assert!(matches!(
            AppEventType::from(pb::EventType::Bell),
            AppEventType::Bell
        ));
        assert!(matches!(
            AppEventType::from(pb::EventType::Title),
            AppEventType::Title
        ));
        assert!(matches!(
            AppEventType::from(pb::EventType::Resize),
            AppEventType::Resize
        ));
        assert!(matches!(
            AppEventType::from(pb::EventType::Cwd),
            AppEventType::Cwd
        ));
        assert!(matches!(
            AppEventType::from(pb::EventType::Trigger),
            AppEventType::Trigger
        ));
        // Unspecified defaults to Output
        assert!(matches!(
            AppEventType::from(pb::EventType::Unspecified),
            AppEventType::Output
        ));
    }

    #[test]
    fn test_encode_decode_cwd_changed() {
        let msg = AppServerMessage::CwdChanged {
            old_cwd: Some("/home/user".to_string()),
            new_cwd: "/home/user/project".to_string(),
            hostname: Some("myhost".to_string()),
            username: Some("user".to_string()),
            timestamp: Some(1234567890),
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::CwdChanged {
                old_cwd,
                new_cwd,
                hostname,
                username,
                timestamp,
            } => {
                assert_eq!(old_cwd, Some("/home/user".to_string()));
                assert_eq!(new_cwd, "/home/user/project");
                assert_eq!(hostname, Some("myhost".to_string()));
                assert_eq!(username, Some("user".to_string()));
                assert_eq!(timestamp, Some(1234567890));
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_cwd_changed_minimal() {
        let msg = AppServerMessage::CwdChanged {
            old_cwd: None,
            new_cwd: "/tmp".to_string(),
            hostname: None,
            username: None,
            timestamp: None,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::CwdChanged {
                old_cwd,
                new_cwd,
                hostname,
                username,
                timestamp,
            } => {
                assert_eq!(old_cwd, None);
                assert_eq!(new_cwd, "/tmp");
                assert_eq!(hostname, None);
                assert_eq!(username, None);
                assert_eq!(timestamp, None);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_trigger_matched() {
        let msg = AppServerMessage::TriggerMatched {
            trigger_id: 42,
            row: 10,
            col: 5,
            end_col: 20,
            text: "error: something failed".to_string(),
            captures: vec!["error".to_string(), "something failed".to_string()],
            timestamp: 9876543210,
        };
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::TriggerMatched {
                trigger_id,
                row,
                col,
                end_col,
                text,
                captures,
                timestamp,
            } => {
                assert_eq!(trigger_id, 42);
                assert_eq!(row, 10);
                assert_eq!(col, 5);
                assert_eq!(end_col, 20);
                assert_eq!(text, "error: something failed");
                assert_eq!(
                    captures,
                    vec!["error".to_string(), "something failed".to_string()]
                );
                assert_eq!(timestamp, 9876543210);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_connected_with_new_fields() {
        let msg = AppServerMessage::connected_full(
            80,
            24,
            Some("initial content".to_string()),
            "sess-full".to_string(),
            None,
            Some("my badge".to_string()),
            Some(0.5),
            Some("/home/user".to_string()),
            Some(2),
            Some("client-42".to_string()),
            Some(true),
        );
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::Connected {
                cols,
                rows,
                initial_screen,
                session_id,
                theme,
                badge,
                faint_text_alpha,
                cwd,
                modify_other_keys,
                client_id,
                readonly,
            } => {
                assert_eq!(cols, 80);
                assert_eq!(rows, 24);
                assert_eq!(initial_screen, Some("initial content".to_string()));
                assert_eq!(session_id, "sess-full");
                assert!(theme.is_none());
                assert_eq!(badge, Some("my badge".to_string()));
                assert_eq!(faint_text_alpha, Some(0.5));
                assert_eq!(cwd, Some("/home/user".to_string()));
                assert_eq!(modify_other_keys, Some(2));
                assert_eq!(client_id, Some("client-42".to_string()));
                assert_eq!(readonly, Some(true));
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_event_type_cwd_trigger_conversions() {
        let cwd_i32: i32 = AppEventType::Cwd.into();
        let trigger_i32: i32 = AppEventType::Trigger.into();
        assert_eq!(cwd_i32, pb::EventType::Cwd as i32);
        assert_eq!(trigger_i32, pb::EventType::Trigger as i32);
    }

    #[test]
    fn test_encode_decode_user_var_changed() {
        let msg = AppServerMessage::user_var_changed("hostname".to_string(), "myhost".to_string());
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::UserVarChanged {
                name,
                value,
                old_value,
            } => {
                assert_eq!(name, "hostname");
                assert_eq!(value, "myhost");
                assert_eq!(old_value, None);
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_encode_decode_user_var_changed_with_old_value() {
        let msg = AppServerMessage::user_var_changed_full(
            "hostname".to_string(),
            "newhost".to_string(),
            Some("oldhost".to_string()),
        );
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::UserVarChanged {
                name,
                value,
                old_value,
            } => {
                assert_eq!(name, "hostname");
                assert_eq!(value, "newhost");
                assert_eq!(old_value, Some("oldhost".to_string()));
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_event_type_user_var_conversion() {
        let user_var_i32: i32 = AppEventType::UserVar.into();
        assert_eq!(user_var_i32, pb::EventType::UserVar as i32);
    }

    #[test]
    fn test_snapshot_server_message_round_trip() {
        let msg = AppServerMessage::semantic_snapshot("{\"cols\":80}".to_string());
        let encoded = encode_server_message(&msg).unwrap();
        let decoded = decode_server_message(&encoded).unwrap();
        match decoded {
            AppServerMessage::SemanticSnapshot { snapshot_json } => {
                assert_eq!(snapshot_json, "{\"cols\":80}");
            }
            _ => panic!("Wrong message type"),
        }
    }

    #[test]
    fn test_snapshot_request_round_trip() {
        let msg = AppClientMessage::snapshot_request("recent".to_string(), Some(10));
        let encoded = encode_client_message(&msg).unwrap();
        let decoded = decode_client_message(&encoded).unwrap();
        match decoded {
            AppClientMessage::SnapshotRequest {
                scope,
                max_commands,
            } => {
                assert_eq!(scope, "recent");
                assert_eq!(max_commands, Some(10));
            }
            _ => panic!("Wrong message type"),
        }
    }
}
