//! Viewer module - Native remote desktop viewer with full keyboard capture
//!
//! This module provides the viewer functionality for connecting to remote
//! GuruConnect sessions with low-level keyboard hooks for Win key capture.

mod input;
mod render;
mod transport;

use crate::proto;
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use tracing::{info, error, warn};

#[derive(Debug, Clone)]
pub enum ViewerEvent {
    Connected,
    Disconnected(String),
    Frame(render::FrameData),
    CursorPosition(i32, i32, bool),
    CursorShape(proto::CursorShape),
}

#[derive(Debug, Clone)]
pub enum InputEvent {
    Mouse(proto::MouseEvent),
    Key(proto::KeyEvent),
    SpecialKey(proto::SpecialKeyEvent),
}

/// Run the viewer to connect to a remote session
pub async fn run(server_url: &str, session_id: &str, api_key: &str) -> Result<()> {
    info!("GuruConnect Viewer starting");
    info!("Server: {}", server_url);
    info!("Session: {}", session_id);

    // Create channels for communication between components
    let (viewer_tx, viewer_rx) = mpsc::channel::<ViewerEvent>(100);
    let (input_tx, input_rx) = mpsc::channel::<InputEvent>(100);

    // Connect to server
    let ws_url = format!("{}?session_id={}", server_url, session_id);
    info!("Connecting to {}", ws_url);

    let (ws_sender, mut ws_receiver) = transport::connect(&ws_url, api_key).await?;
    let ws_sender = Arc::new(Mutex::new(ws_sender));

    info!("Connected to server");
    let _ = viewer_tx.send(ViewerEvent::Connected).await;

    // Clone sender for input forwarding
    let ws_sender_input = ws_sender.clone();

    // Spawn task to forward input events to server
    let mut input_rx = input_rx;
    let input_task = tokio::spawn(async move {
        while let Some(event) = input_rx.recv().await {
            let msg = match event {
                InputEvent::Mouse(m) => proto::Message {
                    payload: Some(proto::message::Payload::MouseEvent(m)),
                },
                InputEvent::Key(k) => proto::Message {
                    payload: Some(proto::message::Payload::KeyEvent(k)),
                },
                InputEvent::SpecialKey(s) => proto::Message {
                    payload: Some(proto::message::Payload::SpecialKey(s)),
                },
            };

            if let Err(e) = transport::send_message(&ws_sender_input, &msg).await {
                error!("Failed to send input: {}", e);
                break;
            }
        }
    });

    // Spawn task to receive messages from server
    let viewer_tx_recv = viewer_tx.clone();
    let receive_task = tokio::spawn(async move {
        while let Some(msg) = ws_receiver.recv().await {
            match msg.payload {
                Some(proto::message::Payload::VideoFrame(frame)) => {
                    if let Some(proto::video_frame::Encoding::Raw(raw)) = frame.encoding {
                        let frame_data = render::FrameData {
                            width: raw.width as u32,
                            height: raw.height as u32,
                            data: raw.data,
                            compressed: raw.compressed,
                            is_keyframe: raw.is_keyframe,
                        };
                        let _ = viewer_tx_recv.send(ViewerEvent::Frame(frame_data)).await;
                    }
                }
                Some(proto::message::Payload::CursorPosition(pos)) => {
                    let _ = viewer_tx_recv.send(ViewerEvent::CursorPosition(
                        pos.x, pos.y, pos.visible
                    )).await;
                }
                Some(proto::message::Payload::CursorShape(shape)) => {
                    let _ = viewer_tx_recv.send(ViewerEvent::CursorShape(shape)).await;
                }
                Some(proto::message::Payload::Disconnect(d)) => {
                    warn!("Server disconnected: {}", d.reason);
                    let _ = viewer_tx_recv.send(ViewerEvent::Disconnected(d.reason)).await;
                    break;
                }
                _ => {}
            }
        }
    });

    // Run the window (this blocks until window closes)
    render::run_window(viewer_rx, input_tx).await?;

    // Cleanup
    input_task.abort();
    receive_task.abort();

    Ok(())
}
