//! Tauri 命令

use crate::state::{AppState, ConnectionState, CurrentUser, DeviceInfo, HistoryItem, ConnectRequest, P2PConnection};
use easyremote_client_core::{ClientConfig, SignalClient, SignalMessage, ScreenCapturer};
use easyremote_client_core::connection::NatTraversal;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::net::SocketAddr;
use tauri::State;
use tokio::sync::RwLock;
use tokio::net::UdpSocket;

type AppStateHandle = Arc<RwLock<AppState>>;

/// 全局信令客户端
static SIGNAL_CLIENT: tokio::sync::OnceCell<Arc<RwLock<Option<SignalClient>>>> = tokio::sync::OnceCell::const_new();

/// 强制下线标志
static FORCE_OFFLINE_FLAG: AtomicBool = AtomicBool::new(false);

/// 当前活跃的屏幕流会话
static ACTIVE_SESSION: tokio::sync::OnceCell<Arc<RwLock<Option<ActiveScreenSession>>>> = tokio::sync::OnceCell::const_new();

/// 流媒体设置 (使用 std::sync::RwLock 以便在普通线程中使用)
#[derive(Clone)]
struct StreamSettings {
    resolution: f64,
    fps: u32,
    quality: u32,
}

impl Default for StreamSettings {
    fn default() -> Self {
        Self {
            resolution: 0.5,   // 50% 分辨率，平衡质量和性能
            fps: 30,           // 30 FPS 提供流畅体验
            quality: 50,       // 50% JPEG 质量，大幅减少数据量和编码时间
        }
    }
}

/// 全局流媒体设置 (使用标准库的 RwLock)
use std::sync::RwLock as StdRwLock;
use once_cell::sync::Lazy;

static STREAM_SETTINGS: Lazy<Arc<StdRwLock<StreamSettings>>> = Lazy::new(|| {
    Arc::new(StdRwLock::new(StreamSettings::default()))
});

fn get_stream_settings_sync() -> Arc<StdRwLock<StreamSettings>> {
    STREAM_SETTINGS.clone()
}

/// 当前显示器索引
static CURRENT_DISPLAY: Lazy<Arc<std::sync::atomic::AtomicUsize>> = Lazy::new(|| {
    Arc::new(std::sync::atomic::AtomicUsize::new(0))
});

fn get_current_display() -> usize {
    CURRENT_DISPLAY.load(Ordering::SeqCst)
}

fn set_current_display(index: usize) {
    CURRENT_DISPLAY.store(index, Ordering::SeqCst);
}

/// 活跃屏幕会话
struct ActiveScreenSession {
    session_id: String,
    controller_device: String,
    stop_flag: Arc<AtomicBool>,
}

async fn get_active_session() -> &'static Arc<RwLock<Option<ActiveScreenSession>>> {
    ACTIVE_SESSION.get_or_init(|| async {
        Arc::new(RwLock::new(None))
    }).await
}

/// 获取信令客户端
async fn get_signal_client() -> &'static Arc<RwLock<Option<SignalClient>>> {
    SIGNAL_CLIENT.get_or_init(|| async {
        Arc::new(RwLock::new(None))
    }).await
}

/// P2P UDP 监听器
static P2P_LISTENER: tokio::sync::OnceCell<Arc<RwLock<Option<P2PListener>>>> = tokio::sync::OnceCell::const_new();

/// P2P 监听器
struct P2PListener {
    socket: Arc<UdpSocket>,
    local_port: u16,
    local_ips: Vec<String>,
}

async fn get_p2p_listener() -> &'static Arc<RwLock<Option<P2PListener>>> {
    P2P_LISTENER.get_or_init(|| async {
        Arc::new(RwLock::new(None))
    }).await
}

/// 初始化 P2P 监听器
async fn init_p2p_listener() -> Result<(u16, Vec<String>), String> {
    let listener = get_p2p_listener().await;
    let mut lock = listener.write().await;
    
    // 如果已经初始化，返回现有信息
    if let Some(ref l) = *lock {
        return Ok((l.local_port, l.local_ips.clone()));
    }
    
    // 创建 UDP socket
    let socket = UdpSocket::bind("0.0.0.0:0").await.map_err(|e| e.to_string())?;
    let local_addr = socket.local_addr().map_err(|e| e.to_string())?;
    let local_port = local_addr.port();
    
    // 获取本地 IP 地址列表
    let local_ips = NatTraversal::get_local_ips();
    
    tracing::info!("P2P 监听器已初始化: port={}, ips={:?}", local_port, local_ips);
    
    let socket_arc = Arc::new(socket);
    let socket_for_listener = socket_arc.clone();
    
    *lock = Some(P2PListener {
        socket: socket_arc,
        local_port,
        local_ips: local_ips.clone(),
    });
    
    // 启动 P2P 包监听任务
    tokio::spawn(async move {
        let mut buf = [0u8; 65536];
        loop {
            match socket_for_listener.recv_from(&mut buf).await {
                Ok((len, from)) => {
                    // 检查是否是打洞包
                    if len >= 9 && &buf[..9] == b"P2P_PUNCH" {
                        println!("★★★ 收到 P2P 打洞包来自: {}", from);
                        // 响应打洞包
                        let _ = socket_for_listener.send_to(b"P2P_ACK", from).await;
                    } else if len > 9 {
                        // 可能是屏幕帧数据或输入事件
                        // TODO: 处理 P2P 数据传输
                        println!("★★★ 收到 P2P 数据包: {} 字节 来自: {}", len, from);
                    }
                }
                Err(e) => {
                    tracing::warn!("P2P 监听错误: {}", e);
                    break;
                }
            }
        }
    });
    
    Ok((local_port, local_ips))
}

/// 获取公网地址（通过 STUN）
async fn get_public_addr(server_url: &str) -> Option<String> {
    let (public_addr, _) = get_ice_info(server_url).await;
    public_addr
}

/// 获取 ICE 信息（公网地址 + TURN 服务器）
async fn get_ice_info(server_url: &str) -> (Option<String>, Option<easyremote_client_core::signal::TurnServerInfo>) {
    use easyremote_client_core::signal::TurnServerInfo;
    
    let mut public_addr = None;
    let mut turn_server = None;
    
    // 尝试从服务器获取 ICE 服务器配置
    match NatTraversal::fetch_ice_servers(server_url).await {
        Ok(config) => {
            // 获取公网地址（通过 STUN）
            for stun in &config.stun_servers {
                match NatTraversal::get_public_addr_from_url(stun).await {
                    Ok(addr) => {
                        tracing::info!("获取到公网地址: {} (来自 {})", addr, stun);
                        println!("★★★ 获取到公网地址: {} (来自 {})", addr, stun);
                        public_addr = Some(addr.to_string());
                        break;
                    }
                    Err(e) => {
                        tracing::warn!("从 STUN {} 获取公网地址失败: {}", stun, e);
                    }
                }
            }
            
            // 获取 TURN 服务器信息
            if let Some(turn) = config.turn_server {
                tracing::info!("获取到 TURN 服务器: {}", turn.url);
                println!("★★★ 获取到 TURN 服务器: {}", turn.url);
                turn_server = Some(TurnServerInfo {
                    url: turn.url,
                    username: turn.username,
                    credential: turn.credential,
                });
            }
        }
        Err(e) => {
            tracing::warn!("获取 ICE 服务器配置失败: {}", e);
        }
    }
    
    (public_addr, turn_server)
}

/// 尝试建立 P2P 连接（增强版 - 双向打洞 + 更多重试）
async fn try_p2p_connect(socket: &UdpSocket, target: SocketAddr) -> bool {
    // 发送打洞包
    let punch_packet = b"P2P_PUNCH";
    let mut success = false;
    
    // 增加重试次数和双向打洞
    for attempt in 0..5 {
        // 快速发送多个打洞包（模拟突发，提高穿透率）
        for _ in 0..3 {
            let _ = socket.send_to(punch_packet, target).await;
            tokio::time::sleep(std::time::Duration::from_millis(50)).await;
        }
        
        // 等待响应（超时 800ms）
        let mut buf = [0u8; 64];
        let deadline = std::time::Instant::now() + std::time::Duration::from_millis(800);
        
        while std::time::Instant::now() < deadline {
            match tokio::time::timeout(
                std::time::Duration::from_millis(200),
                socket.recv_from(&mut buf)
            ).await {
                Ok(Ok((len, from))) => {
                    // 检查是否是来自目标的响应或打洞包
                    if len >= 7 && &buf[..7] == b"P2P_ACK" {
                        tracing::info!("P2P 打洞成功: {} (ACK 来自 {})", target, from);
                        println!("★★★ P2P 打洞成功: {} (ACK 来自 {})", target, from);
                        return true;
                    } else if len >= 9 && &buf[..9] == b"P2P_PUNCH" {
                        // 收到对方的打洞包，立即响应
                        let _ = socket.send_to(b"P2P_ACK", from).await;
                        tracing::info!("P2P 收到打洞包，已响应: {}", from);
                        println!("★★★ P2P 收到打洞包来自: {}，已响应 ACK", from);
                        success = true;
                    }
                }
                Ok(Err(_)) => break,
                Err(_) => {}
            }
        }
        
        if success {
            // 再发送几次确认包
            for _ in 0..2 {
                let _ = socket.send_to(b"P2P_ACK", target).await;
                tokio::time::sleep(std::time::Duration::from_millis(50)).await;
            }
            return true;
        }
        
        // 指数退避等待
        let wait_time = 100 * (1 << attempt.min(3));
        tokio::time::sleep(std::time::Duration::from_millis(wait_time)).await;
    }
    
    println!("★★★ P2P 打洞失败: {} (尝试 5 次)", target);
    false
}

/// 检查是否需要强制下线
pub fn check_force_offline() -> bool {
    FORCE_OFFLINE_FLAG.swap(false, Ordering::SeqCst)
}

/// 设置强制下线标志并清除登录信息
fn set_force_offline() {
    FORCE_OFFLINE_FLAG.store(true, Ordering::SeqCst);
    
    // 清除本地保存的登录信息
    let auth_path = dirs::data_dir()
        .unwrap_or_else(|| std::path::PathBuf::from("."))
        .join("easyremote")
        .join("auth.json");
    let _ = std::fs::remove_file(&auth_path);
    tracing::info!("已清除登录信息");
}

/// 启动屏幕流（优化版 - 低延迟）
async fn start_screen_streaming(session_id: String, controller_device: String, my_device_id: String) {
    tracing::info!("★★★ 启动屏幕流: session={}, controller={}, my_device={}", session_id, controller_device, my_device_id);
    println!("★★★ 启动屏幕流: session={}, controller={}, my_device={}", session_id, controller_device, my_device_id);
    
    // 检查是否已有活跃会话
    let active_session = get_active_session().await;
    {
        let mut session = active_session.write().await;
        if session.is_some() {
            tracing::warn!("已有活跃屏幕流会话，停止旧会话");
            if let Some(s) = session.as_ref() {
                s.stop_flag.store(true, Ordering::SeqCst);
            }
        }
        
        let stop_flag = Arc::new(AtomicBool::new(false));
        *session = Some(ActiveScreenSession {
            session_id: session_id.clone(),
            controller_device: controller_device.clone(),
            stop_flag: stop_flag.clone(),
        });
    }
    
    // 获取信令客户端
    let signal_client = get_signal_client().await;
    
    // 获取 stop_flag 用于停止控制
    let stop_flag_clone = {
        let session = active_session.read().await;
        session.as_ref().map(|s| s.stop_flag.clone()).unwrap_or_else(|| Arc::new(AtomicBool::new(true)))
    };
    
    // 启动屏幕捕获线程
    let session_id_capture = session_id.clone();
    let controller_device_capture = controller_device.clone();
    let _my_device_id_capture = my_device_id.clone();
    let signal_client_clone = signal_client.clone();
    
    let stream_settings = get_stream_settings_sync();
    
    std::thread::spawn(move || {
        println!("★★★ 开始创建屏幕捕获器...");
        
        // 创建一个多线程 tokio runtime 用于异步发送帧
        let rt = match tokio::runtime::Builder::new_multi_thread()
            .worker_threads(2)
            .enable_all()
            .build() {
            Ok(r) => r,
            Err(e) => {
                println!("★★★ 创建 tokio runtime 失败: {}", e);
                return;
            }
        };
        
        // 创建屏幕捕获器（使用当前选择的显示器）
        let display_index = get_current_display();
        println!("★★★ 使用显示器: {}", display_index);
        let mut capturer = match ScreenCapturer::new(display_index) {
            Ok(c) => {
                println!("★★★ 屏幕捕获器创建成功");
                c
            }
            Err(e) => {
                println!("★★★ 创建屏幕捕获器失败: {}", e);
                tracing::error!("创建屏幕捕获器失败: {}", e);
                return;
            }
        };
        
        let width = capturer.width();
        let height = capturer.height();
        println!("★★★ 屏幕分辨率: {}x{}", width, height);
        tracing::info!("屏幕分辨率: {}x{}", width, height);
        
        let mut frame_count = 0u64;
        let start_time = std::time::Instant::now();
        let mut last_frame_time = std::time::Instant::now();
        
        // 创建发送通道（异步发送，不阻塞捕获）
        let (frame_tx, mut frame_rx) = tokio::sync::mpsc::channel::<(u32, u32, Vec<u8>)>(2);
        
        // 启动异步发送任务
        let session_id_send = session_id_capture.clone();
        let controller_device_send = controller_device_capture.clone();
        let signal_client_send = signal_client_clone.clone();
        rt.spawn(async move {
            while let Some((w, h, jpeg_data)) = frame_rx.recv().await {
                let client = signal_client_send.read().await;
                if let Some(c) = client.as_ref() {
                    let _ = c.send_frame(
                        session_id_send.clone(),
                        controller_device_send.clone(),
                        w,
                        h,
                        &jpeg_data,
                    ).await;
                }
            }
        });
        
        loop {
            let frame_start = std::time::Instant::now();
            
            // 获取当前设置
            let (resolution, fps, quality) = {
                let s = stream_settings.read().unwrap();
                (s.resolution, s.fps, s.quality)
            };
            
            // 计算目标帧间隔
            let target_interval = std::time::Duration::from_micros(1_000_000 / fps as u64);
            
            // 检查是否需要停止
            if stop_flag_clone.load(Ordering::SeqCst) {
                tracing::info!("屏幕流停止");
                break;
            }
            
            // 捕获帧
            match capturer.capture_rgba() {
                Ok(Some(rgba_data)) => {
                    // 计算目标分辨率
                    let scaled_width = ((width as f64) * resolution) as u32;
                    let scaled_height = ((height as f64) * resolution) as u32;
                    
                    // 使用更高效的编码流程
                    let jpeg_data = if resolution >= 0.99 {
                        // 不需要缩放，直接编码
                        encode_jpeg_fast(&rgba_data, width, height, quality)
                    } else {
                        // 需要缩放
                        let img = image::RgbaImage::from_raw(width, height, rgba_data);
                        if let Some(img) = img {
                            // 使用最快的缩放算法
                            let scaled = image::imageops::resize(
                                &img, 
                                scaled_width, 
                                scaled_height, 
                                image::imageops::FilterType::Nearest
                            );
                            encode_jpeg_fast(scaled.as_raw(), scaled_width, scaled_height, quality)
                        } else {
                            None
                        }
                    };
                    
                    if let Some(jpeg_data) = jpeg_data {
                        let jpeg_len = jpeg_data.len();
                        
                        // 异步发送（不阻塞）
                        let _ = frame_tx.try_send((scaled_width, scaled_height, jpeg_data));
                        
                        frame_count += 1;
                        if frame_count % 30 == 0 {
                            let elapsed = start_time.elapsed().as_secs_f64();
                            let actual_fps = frame_count as f64 / elapsed;
                            let frame_time = frame_start.elapsed().as_millis();
                            println!("★★★ 屏幕流: {:.1} fps, {}x{}, {} KB, 帧处理: {}ms", 
                                actual_fps, scaled_width, scaled_height, jpeg_len / 1024, frame_time);
                        }
                        if frame_count == 1 {
                            println!("★★★ 发送第一帧: {}x{}, {} 字节", scaled_width, scaled_height, jpeg_len);
                        }
                    }
                }
                Ok(None) => {
                    // 没有新帧，短暂等待
                    std::thread::sleep(std::time::Duration::from_micros(500));
                    continue;
                }
                Err(e) => {
                    tracing::error!("捕获屏幕失败: {}", e);
                    break;
                }
            }
            
            // 精确帧率控制（考虑处理时间）
            let elapsed = last_frame_time.elapsed();
            if elapsed < target_interval {
                let sleep_time = target_interval - elapsed;
                if sleep_time > std::time::Duration::from_micros(500) {
                    std::thread::sleep(sleep_time - std::time::Duration::from_micros(200));
                }
            }
            last_frame_time = std::time::Instant::now();
        }
        
        println!("★★★ 屏幕流线程结束");
    });
}

/// 快速 JPEG 编码（使用较低质量以减少延迟）
fn encode_jpeg_fast(rgba_data: &[u8], width: u32, height: u32, quality: u32) -> Option<Vec<u8>> {
    use image::codecs::jpeg::JpegEncoder;
    
    // RGBA -> RGB（JPEG 不支持透明通道）
    let rgb_data: Vec<u8> = rgba_data
        .chunks(4)
        .flat_map(|chunk| [chunk[0], chunk[1], chunk[2]])
        .collect();
    
    let mut jpeg_data = Vec::with_capacity(width as usize * height as usize / 4);
    {
        // 使用指定质量编码
        let mut encoder = JpegEncoder::new_with_quality(&mut jpeg_data, quality as u8);
        if encoder.encode(&rgb_data, width, height, image::ColorType::Rgb8.into()).is_err() {
            return None;
        }
    }
    Some(jpeg_data)
}

/// 处理鼠标输入
fn handle_mouse_input(x: f64, y: f64, event_type: &str, button: Option<u8>, delta: Option<f64>) {
    use easyremote_client_core::InputController;
    
    // 获取当前分辨率设置以计算缩放比例
    let scale = {
        let settings = get_stream_settings_sync();
        let s = settings.read().unwrap();
        1.0 / s.resolution
    };
    let actual_x = (x * scale) as i32;
    let actual_y = (y * scale) as i32;
    
    // 调试日志（仅对非移动事件打印）
    if event_type != "move" {
        println!("★★★★★ [被控端] 执行鼠标操作: type={}, raw=({:.0}, {:.0}), scaled=({}, {})", 
            event_type, x, y, actual_x, actual_y);
    }
    
    match InputController::new() {
        Ok(mut controller) => {
            // 所有事件都需要先移动鼠标到目标位置
            match controller.move_mouse(actual_x, actual_y) {
                Ok(_) => {
                    if event_type != "move" {
                        println!("★★★★★ [被控端] 鼠标已移动到 ({}, {})", actual_x, actual_y);
                    }
                }
                Err(e) => {
                    println!("★★★★★ [被控端] 移动鼠标到 ({}, {}) 失败: {}", actual_x, actual_y, e);
                }
            }
            
            match event_type {
                "move" => {
                    // 已经在上面移动了
                }
                "click" => {
                    let btn = match button {
                        Some(0) => easyremote_client_core::MouseButton::Left,
                        Some(2) => easyremote_client_core::MouseButton::Right,
                        Some(1) => easyremote_client_core::MouseButton::Middle,
                        _ => easyremote_client_core::MouseButton::Left,
                    };
                    match controller.click(btn) {
                        Ok(_) => println!("★★★★★ [被控端] 鼠标点击成功"),
                        Err(e) => println!("★★★★★ [被控端] 点击鼠标失败: {}", e),
                    }
                }
                "down" => {
                    let btn = match button {
                        Some(0) => easyremote_client_core::MouseButton::Left,
                        Some(2) => easyremote_client_core::MouseButton::Right,
                        Some(1) => easyremote_client_core::MouseButton::Middle,
                        _ => easyremote_client_core::MouseButton::Left,
                    };
                    match controller.mouse_down(btn) {
                        Ok(_) => println!("★★★★★ [被控端] 鼠标按下成功"),
                        Err(e) => println!("★★★★★ [被控端] 鼠标按下失败: {}", e),
                    }
                }
                "up" => {
                    let btn = match button {
                        Some(0) => easyremote_client_core::MouseButton::Left,
                        Some(2) => easyremote_client_core::MouseButton::Right,
                        Some(1) => easyremote_client_core::MouseButton::Middle,
                        _ => easyremote_client_core::MouseButton::Left,
                    };
                    match controller.mouse_up(btn) {
                        Ok(_) => println!("★★★★★ [被控端] 鼠标释放成功"),
                        Err(e) => println!("★★★★★ [被控端] 鼠标释放失败: {}", e),
                    }
                }
                "scroll" => {
                    if let Some(d) = delta {
                        match controller.scroll(0, -(d as i32) / 3) {
                            Ok(_) => println!("★★★★★ [被控端] 滚动成功"),
                            Err(e) => println!("★★★★★ [被控端] 滚动失败: {}", e),
                        }
                    }
                }
                _ => {}
            }
        }
        Err(e) => {
            println!("★★★★★ [被控端] 创建 InputController 失败: {}", e);
        }
    }
}

/// 处理键盘输入
fn handle_keyboard_input(key: &str, event_type: &str) {
    use easyremote_client_core::InputController;
    
    match InputController::new() {
        Ok(mut controller) => {
            match event_type {
                "down" => {
                    if let Err(e) = controller.key_down(key) {
                        println!("★★★ 键盘按下失败: {} - {}", key, e);
                    }
                }
                "up" => {
                    if let Err(e) = controller.key_up(key) {
                        println!("★★★ 键盘释放失败: {} - {}", key, e);
                    }
                }
                _ => {}
            }
        }
        Err(e) => {
            println!("★★★ 创建 InputController 失败: {}", e);
        }
    }
}

/// 获取操作系统类型
fn get_os_type() -> &'static str {
    if cfg!(target_os = "windows") {
        "Windows"
    } else if cfg!(target_os = "macos") {
        "macOS"
    } else {
        "Linux"
    }
}

/// 获取操作系统版本
fn get_os_version() -> String {
    std::env::consts::OS.to_string()
}

/// 向服务端注册设备
async fn register_device_to_server(
    server_url: &str,
    device_id: &str,
    device_name: &str,
    auth_token: Option<&str>,
) -> Result<(), String> {
    let client = reqwest::Client::new();
    let http_url = server_url.replace("ws://", "http://").replace("wss://", "https://");
    
    let mut request = client
        .post(format!("{}/api/devices/register", http_url))
        .json(&serde_json::json!({
            "device_id": device_id,
            "name": device_name,
            "os_type": get_os_type(),
            "os_version": get_os_version()
        }));
    
    // 如果已登录，附带 token 以绑定设备到用户
    if let Some(token) = auth_token {
        request = request.header("Authorization", format!("Bearer {}", token));
    }
    
    let response = request.send().await.map_err(|e| e.to_string())?;
    
    if !response.status().is_success() {
        let error: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
        return Err(error["error"].as_str().unwrap_or("设备注册失败").to_string());
    }
    
    tracing::info!("设备已注册到服务端");
    Ok(())
}

/// 连接到信令服务器
async fn connect_to_signal_server(device_id: String, server_url: String) -> Result<(), String> {
    let signal_client_holder = get_signal_client().await;
    
    // 如果已连接，先断开
    {
        let mut holder = signal_client_holder.write().await;
        *holder = None;
    }
    
    // 创建新的信令客户端
    let mut signal_client = SignalClient::new(device_id.clone(), server_url.clone());
    
    // 连接到信令服务器
    let device_id_clone = device_id.clone();
    let device_id_for_callback = device_id.clone();
    match signal_client.connect(move |msg| {
        let device_id_inner = device_id_for_callback.clone();
        match msg {
            SignalMessage::HeartbeatAck => {
                tracing::debug!("收到心跳响应");
            }
            SignalMessage::ForceOffline { device_id } => {
                tracing::info!("收到强制下线命令: device_id={}", device_id);
                // 设置强制下线标志
                set_force_offline();
            }
            SignalMessage::ConnectRequest { session_id, from_device, to_device, .. } => {
                tracing::info!("★★★ 收到连接请求: session_id={}, from={}, to={}", session_id, from_device, to_device);
                println!("★★★ 收到连接请求: session_id={}, from={}, to={}", session_id, from_device, to_device);
                
                // 启动屏幕流（先通过服务器中转，同时尝试建立 P2P）
                let session_id_clone = session_id.clone();
                let from_device_clone = from_device.clone();
                let device_id_stream = device_id_inner.clone();
                
                // 启动 P2P 地址交换
                let session_id_p2p = session_id.clone();
                let from_device_p2p = from_device.clone();
                let device_id_p2p = device_id_inner.clone();
                
                std::thread::spawn(move || {
                    let rt = tokio::runtime::Runtime::new().unwrap();
                    rt.block_on(async move {
                        // 先启动屏幕流（通过服务器中转）
                        start_screen_streaming(session_id_clone, from_device_clone.clone(), device_id_stream.clone()).await;
                        
                        // 初始化 P2P 监听器并发送地址交换消息
                        match init_p2p_listener().await {
                            Ok((local_port, local_ips)) => {
                                // 获取公网地址和 TURN 服务器信息
                                let signal_client_holder = get_signal_client().await;
                                let client = signal_client_holder.read().await;
                                if let Some(c) = client.as_ref() {
                                    // 获取服务器 URL
                                    if let Some(server_url) = c.server_url() {
                                        // 获取 ICE 服务器配置（包含 STUN 和 TURN）
                                        let (public_addr, turn_server) = get_ice_info(&server_url).await;
                                        
                                        // 发送 P2P 地址交换消息
                                        let exchange_msg = SignalMessage::P2PExchange {
                                            session_id: session_id_p2p,
                                            from_device: device_id_p2p,
                                            to_device: from_device_p2p,
                                            local_addrs: local_ips,
                                            public_addr,
                                            udp_port: local_port,
                                            turn_server,
                                        };
                                        let _ = c.send(exchange_msg).await;
                                        tracing::info!("★★★ 已发送 P2P 地址交换消息");
                                    }
                                }
                            }
                            Err(e) => {
                                tracing::warn!("初始化 P2P 监听器失败: {}", e);
                            }
                        }
                    });
                });
            }
            SignalMessage::MouseEvent { x, y, event_type, button, delta, .. } => {
                // 处理鼠标事件
                if event_type != "move" {
                    println!("★★★★★ [被控端] 收到鼠标事件: x={}, y={}, type={}, button={:?}", x, y, event_type, button);
                }
                handle_mouse_input(x, y, &event_type, button, delta);
            }
            SignalMessage::KeyboardEvent { key, event_type, .. } => {
                // 处理键盘事件
                println!("★★★★★ [被控端] 收到键盘事件: key={}, type={}", key, event_type);
                handle_keyboard_input(&key, &event_type);
            }
            SignalMessage::StreamSettings { resolution, fps, quality, .. } => {
                // 更新流媒体设置
                tracing::info!("更新流媒体设置: resolution={}, fps={}, quality={}", resolution, fps, quality);
                println!("★★★ 更新流媒体设置: resolution={}, fps={}, quality={}", resolution, fps, quality);
                let settings = get_stream_settings_sync();
                let mut s = settings.write().unwrap();
                s.resolution = resolution;
                s.fps = fps;
                s.quality = quality;
            }
            SignalMessage::GetDisplays { session_id, from_device, .. } => {
                // 获取显示器列表并返回
                println!("★★★ 收到获取显示器列表请求");
                use easyremote_client_core::capture::get_displays;
                match get_displays() {
                    Ok(displays) => {
                        println!("★★★ 获取到 {} 个显示器", displays.len());
                        for d in &displays {
                            println!("  - 显示器 {}: {}x{} (primary: {})", d.index, d.width, d.height, d.is_primary);
                        }
                        let display_msgs: Vec<_> = displays.iter().map(|d| {
                            easyremote_client_core::signal::DisplayInfoMsg {
                                index: d.index,
                                width: d.width,
                                height: d.height,
                                is_primary: d.is_primary,
                                name: format!("显示器 {} ({}x{})", d.index + 1, d.width, d.height),
                            }
                        }).collect();
                        
                        let display_count = display_msgs.len();
                        println!("★★★ 发送显示器列表响应: {} 个显示器", display_count);
                        
                        let response = SignalMessage::DisplaysList {
                            session_id,
                            from_device: device_id_inner.clone(),
                            to_device: from_device,
                            displays: display_msgs,
                            current_display: get_current_display(),
                        };
                        let signal_client_holder = SIGNAL_CLIENT.get();
                        if let Some(holder) = signal_client_holder {
                            let holder_clone = holder.clone();
                            tokio::spawn(async move {
                                let client = holder_clone.read().await;
                                if let Some(c) = client.as_ref() {
                                    let _ = c.send(response).await;
                                }
                            });
                        }
                    }
                    Err(e) => {
                        println!("★★★ 获取显示器列表失败: {}", e);
                    }
                }
            }
            SignalMessage::SwitchDisplay { display_index, from_device, session_id, .. } => {
                // 切换显示器
                println!("★★★ 收到切换显示器请求: display_index={}", display_index);
                set_current_display(display_index);
                
                // 停止当前屏幕流并重新启动
                let device_id_restart = device_id_inner.clone();
                let from_device_restart = from_device.clone();
                let session_id_restart = session_id.clone();
                
                tokio::spawn(async move {
                    // 停止当前会话
                    if let Some(holder) = ACTIVE_SESSION.get() {
                        let session = holder.read().await;
                        if let Some(s) = session.as_ref() {
                            s.stop_flag.store(true, Ordering::SeqCst);
                        }
                    }
                    
                    // 等待一会儿再重新启动
                    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
                    start_screen_streaming(session_id_restart, from_device_restart, device_id_restart).await;
                });
            }
            SignalMessage::SessionEnd { session_id } => {
                // 会话结束，停止屏幕流
                println!("★★★ 收到会话结束消息: session_id={}", session_id);
                tracing::info!("收到会话结束消息: session_id={}", session_id);
                
                // 停止屏幕流
                tokio::spawn(async move {
                    if let Some(holder) = ACTIVE_SESSION.get() {
                        let mut session = holder.write().await;
                        if let Some(s) = session.as_ref() {
                            s.stop_flag.store(true, Ordering::SeqCst);
                            println!("★★★ 已停止屏幕流会话");
                        }
                        *session = None;
                    }
                });
            }
            SignalMessage::P2PExchange { session_id, from_device, local_addrs, public_addr, udp_port, turn_server, .. } => {
                // 控制端收到被控端的地址信息，尝试建立 P2P 连接
                println!("★★★ 收到 P2P 地址交换: session_id={}, from={}", session_id, from_device);
                println!("    本地地址: {:?}", local_addrs);
                println!("    公网地址: {:?}", public_addr);
                println!("    UDP 端口: {}", udp_port);
                if let Some(ref turn) = turn_server {
                    println!("    TURN 服务器: {}", turn.url);
                }
                
                let device_id_p2p = device_id_inner.clone();
                let session_id_p2p = session_id.clone();
                let from_device_p2p = from_device.clone();
                let turn_info = turn_server.clone();
                
                tokio::spawn(async move {
                    // 尝试连接到对方的各个地址
                    let mut connected = false;
                    let mut connected_addr = String::new();
                    let mut connection_type = "p2p"; // "p2p" 或 "relay"
                    
                    // 创建本地 UDP socket
                    if let Ok(socket) = UdpSocket::bind("0.0.0.0:0").await {
                        // 首先尝试局域网地址（延迟最低）
                        for ip in &local_addrs {
                            let addr = format!("{}:{}", ip, udp_port);
                            if let Ok(target) = addr.parse::<SocketAddr>() {
                                println!("★★★ 尝试连接局域网地址: {}", addr);
                                if try_p2p_connect(&socket, target).await {
                                    connected = true;
                                    connected_addr = addr;
                                    connection_type = "p2p-lan";
                                    println!("★★★ P2P 局域网连接成功: {}", connected_addr);
                                    break;
                                }
                            }
                        }
                        
                        // 如果局域网连接失败，尝试公网地址（UDP 打洞）
                        if !connected {
                            if let Some(ref public) = public_addr {
                                // 公网地址可能是 "ip:port" 格式
                                let addr = if public.contains(':') {
                                    // 替换端口
                                    let parts: Vec<&str> = public.split(':').collect();
                                    format!("{}:{}", parts[0], udp_port)
                                } else {
                                    format!("{}:{}", public, udp_port)
                                };
                                
                                if let Ok(target) = addr.parse::<SocketAddr>() {
                                    println!("★★★ 尝试连接公网地址: {}", addr);
                                    if try_p2p_connect(&socket, target).await {
                                        connected = true;
                                        connected_addr = addr;
                                        connection_type = "p2p-wan";
                                        println!("★★★ P2P 公网连接成功: {}", connected_addr);
                                    }
                                }
                            }
                        }
                    }
                    
                    // 如果 P2P 直连失败，记录 TURN 服务器信息（备用）
                    if !connected {
                        if let Some(ref turn) = turn_info {
                            println!("★★★ P2P 直连失败，可使用 TURN 中继: {}", turn.url);
                            tracing::info!("P2P 直连失败，TURN 服务器可用: {}", turn.url);
                            // 注意：当前使用服务器 WebSocket 中转，TURN 中继可用于未来优化
                        }
                    }
                    
                    // 发送连接结果
                    let signal_client_holder = get_signal_client().await;
                    let client = signal_client_holder.read().await;
                    if let Some(c) = client.as_ref() {
                        if connected {
                            let msg = SignalMessage::P2PReady {
                                session_id: session_id_p2p,
                                from_device: device_id_p2p,
                                to_device: from_device_p2p,
                                connected_addr: format!("{} ({})", connected_addr, connection_type),
                            };
                            let _ = c.send(msg).await;
                        } else {
                            let msg = SignalMessage::P2PFallback {
                                session_id: session_id_p2p,
                                from_device: device_id_p2p,
                                to_device: from_device_p2p,
                                reason: "无法建立 P2P 连接，使用服务器中转".to_string(),
                            };
                            let _ = c.send(msg).await;
                        }
                    }
                });
            }
            SignalMessage::P2PReady { session_id, from_device, connected_addr, .. } => {
                println!("★★★ P2P 连接就绪: session_id={}, from={}, addr={}", session_id, from_device, connected_addr);
                tracing::info!("P2P 连接就绪: 对方设备={}, 地址={}", from_device, connected_addr);
                // TODO: 切换到 P2P 传输
            }
            SignalMessage::P2PFallback { session_id, from_device, reason, .. } => {
                println!("★★★ P2P 连接失败，使用服务器中转: session_id={}, from={}, reason={}", session_id, from_device, reason);
                tracing::info!("P2P 连接失败，继续使用服务器中转: {}", reason);
            }
            _ => {
                tracing::debug!("收到信令消息: {:?}", msg);
            }
        }
    }).await {
        Ok(_) => {
            tracing::info!("已连接到信令服务器");
            
            // 存储客户端
            let mut holder = signal_client_holder.write().await;
            *holder = Some(signal_client);
            
            // 启动心跳任务
            let holder_clone = signal_client_holder.clone();
            let device_id_for_heartbeat = device_id.clone();
            tokio::spawn(async move {
                let mut interval = tokio::time::interval(std::time::Duration::from_secs(30));
                loop {
                    interval.tick().await;
                    let holder = holder_clone.read().await;
                    if let Some(client) = holder.as_ref() {
                        if client.is_connected().await {
                            if let Err(e) = client.send_heartbeat().await {
                                tracing::warn!("发送心跳失败: {}", e);
                                break;
                            }
                            tracing::debug!("发送心跳");
                        } else {
                            tracing::info!("信令连接已断开");
                            break;
                        }
                    } else {
                        break;
                    }
                }
            });
            
            Ok(())
        }
        Err(e) => {
            tracing::warn!("连接信令服务器失败: {}", e);
            Err(format!("连接信令服务器失败: {}", e))
        }
    }
}

/// 初始化 - 验证 token、注册设备并连接信令服务器
#[tauri::command]
pub async fn initialize(state: State<'_, AppStateHandle>) -> Result<bool, String> {
    let mut app_state = state.write().await;
    let mut logged_in = false;
    
    // 如果有保存的 token，验证是否仍然有效
    if let Some(token) = &app_state.auth_token {
        let client = reqwest::Client::new();
        let server_url = app_state.config.server_url.replace("ws://", "http://").replace("wss://", "https://");
        
        let response = client
            .get(format!("{}/api/users/me", server_url))
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await;
        
        match response {
            Ok(resp) if resp.status().is_success() => {
                tracing::info!("Token 验证成功，用户已登录");
                logged_in = true;
            }
            _ => {
                tracing::info!("Token 已过期，需要重新登录");
                app_state.current_user = None;
                app_state.auth_token = None;
                app_state.clear_auth_data();
            }
        }
    }
    
    // 获取必要信息
    let device_id = app_state.device_id.0.clone();
    let device_name = app_state.config.device_name.clone();
    let server_url = app_state.config.server_url.clone();
    let token = app_state.auth_token.clone();
    
    drop(app_state); // 释放锁
    
    // 注册设备到服务端
    let _ = register_device_to_server(
        &server_url,
        &device_id,
        &device_name,
        token.as_deref(),
    ).await;
    
    // 连接到信令服务器（WebSocket）- 这会使设备显示为在线
    let _ = connect_to_signal_server(device_id, server_url).await;
    
    Ok(logged_in)
}

/// 获取设备信息
#[tauri::command]
pub async fn get_device_info(state: State<'_, AppStateHandle>) -> Result<DeviceInfo, String> {
    let state = state.read().await;

    Ok(DeviceInfo {
        device_id: state.device_id.0.clone(),
        device_id_formatted: state.device_id.formatted(),
        verification_code: state.verification_code.0.clone(),
        device_name: state.config.device_name.clone(),
        os_type: get_os_type().to_string(),
    })
}

/// 刷新验证码
#[tauri::command]
pub async fn refresh_verification_code(state: State<'_, AppStateHandle>) -> Result<String, String> {
    let mut state = state.write().await;
    Ok(state.refresh_code())
}

/// 用户登录
#[tauri::command]
pub async fn login(
    state: State<'_, AppStateHandle>,
    username: String,
    password: String,
) -> Result<CurrentUser, String> {
    let mut app_state = state.write().await;
    
    // 构建登录请求
    let client = reqwest::Client::new();
    let server_url = app_state.config.server_url.replace("ws://", "http://").replace("wss://", "https://");
    
    let response = client
        .post(format!("{}/api/auth/login", server_url))
        .json(&serde_json::json!({
            "username": username,
            "password": password,
            "device_id": app_state.device_id.0
        }))
        .send()
        .await
        .map_err(|e| e.to_string())?;

    if !response.status().is_success() {
        let error: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
        return Err(error["error"].as_str().unwrap_or("登录失败").to_string());
    }

    let result: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
    let data = &result["data"];
    
    let user = CurrentUser {
        id: data["user"]["id"].as_str().unwrap_or("").to_string(),
        username: data["user"]["username"].as_str().unwrap_or("").to_string(),
        email: data["user"]["email"].as_str().map(|s| s.to_string()),
    };
    let token = data["token"].as_str().unwrap_or("").to_string();

    app_state.set_user(user.clone(), token.clone());
    
    // 登录成功后注册设备到服务端（绑定用户）
    let device_id = app_state.device_id.0.clone();
    let device_name = app_state.config.device_name.clone();
    
    drop(app_state);
    
    let _ = register_device_to_server(&server_url, &device_id, &device_name, Some(&token)).await;
    
    Ok(user)
}

/// 用户注册
#[tauri::command]
pub async fn register(
    state: State<'_, AppStateHandle>,
    username: String,
    password: String,
    email: Option<String>,
) -> Result<CurrentUser, String> {
    let mut app_state = state.write().await;
    
    let client = reqwest::Client::new();
    let server_url = &app_state.config.server_url.replace("ws://", "http://").replace("wss://", "https://");
    
    let response = client
        .post(format!("{}/api/auth/register", server_url))
        .json(&serde_json::json!({
            "username": username,
            "password": password,
            "email": email
        }))
        .send()
        .await
        .map_err(|e| e.to_string())?;

    if !response.status().is_success() {
        let error: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
        return Err(error["error"].as_str().unwrap_or("注册失败").to_string());
    }

    let result: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
    let data = &result["data"];
    
    let user = CurrentUser {
        id: data["user"]["id"].as_str().unwrap_or("").to_string(),
        username: data["user"]["username"].as_str().unwrap_or("").to_string(),
        email: data["user"]["email"].as_str().map(|s| s.to_string()),
    };
    let token = data["token"].as_str().unwrap_or("").to_string();

    app_state.set_user(user.clone(), token);
    Ok(user)
}

/// 用户退出登录
#[tauri::command]
pub async fn logout(state: State<'_, AppStateHandle>) -> Result<(), String> {
    let mut state = state.write().await;
    state.logout();
    Ok(())
}

/// 获取当前用户
#[tauri::command]
pub async fn get_current_user(state: State<'_, AppStateHandle>) -> Result<Option<CurrentUser>, String> {
    let state = state.read().await;
    Ok(state.current_user.clone())
}

/// 获取用户设备列表
#[tauri::command]
pub async fn get_devices(state: State<'_, AppStateHandle>) -> Result<Vec<serde_json::Value>, String> {
    let app_state = state.read().await;
    
    let token = app_state.auth_token.as_ref()
        .ok_or("未登录")?;
    
    let client = reqwest::Client::new();
    let server_url = &app_state.config.server_url.replace("ws://", "http://").replace("wss://", "https://");
    
    let response = client
        .get(format!("{}/api/devices", server_url))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .map_err(|e| e.to_string())?;

    if !response.status().is_success() {
        return Err("获取设备列表失败".to_string());
    }

    let result: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
    let devices = result["data"].as_array()
        .map(|arr| arr.clone())
        .unwrap_or_default();
    
    Ok(devices)
}

/// 移除设备
#[tauri::command]
pub async fn remove_device(
    state: State<'_, AppStateHandle>,
    device_id: String,
) -> Result<(), String> {
    let app_state = state.read().await;
    
    let token = app_state.auth_token.as_ref()
        .ok_or("未登录")?;
    
    let client = reqwest::Client::new();
    let server_url = &app_state.config.server_url.replace("ws://", "http://").replace("wss://", "https://");
    
    let response = client
        .delete(format!("{}/api/devices/{}", server_url, device_id))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .map_err(|e| e.to_string())?;

    if !response.status().is_success() {
        return Err("移除设备失败".to_string());
    }

    Ok(())
}

/// 连接到目标设备（保留用于兼容性，实际连接通过前端 WebSocket 实现）
#[tauri::command]
pub async fn connect_to_device(
    state: State<'_, AppStateHandle>,
    request: ConnectRequest,
) -> Result<(), String> {
    let mut app_state = state.write().await;
    
    app_state.connection_state = ConnectionState::Connected {
        target_device_id: request.device_id.clone(),
        target_device_name: format!("设备 {}", request.device_id),
        connection_type: "P2P".to_string(),
    };
    
    Ok(())
}

/// 打开远程控制窗口
#[tauri::command]
pub async fn open_remote_window(
    app_handle: tauri::AppHandle,
    device_id: String,
    device_name: String,
    server_url: String,
) -> Result<(), String> {
    use tauri::Manager;
    
    println!("★★★ open_remote_window: device_id={}, device_name={}, server_url={}", device_id, device_name, server_url);
    
    // 移除 device_id 中的空格和横杠，生成安全的窗口标签
    let safe_id = device_id.chars().filter(|c| c.is_alphanumeric()).collect::<String>();
    let window_label = format!("remote_{}", safe_id);
    
    // 检查窗口是否已存在
    if let Some(existing) = app_handle.get_window(&window_label) {
        println!("★★★ 窗口已存在，聚焦");
        let _ = existing.set_focus();
        return Ok(());
    }
    
    // 构建远程控制页面的 HTML
    let html_content = generate_remote_html(&device_id, &device_name, &server_url);
    
    println!("★★★ 创建新窗口...");
    
    // 创建一个空白页面的窗口
    let window = match tauri::WindowBuilder::new(
        &app_handle,
        &window_label,
        tauri::WindowUrl::App("about:blank".into())
    )
    .title(format!("远程控制 - {}", device_name))
    .inner_size(1280.0, 720.0)
    .min_inner_size(800.0, 600.0)
    .center()
    .resizable(true)
    .decorations(true)
    .build() {
        Ok(w) => w,
        Err(e) => {
            println!("★★★ 创建窗口失败: {}", e);
            return Err(e.to_string());
        }
    };
    
    println!("★★★ 窗口创建成功，注入 HTML...");
    
    // 使用 eval 注入 HTML 内容
    let escaped_html = html_content
        .replace('\\', "\\\\")
        .replace('`', "\\`")
        .replace("${", "\\${");
    
    let script = format!(
        r#"document.open(); document.write(`{}`); document.close();"#,
        escaped_html
    );
    
    // 延迟注入，确保窗口准备好
    let window_clone = window.clone();
    std::thread::spawn(move || {
        std::thread::sleep(std::time::Duration::from_millis(500));
        if let Err(e) = window_clone.eval(&script) {
            println!("★★★ 注入 HTML 失败: {}", e);
        } else {
            println!("★★★ HTML 注入成功");
        }
    });
    
    // 聚焦新窗口
    let _ = window.set_focus();
    
    tracing::info!("打开远程控制窗口: device_id={}, device_name={}", device_id, device_name);
    
    Ok(())
}

/// 生成远程控制页面的 HTML (参考 UU 远程布局 - 优化版)
fn generate_remote_html(device_id: &str, device_name: &str, server_url: &str) -> String {
    format!(r#"<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>远程控制 - {device_name}</title>
  <style>
    * {{ margin: 0; padding: 0; box-sizing: border-box; }}
    :root {{
      --bg-primary: #0a0a0a;
      --bg-secondary: #141414;
      --bg-tertiary: #1e1e1e;
      --bg-panel: rgba(20, 20, 20, 0.92);
      --text-primary: #f0f0f0;
      --text-secondary: #9ca3af;
      --text-muted: #6b7280;
      --primary: #3b82f6;
      --primary-glow: rgba(59, 130, 246, 0.3);
      --success: #22c55e;
      --success-glow: rgba(34, 197, 94, 0.3);
      --warning: #eab308;
      --error: #ef4444;
      --border-color: rgba(255, 255, 255, 0.08);
      --border-light: rgba(255, 255, 255, 0.12);
    }}
    body {{
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
      background: var(--bg-primary);
      color: var(--text-primary);
      overflow: hidden;
      height: 100vh;
      display: flex;
      flex-direction: column;
    }}
    
    /* 顶部标签栏 - 类似浏览器标签页 */
    .tab-bar {{
      display: flex;
      align-items: center;
      background: var(--bg-secondary);
      border-bottom: 1px solid var(--border-color);
      height: 38px;
      flex-shrink: 0;
      -webkit-app-region: drag;
    }}
    .tabs {{
      display: flex;
      align-items: stretch;
      height: 100%;
      flex: 1;
      -webkit-app-region: no-drag;
    }}
    .tab {{
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 0 14px;
      background: transparent;
      border: none;
      border-right: 1px solid var(--border-color);
      color: var(--text-secondary);
      font-size: 12px;
      cursor: pointer;
      transition: all 0.15s;
      max-width: 180px;
      min-width: 100px;
    }}
    .tab:hover {{
      background: var(--bg-tertiary);
      color: var(--text-primary);
    }}
    .tab.active {{
      background: var(--bg-primary);
      color: var(--text-primary);
      border-bottom: 2px solid var(--primary);
      margin-bottom: -1px;
    }}
    .tab-icon {{
      width: 14px;
      height: 14px;
      color: var(--text-muted);
      flex-shrink: 0;
    }}
    .tab-title {{
      flex: 1;
      text-align: left;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }}
    .tab-close {{
      width: 16px;
      height: 16px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 3px;
      color: var(--text-muted);
      opacity: 0;
      font-size: 14px;
      transition: all 0.15s;
    }}
    .tab:hover .tab-close {{ opacity: 1; }}
    .tab-close:hover {{
      background: rgba(239, 68, 68, 0.2);
      color: var(--error);
    }}
    .tab-add {{
      width: 28px;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      background: transparent;
      border: none;
      color: var(--text-muted);
      cursor: pointer;
      font-size: 16px;
      transition: all 0.15s;
      -webkit-app-region: no-drag;
    }}
    .tab-add:hover {{
      background: var(--bg-tertiary);
      color: var(--text-primary);
    }}
    
    /* 连接状态指示器 */
    .connection-status {{
      display: flex;
      align-items: center;
      gap: 12px;
      padding: 0 16px;
      color: var(--text-secondary);
      font-size: 12px;
      -webkit-app-region: no-drag;
    }}
    .status-indicator {{
      display: flex;
      align-items: center;
      gap: 6px;
    }}
    .status-dot {{
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--success);
      box-shadow: 0 0 8px var(--success-glow);
      animation: pulse 2s infinite;
    }}
    .status-dot.connecting {{
      background: var(--warning);
      box-shadow: 0 0 8px rgba(234, 179, 8, 0.3);
    }}
    .status-dot.error {{
      background: var(--error);
      box-shadow: 0 0 8px rgba(239, 68, 68, 0.3);
      animation: none;
    }}
    @keyframes pulse {{
      0%, 100% {{ opacity: 1; }}
      50% {{ opacity: 0.5; }}
    }}
    .duration-text {{
      font-family: 'JetBrains Mono', 'Consolas', monospace;
      font-size: 12px;
      color: var(--text-primary);
      font-weight: 500;
    }}
    
    /* 右侧控制区 */
    .controls {{
      display: flex;
      align-items: center;
      gap: 6px;
      padding: 0 10px;
      -webkit-app-region: no-drag;
    }}
    .control-btn {{
      display: flex;
      align-items: center;
      gap: 5px;
      padding: 5px 10px;
      background: var(--bg-tertiary);
      border: 1px solid var(--border-color);
      border-radius: 5px;
      color: var(--text-secondary);
      font-size: 11px;
      cursor: pointer;
      transition: all 0.15s;
    }}
    .control-btn:hover {{
      background: var(--bg-panel);
      color: var(--text-primary);
      border-color: var(--border-light);
    }}
    .control-btn svg {{
      width: 13px;
      height: 13px;
    }}
    
    /* 窗口控制按钮 */
    .window-controls {{
      display: flex;
      align-items: center;
      -webkit-app-region: no-drag;
    }}
    .window-btn {{
      width: 44px;
      height: 38px;
      display: flex;
      align-items: center;
      justify-content: center;
      background: transparent;
      border: none;
      color: var(--text-muted);
      cursor: pointer;
      transition: all 0.15s;
    }}
    .window-btn:hover {{
      background: var(--bg-tertiary);
      color: var(--text-primary);
    }}
    .window-btn.close:hover {{
      background: var(--error);
      color: white;
    }}
    .window-btn svg {{
      width: 11px;
      height: 11px;
    }}
    
    /* 画布区域 */
    .canvas-wrapper {{
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      background: #000;
      position: relative;
      overflow: hidden;
    }}
    #canvasContainer {{
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
    }}
    .remote-canvas {{
      max-width: 100%;
      max-height: 100%;
      cursor: default;
      outline: none;
      display: none;
      position: absolute;
    }}
    .remote-canvas.active {{
      display: block;
    }}
    .remote-canvas:focus {{
      outline: none;
    }}
    .switch-toast {{
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      padding: 12px 24px;
      background: rgba(59, 130, 246, 0.9);
      border-radius: 8px;
      color: white;
      font-size: 14px;
      font-weight: 500;
      z-index: 60;
      pointer-events: none;
      transition: opacity 0.3s;
    }}
    .switch-toast.hidden {{
      opacity: 0;
    }}
    
    /* 右下角统计面板 - 优化版 */
    .stats-panel {{
      position: fixed;
      right: 12px;
      bottom: 12px;
      background: var(--bg-panel);
      border: 1px solid var(--border-color);
      border-radius: 6px;
      padding: 10px 14px;
      font-family: 'JetBrains Mono', 'Consolas', monospace;
      font-size: 11px;
      z-index: 100;
      min-width: 130px;
      backdrop-filter: blur(12px);
    }}
    .stats-panel.hidden {{
      display: none;
    }}
    .stat-row {{
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 2px 0;
      color: var(--text-muted);
    }}
    .stat-row .label {{
      font-size: 10px;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }}
    .stat-row .value {{
      color: var(--text-primary);
      font-weight: 500;
      font-size: 11px;
    }}
    .stat-row .value.good {{ color: var(--success); }}
    .stat-row .value.medium {{ color: var(--warning); }}
    .stat-row .value.bad {{ color: var(--error); }}
    .stat-row .value.p2p {{ 
      color: var(--success); 
      text-shadow: 0 0 6px var(--success-glow);
    }}
    .stat-row .value.relay {{ color: var(--primary); }}
    .stats-divider {{
      height: 1px;
      background: var(--border-color);
      margin: 6px 0;
    }}
    
    /* 右侧工具栏 */
    .bottom-toolbar {{
      position: fixed;
      right: 12px;
      bottom: 170px;
      display: flex;
      flex-direction: column;
      gap: 6px;
      z-index: 100;
    }}
    .tool-btn {{
      width: 32px;
      height: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      background: var(--bg-panel);
      border: 1px solid var(--border-color);
      border-radius: 6px;
      color: var(--text-secondary);
      cursor: pointer;
      transition: all 0.15s;
      backdrop-filter: blur(12px);
    }}
    .tool-btn:hover {{
      background: var(--bg-tertiary);
      color: var(--text-primary);
      border-color: var(--primary);
    }}
    .tool-btn.active {{
      background: var(--primary);
      color: white;
      border-color: var(--primary);
      box-shadow: 0 0 12px var(--primary-glow);
    }}
    .tool-btn svg {{
      width: 16px;
      height: 16px;
    }}
    
    /* 遮罩层 */
    .overlay {{
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      background: rgba(0, 0, 0, 0.92);
      color: var(--text-secondary);
      z-index: 50;
    }}
    .overlay.hidden {{ display: none; }}
    .spinner {{
      width: 44px;
      height: 44px;
      border: 3px solid rgba(255, 255, 255, 0.08);
      border-top-color: var(--primary);
      border-radius: 50%;
      animation: spin 0.8s linear infinite;
      margin-bottom: 18px;
    }}
    @keyframes spin {{ to {{ transform: rotate(360deg); }} }}
    .overlay-title {{
      font-size: 16px;
      font-weight: 600;
      color: var(--text-primary);
      margin-bottom: 6px;
    }}
    .overlay-text {{
      font-size: 13px;
      color: var(--text-secondary);
      margin-bottom: 18px;
    }}
    .overlay-status {{
      font-size: 11px;
      color: var(--text-muted);
      margin-top: 12px;
      font-family: 'JetBrains Mono', 'Consolas', monospace;
    }}
    .btn-action {{
      padding: 9px 22px;
      background: var(--primary);
      border: none;
      color: white;
      font-size: 13px;
      font-weight: 500;
      cursor: pointer;
      border-radius: 6px;
      transition: all 0.15s;
    }}
    .btn-action:hover {{ 
      background: #2563eb;
      box-shadow: 0 0 16px var(--primary-glow);
    }}
    .btn-action.secondary {{
      background: transparent;
      border: 1px solid var(--border-color);
      color: var(--text-secondary);
      margin-left: 10px;
    }}
    .btn-action.secondary:hover {{
      background: var(--bg-tertiary);
      color: var(--text-primary);
    }}
    
    /* 全屏模式 */
    body.fullscreen .tab-bar {{
      position: fixed;
      top: -48px;
      left: 0;
      right: 0;
      transition: top 0.3s ease;
      z-index: 200;
    }}
    body.fullscreen .tab-bar:hover,
    body.fullscreen .tab-bar.show {{
      top: 0;
    }}
    body.fullscreen .canvas-wrapper {{
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
    }}
    body.fullscreen .remote-canvas.active {{
      max-width: none;
      max-height: none;
      width: 100%;
      height: 100%;
      object-fit: contain;
    }}
    
    /* Toast 提示 */
    .toast {{
      position: fixed;
      top: 56px;
      left: 50%;
      transform: translateX(-50%) translateY(-16px);
      padding: 9px 18px;
      background: var(--bg-panel);
      border: 1px solid var(--border-color);
      border-radius: 5px;
      color: var(--text-primary);
      font-size: 12px;
      z-index: 1000;
      opacity: 0;
      transition: all 0.3s ease;
      backdrop-filter: blur(12px);
    }}
    .toast.show {{
      transform: translateX(-50%) translateY(0);
      opacity: 1;
    }}
    .toast.success {{ border-color: var(--success); color: var(--success); }}
    .toast.error {{ border-color: var(--error); color: var(--error); }}
  </style>
</head>
<body>
  <!-- 顶部标签栏 -->
  <div class="tab-bar" id="tabBar">
    <div class="tabs" id="displayTabs">
      <button class="tab active" id="tab0" data-index="0">
        <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
          <line x1="8" y1="21" x2="16" y2="21"/>
          <line x1="12" y1="17" x2="12" y2="21"/>
        </svg>
        <span class="tab-title">显示屏 1</span>
        <span class="tab-close" title="关闭">×</span>
      </button>
    </div>
    <button class="tab-add" id="btnAddTab" title="添加连接">+</button>
    
    <!-- 连接状态和时长 -->
    <div class="connection-status">
      <div class="status-indicator">
        <span class="status-dot connecting" id="statusDot"></span>
        <span id="statusText">连接中</span>
      </div>
      <span class="duration-text" id="durationText">00:00:00</span>
    </div>
    
    <div class="controls">
      <button class="control-btn" id="btnControlCenter">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <circle cx="12" cy="12" r="3"/>
          <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
        </svg>
        控制中心
      </button>
    </div>
    
    <div class="window-controls">
      <button class="window-btn" id="btnMinimize" title="最小化">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/></svg>
      </button>
      <button class="window-btn" id="btnMaximize" title="最大化">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="2"/></svg>
      </button>
      <button class="window-btn close" id="btnClose" title="关闭">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
      </button>
    </div>
  </div>
  
  <div class="canvas-wrapper">
    <!-- 多显示器画布容器 -->
    <div id="canvasContainer">
      <canvas id="remoteCanvas0" class="remote-canvas active" tabindex="0" data-display="0"></canvas>
    </div>
    
    <!-- 切换提示 -->
    <div class="switch-toast hidden" id="switchToast">已切换到 显示器 1</div>
    
    <!-- 连接中遮罩 -->
    <div class="overlay" id="connectingOverlay">
      <div class="spinner"></div>
      <p class="overlay-title">正在连接</p>
      <p class="overlay-text">正在连接到 {device_name}...</p>
      <p class="overlay-status" id="connectStatus">正在建立 WebSocket 连接...</p>
    </div>
    
    <!-- 断开连接遮罩 -->
    <div class="overlay hidden" id="disconnectedOverlay">
      <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--error); margin-bottom: 20px;">
        <line x1="1" y1="1" x2="23" y2="23"/>
        <path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"/>
        <path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"/>
        <path d="M10.71 5.05A16 16 0 0 1 22.58 9"/>
        <path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"/>
        <path d="M8.53 16.11a6 6 0 0 1 6.95 0"/>
        <line x1="12" y1="20" x2="12.01" y2="20"/>
      </svg>
      <p class="overlay-title" id="disconnectTitle">连接已断开</p>
      <p class="overlay-text" id="disconnectText">与远程设备的连接已中断</p>
      <div style="display: flex; gap: 12px;">
        <button class="btn-action" id="btnRetry">重新连接</button>
        <button class="btn-action secondary" id="btnCloseOverlay">关闭</button>
      </div>
    </div>
  </div>
  
  <!-- 右下角统计面板 - 优化版 -->
  <div class="stats-panel" id="statsPanel">
    <div class="stat-row"><span class="label">时长</span><span class="value" id="statDuration">00:00:00</span></div>
    <div class="stat-row"><span class="label">连接</span><span class="value relay" id="statConnType">TCP relay</span></div>
    <div class="stats-divider"></div>
    <div class="stat-row"><span class="label">帧率</span><span class="value" id="statFps">-- fps</span></div>
    <div class="stat-row"><span class="label">码率</span><span class="value" id="statBandwidth">-- Mbps</span></div>
    <div class="stat-row"><span class="label">延迟</span><span class="value" id="statLatency">-- ms</span></div>
    <div class="stat-row"><span class="label">帧延</span><span class="value" id="statFrameLatency">-- ms frm.</span></div>
    <div class="stat-row"><span class="label">丢包</span><span class="value good" id="statLoss">0.0%</span></div>
    <div class="stats-divider"></div>
    <div class="stat-row"><span class="label">分辨率</span><span class="value" id="statResolution">-- auto</span></div>
  </div>
  
  <!-- 右侧工具栏 -->
  <div class="bottom-toolbar" id="bottomToolbar">
    <button class="tool-btn" id="btnFullscreen" title="全屏 (F11)">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
      </svg>
    </button>
    <button class="tool-btn" id="btnStats" title="显示统计">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M18 20V10M12 20V4M6 20v-6"/>
      </svg>
    </button>
    <button class="tool-btn" id="btnKeyboard" title="发送按键">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <rect x="2" y="4" width="20" height="16" rx="2" ry="2"/>
        <path d="M6 8h.001M10 8h.001M14 8h.001M18 8h.001M8 12h.001M12 12h.001M16 12h.001M7 16h10"/>
      </svg>
    </button>
  </div>
  
  <div class="toast" id="toast"></div>

  <script>
    const deviceId = '{device_id}';
    const deviceName = '{device_name}';
    const serverUrl = '{server_url}';
    
    // DOM 元素
    const canvasContainer = document.getElementById('canvasContainer');
    const displayTabs = document.getElementById('displayTabs');
    const statsPanel = document.getElementById('statsPanel');
    const statFps = document.getElementById('statFps');
    const statLatency = document.getElementById('statLatency');
    const statConnType = document.getElementById('statConnType');
    const statBandwidth = document.getElementById('statBandwidth');
    const statFrameLatency = document.getElementById('statFrameLatency');
    const statLoss = document.getElementById('statLoss');
    const statResolution = document.getElementById('statResolution');
    const statDuration = document.getElementById('statDuration');
    const durationText = document.getElementById('durationText');
    const connectingOverlay = document.getElementById('connectingOverlay');
    const disconnectedOverlay = document.getElementById('disconnectedOverlay');
    const disconnectTitle = document.getElementById('disconnectTitle');
    const disconnectText = document.getElementById('disconnectText');
    const tabBar = document.getElementById('tabBar');
    const toast = document.getElementById('toast');
    const statusDot = document.getElementById('statusDot');
    const statusText = document.getElementById('statusText');
    const connectStatus = document.getElementById('connectStatus');
    const switchToast = document.getElementById('switchToast');
    
    // 多画布管理
    let canvases = {{}};  // display_index -> {{ canvas, ctx }}
    let activeDisplayIndex = 0;
    
    // 状态变量
    let ws = null;
    let frameCount = 0;
    let lastFrameCount = 0;
    let isFullscreen = false;
    let displays = [];
    let currentDisplay = 0;
    let latencySum = 0;
    let latencyCount = 0;
    let pingStartTime = 0;
    let lastPing = 0;
    let isP2P = false;
    let connectionMode = 'relay'; // 'relay', 'p2p-lan', 'p2p-wan', 'turn'
    let reconnectAttempts = 0;
    let connectionStartTime = null;
    let totalBytes = 0;
    let lastTotalBytes = 0;
    let showStats = true;
    let lastResolution = {{}};  // 每个显示器独立的分辨率
    const MAX_RECONNECT_ATTEMPTS = 3;
    
    // 获取或创建画布
    function getOrCreateCanvas(displayIndex) {{
      if (!canvases[displayIndex]) {{
        const canvas = document.createElement('canvas');
        canvas.id = 'remoteCanvas' + displayIndex;
        canvas.className = 'remote-canvas' + (displayIndex === activeDisplayIndex ? ' active' : '');
        canvas.tabIndex = 0;
        canvas.dataset.display = displayIndex;
        canvasContainer.appendChild(canvas);
        
        const ctx = canvas.getContext('2d');
        canvases[displayIndex] = {{ canvas, ctx }};
        
        // 绑定事件
        canvas.addEventListener('mousemove', (e) => sendMouseEvent(e, 'move', displayIndex));
        canvas.addEventListener('mousedown', (e) => {{ sendMouseEvent(e, 'down', displayIndex); canvas.focus(); }});
        canvas.addEventListener('mouseup', (e) => sendMouseEvent(e, 'up', displayIndex));
        canvas.addEventListener('click', (e) => sendMouseEvent(e, 'click', displayIndex));
        canvas.addEventListener('wheel', (e) => sendWheelEvent(e, displayIndex), {{ passive: false }});
        canvas.addEventListener('keydown', (e) => sendKeyboardEvent(e, 'down'));
        canvas.addEventListener('keyup', (e) => sendKeyboardEvent(e, 'up'));
        canvas.addEventListener('contextmenu', (e) => e.preventDefault());
      }}
      return canvases[displayIndex];
    }}
    
    // 切换活动画布
    function setActiveCanvas(displayIndex) {{
      activeDisplayIndex = displayIndex;
      Object.keys(canvases).forEach(idx => {{
        const c = canvases[idx];
        if (parseInt(idx) === displayIndex) {{
          c.canvas.classList.add('active');
          c.canvas.focus();
        }} else {{
          c.canvas.classList.remove('active');
        }}
      }});
    }}
    
    // 初始化默认画布
    getOrCreateCanvas(0);
    
    // 更新连接状态显示
    function updateConnectionStatus(status, mode) {{
      statusDot.className = 'status-dot';
      switch(status) {{
        case 'connecting':
          statusDot.classList.add('connecting');
          statusText.textContent = '连接中';
          break;
        case 'connected':
          statusText.textContent = mode === 'p2p-lan' ? 'P2P 局域网' : 
                                   mode === 'p2p-wan' ? 'P2P 广域网' : 
                                   mode === 'turn' ? 'TURN 中继' : 'TCP 中转';
          break;
        case 'error':
          statusDot.classList.add('error');
          statusText.textContent = '已断开';
          break;
      }}
    }}
    
    // 更新连接模式显示
    function setConnectionMode(mode) {{
      connectionMode = mode;
      isP2P = mode.startsWith('p2p');
      
      const modeText = mode === 'p2p-lan' ? 'P2P direct (LAN)' :
                       mode === 'p2p-wan' ? 'P2P direct (WAN)' :
                       mode === 'turn' ? 'TURN relay' : 'TCP relay';
      
      statConnType.textContent = modeText;
      statConnType.className = 'value ' + (isP2P ? 'p2p' : 'relay');
      
      updateConnectionStatus('connected', mode);
      
      if (isP2P) {{
        showToast('已建立 P2P 直连 - ' + (mode === 'p2p-lan' ? '局域网' : '广域网'), 'success');
      }}
    }}
    
    // 显示 Toast 提示
    function showToast(message, type = '') {{
      toast.textContent = message;
      toast.className = 'toast' + (type ? ' ' + type : '');
      toast.classList.add('show');
      setTimeout(() => toast.classList.remove('show'), 3000);
    }}
    
    // 格式化时间
    function formatDuration(ms) {{
      const s = Math.floor(ms / 1000);
      const h = Math.floor(s / 3600);
      const m = Math.floor((s % 3600) / 60);
      const sec = s % 60;
      return String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0') + ':' + String(sec).padStart(2, '0');
    }}
    
    // 更新连接时长
    function updateDuration() {{
      if (connectionStartTime) {{
        const d = formatDuration(Date.now() - connectionStartTime);
        durationText.textContent = d;
        statDuration.textContent = d;
      }}
    }}
    
    // 更新统计信息
    function updateStats() {{
      const fps = frameCount - lastFrameCount;
      lastFrameCount = frameCount;
      
      // FPS
      statFps.textContent = fps + ' fps';
      statFps.className = 'value ' + (fps >= 20 ? 'good' : fps >= 10 ? 'medium' : 'bad');
      
      // 带宽
      const bytesPerSec = totalBytes - lastTotalBytes;
      lastTotalBytes = totalBytes;
      const mbps = (bytesPerSec * 8 / 1024 / 1024).toFixed(1);
      statBandwidth.textContent = mbps + ' Mbps';
      
      // 平均延迟
      if (latencyCount > 0) {{
        const avgLatency = Math.round(latencySum / latencyCount);
        statLatency.textContent = avgLatency + ' ms';
        statLatency.className = 'value ' + (avgLatency < 50 ? 'good' : avgLatency < 150 ? 'medium' : 'bad');
        statFrameLatency.textContent = avgLatency + ' ms frm.';
        statFrameLatency.className = 'value ' + (avgLatency < 50 ? 'good' : avgLatency < 150 ? 'medium' : 'bad');
        latencySum = 0;
        latencyCount = 0;
      }}
      
      // 发送 Ping
      if (ws && ws.readyState === WebSocket.OPEN) {{
        pingStartTime = Date.now();
        ws.send(JSON.stringify({{ type: 'ping', timestamp: pingStartTime }}));
      }}
      
      // 更新时长
      updateDuration();
    }}
    
    // 更新显示器标签页
    function updateDisplayTabs(displayList) {{
      displays = displayList;
      displayTabs.innerHTML = '';
      
      if (displays.length === 0) {{
        // 默认一个标签
        displays = [{{ name: deviceName, width: 1920, height: 1080, index: 0 }}];
      }}
      
      // 为每个显示器创建画布
      displays.forEach((d, i) => {{
        getOrCreateCanvas(i);
      }});
      
      displays.forEach((d, i) => {{
        const tab = document.createElement('button');
        tab.className = 'tab' + (i === currentDisplay ? ' active' : '');
        tab.dataset.index = i;
        
        const displayName = d.name || ('显示屏 ' + (i + 1) + ' (' + d.width + 'x' + d.height + ')');
        tab.innerHTML = '<svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>' +
          '<span class="tab-title">' + displayName + '</span>' +
          '<span class="tab-close" title="关闭">×</span>';
        
        tab.onclick = (e) => {{
          if (e.target.classList.contains('tab-close')) {{
            return;
          }}
          switchDisplay(i);
        }};
        
        displayTabs.appendChild(tab);
      }});
      
      // 如果有多个显示器，请求每个显示器开始发送流
      if (displays.length > 1) {{
        // 默认请求显示器0
        requestDisplayStream(0);
      }}
    }}
    
    // 请求显示器开始发送流
    function requestDisplayStream(displayIndex) {{
      if (!ws || ws.readyState !== WebSocket.OPEN) return;
      ws.send(JSON.stringify({{
        type: 'switch_display',
        session_id: '',
        from_device: 'browser',
        to_device: deviceId,
        display_index: displayIndex
      }}));
    }}
    
    // 切换显示器（只切换视图，同时请求新显示器的流）
    function switchDisplay(index) {{
      if (index === currentDisplay || !ws || ws.readyState !== WebSocket.OPEN) return;
      
      const prevDisplay = currentDisplay;
      currentDisplay = index;
      
      // 切换画布视图
      setActiveCanvas(index);
      
      // 请求新显示器的流
      requestDisplayStream(index);
      
      // 更新标签状态
      displayTabs.querySelectorAll('.tab').forEach((tab, i) => {{
        tab.classList.toggle('active', i === index);
      }});
      
      // 显示切换提示
      const displayName = displays[index]?.name || ('显示器 ' + (index + 1));
      switchToast.textContent = '已切换到 ' + displayName;
      switchToast.classList.remove('hidden');
      setTimeout(() => switchToast.classList.add('hidden'), 1500);
      
      showToast('已切换到 ' + displayName, 'success');
    }}
    
    // 连接
    function connect() {{
      const wsUrl = serverUrl.replace('http://', 'ws://').replace('https://', 'wss://');
      const url = wsUrl + '/ws/remote/' + deviceId;
      
      console.log('连接:', url);
      
      connectingOverlay.classList.remove('hidden');
      disconnectedOverlay.classList.add('hidden');
      updateConnectionStatus('connecting');
      connectStatus.textContent = '正在建立 WebSocket 连接...';
      
      ws = new WebSocket(url);
      
      ws.onopen = () => {{
        console.log('★ WebSocket 已连接');
        reconnectAttempts = 0;
        connectionStartTime = Date.now();
        
        connectStatus.textContent = '已连接，正在获取远程画面...';
        
        // 默认连接方式 - TCP 中转
        setConnectionMode('relay');
        
        // 请求显示器列表
        ws.send(JSON.stringify({{
          type: 'get_displays',
          session_id: '',
          from_device: 'browser',
          to_device: deviceId
        }}));
        
        // 请求P2P直连（尝试建立更快的连接）
        ws.send(JSON.stringify({{
          type: 'p2p_request',
          session_id: '',
          from_device: 'browser',
          to_device: deviceId
        }}));
        
        // 启动统计更新
        setInterval(updateStats, 1000);
        
        // 延迟隐藏连接遮罩（等待第一帧）
        setTimeout(() => {{
          if (frameCount > 0) {{
            connectingOverlay.classList.add('hidden');
            const activeCanvas = canvases[currentDisplay];
            if (activeCanvas) activeCanvas.canvas.focus();
            showToast('已连接到 ' + deviceName, 'success');
          }}
        }}, 500);
      }};
      
      ws.onmessage = (event) => {{
        try {{
          const msg = JSON.parse(event.data);
          
          if (msg.type === 'screen_frame') {{
            // 首帧时隐藏遮罩
            if (frameCount === 0) {{
              connectingOverlay.classList.add('hidden');
              const activeCanvas = canvases[currentDisplay];
              if (activeCanvas) activeCanvas.canvas.focus();
              showToast('已连接到 ' + deviceName, 'success');
            }}
            
            // 计算延迟和带宽
            if (msg.timestamp) {{
              const latency = Date.now() - msg.timestamp;
              latencySum += latency;
              latencyCount++;
            }}
            if (msg.data) {{
              totalBytes += msg.data.length * 0.75; // base64 to bytes approximation
            }}
            frameCount++;
            
            // 渲染到当前活动的显示器画布
            // 服务端发送的帧始终对应当前选择的显示器
            renderFrame(msg.data, msg.width, msg.height, currentDisplay);
          }} else if (msg.type === 'pong') {{
            lastPing = Date.now() - msg.timestamp;
          }} else if (msg.type === 'displays_list') {{
            updateDisplayTabs(msg.displays || []);
          }} else if (msg.type === 'p2p_ready') {{
            // P2P 直连建立成功
            const addr = msg.connected_addr || '';
            if (addr.includes('LAN') || addr.includes('lan') || addr.includes('192.168') || addr.includes('10.') || addr.includes('172.')) {{
              setConnectionMode('p2p-lan');
            }} else {{
              setConnectionMode('p2p-wan');
            }}
          }} else if (msg.type === 'p2p_fallback') {{
            // P2P 失败，继续使用服务器中转
            console.log('P2P 回退: ' + (msg.reason || ''));
            setConnectionMode('relay');
          }} else if (msg.type === 'p2p_connected') {{
            // 兼容旧消息格式
            setConnectionMode('p2p-wan');
          }} else if (msg.type === 'p2p_mode') {{
            // P2P 模式更新
            if (msg.direct) {{
              setConnectionMode(msg.mode === 'lan' ? 'p2p-lan' : 'p2p-wan');
            }} else if (msg.mode === 'turn') {{
              setConnectionMode('turn');
            }} else {{
              setConnectionMode('relay');
            }}
          }} else if (msg.type === 'error') {{
            console.error('错误:', msg);
            showToast(msg.message || '发生错误', 'error');
          }} else if (msg.type === 'session_end') {{
            showDisconnected('远程设备已关闭连接', '被控端已主动断开连接');
          }}
        }} catch (e) {{
          console.error('解析消息失败:', e);
        }}
      }};
      
      ws.onerror = (e) => {{
        console.error('WebSocket 错误:', e);
      }};
      
      ws.onclose = (e) => {{
        console.log('WebSocket 已关闭, code:', e.code);
        connectionStartTime = null;
        
        if (e.code === 1000 || e.code === 1001) {{
          showDisconnected('连接已关闭', '远程控制会话已结束');
        }} else if (e.code === 1006) {{
          if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {{
            reconnectAttempts++;
            showToast('连接断开，正在重试 (' + reconnectAttempts + '/' + MAX_RECONNECT_ATTEMPTS + ')...', 'error');
            setTimeout(connect, 2000);
          }} else {{
            showDisconnected('连接失败', '无法连接到远程设备，请检查网络');
          }}
        }} else {{
          showDisconnected('连接已断开', '与远程设备的连接已中断');
        }}
      }};
    }}
    
    // 显示断开连接界面
    function showDisconnected(title, text) {{
      disconnectTitle.textContent = title;
      disconnectText.textContent = text;
      connectingOverlay.classList.add('hidden');
      disconnectedOverlay.classList.remove('hidden');
      statConnType.textContent = '已断开';
      statConnType.className = 'value bad';
      updateConnectionStatus('error');
    }}
    
    // 渲染帧到指定显示器
    function renderFrame(base64Data, width, height, displayIndex = currentDisplay) {{
      const canvasInfo = getOrCreateCanvas(displayIndex);
      const canvas = canvasInfo.canvas;
      const ctx = canvasInfo.ctx;
      
      if (canvas.width !== width || canvas.height !== height) {{
        canvas.width = width;
        canvas.height = height;
      }}
      const img = new Image();
      img.onload = () => ctx.drawImage(img, 0, 0);
      img.src = 'data:image/jpeg;base64,' + base64Data;
      
      // 更新该显示器的分辨率显示
      if (displayIndex === currentDisplay) {{
        const res = width + 'x' + height;
        if (lastResolution[displayIndex] !== res) {{
          lastResolution[displayIndex] = res;
          const p = height >= 2160 ? '4K' : height >= 1440 ? '2K' : height >= 1080 ? '1080P' : height >= 720 ? '720P' : height + 'P';
          statResolution.textContent = p + ' auto';
        }}
      }}
    }}
    
    // 发送鼠标事件
    function sendMouseEvent(e, eventType, displayIndex = currentDisplay) {{
      if (!ws || ws.readyState !== WebSocket.OPEN) return;
      
      const canvasInfo = canvases[displayIndex];
      if (!canvasInfo) return;
      
      const canvas = canvasInfo.canvas;
      const rect = canvas.getBoundingClientRect();
      if (!rect.width || !rect.height || !canvas.width || !canvas.height) return;
      
      const scaleX = canvas.width / rect.width;
      const scaleY = canvas.height / rect.height;
      const x = Math.max(0, Math.min(canvas.width, (e.clientX - rect.left) * scaleX));
      const y = Math.max(0, Math.min(canvas.height, (e.clientY - rect.top) * scaleY));
      
      ws.send(JSON.stringify({{
        type: 'mouse_event',
        session_id: '',
        from_device: 'browser',
        to_device: deviceId,
        x: x,
        y: y,
        event_type: eventType,
        button: e.button,
        delta: null,
        display_index: displayIndex
      }}));
    }}
    
    // 发送滚轮事件
    function sendWheelEvent(e, displayIndex = currentDisplay) {{
      if (!ws || ws.readyState !== WebSocket.OPEN) return;
      
      const canvasInfo = canvases[displayIndex];
      if (!canvasInfo) return;
      
      const canvas = canvasInfo.canvas;
      const rect = canvas.getBoundingClientRect();
      const scaleX = canvas.width / rect.width;
      const scaleY = canvas.height / rect.height;
      const x = (e.clientX - rect.left) * scaleX;
      const y = (e.clientY - rect.top) * scaleY;
      
      ws.send(JSON.stringify({{
        type: 'mouse_event',
        session_id: '',
        from_device: 'browser',
        to_device: deviceId,
        x: x,
        y: y,
        event_type: 'scroll',
        button: null,
        delta: e.deltaY,
        display_index: displayIndex
      }}));
      e.preventDefault();
    }}
    
    // 发送键盘事件
    function sendKeyboardEvent(e, eventType) {{
      if (!ws || ws.readyState !== WebSocket.OPEN) return;
      if (e.key === 'F11') {{
        e.preventDefault();
        toggleFullscreen();
        return;
      }}
      ws.send(JSON.stringify({{
        type: 'keyboard_event',
        session_id: '',
        from_device: 'browser',
        to_device: deviceId,
        key: e.key,
        event_type: eventType
      }}));
      e.preventDefault();
    }}
    
    // 事件监听 - 已在 getOrCreateCanvas 中为每个画布绑定
    
    // 全屏切换
    function toggleFullscreen() {{
      if (!document.fullscreenElement) {{
        document.documentElement.requestFullscreen().then(() => {{
          isFullscreen = true;
          document.body.classList.add('fullscreen');
        }}).catch(err => {{
          if (window.__TAURI__) {{
            window.__TAURI__.window.appWindow.setFullscreen(true);
            isFullscreen = true;
            document.body.classList.add('fullscreen');
          }}
        }});
      }} else {{
        document.exitFullscreen().then(() => {{
          isFullscreen = false;
          document.body.classList.remove('fullscreen');
        }}).catch(() => {{
          if (window.__TAURI__) {{
            window.__TAURI__.window.appWindow.setFullscreen(false);
            isFullscreen = false;
            document.body.classList.remove('fullscreen');
          }}
        }});
      }}
    }}
    
    // 监听全屏变化
    document.addEventListener('fullscreenchange', () => {{
      isFullscreen = !!document.fullscreenElement;
      document.body.classList.toggle('fullscreen', isFullscreen);
    }});
    
    // 按钮事件
    document.getElementById('btnFullscreen').addEventListener('click', toggleFullscreen);
    document.getElementById('btnStats').addEventListener('click', () => {{
      showStats = !showStats;
      statsPanel.classList.toggle('hidden', !showStats);
      document.getElementById('btnStats').classList.toggle('active', showStats);
    }});
    document.getElementById('btnClose').addEventListener('click', () => {{
      if (ws) ws.close();
      if (window.__TAURI__) {{
        window.__TAURI__.window.appWindow.close();
      }} else {{
        window.close();
      }}
    }});
    document.getElementById('btnMinimize')?.addEventListener('click', () => {{
      if (window.__TAURI__) {{
        window.__TAURI__.window.appWindow.minimize();
      }}
    }});
    document.getElementById('btnMaximize')?.addEventListener('click', () => {{
      if (window.__TAURI__) {{
        window.__TAURI__.window.appWindow.toggleMaximize();
      }}
    }});
    document.getElementById('btnRetry').addEventListener('click', () => {{
      reconnectAttempts = 0;
      if (ws) ws.close();
      connect();
    }});
    document.getElementById('btnCloseOverlay').addEventListener('click', () => {{
      if (window.__TAURI__) {{
        window.__TAURI__.window.appWindow.close();
      }} else {{
        window.close();
      }}
    }});
    
    // 全屏时标签栏显示/隐藏
    let hideTabBarTimer = null;
    document.addEventListener('mousemove', (e) => {{
      if (isFullscreen && e.clientY < 50) {{
        tabBar.classList.add('show');
        clearTimeout(hideTabBarTimer);
        hideTabBarTimer = setTimeout(() => {{
          if (e.clientY >= 50) tabBar.classList.remove('show');
        }}, 2000);
      }} else if (isFullscreen) {{
        tabBar.classList.remove('show');
      }}
    }});
    
    // 初始化默认标签
    updateDisplayTabs([{{ name: deviceName, width: 1920, height: 1080, index: 0 }}]);
    document.getElementById('btnStats').classList.add('active');
    
    // 绑定默认画布的事件（确保初始画布可用）
    const defaultCanvas = canvases[0];
    if (defaultCanvas) {{
      defaultCanvas.canvas.focus();
    }}
    
    connect();
  </script>
</body>
</html>"#, device_name = device_name, device_id = device_id, server_url = server_url)
}

/// 断开连接
#[tauri::command]
pub async fn disconnect(state: State<'_, AppStateHandle>) -> Result<(), String> {
    let mut app_state = state.write().await;
    
    // 获取当前会话信息
    let session_id = app_state.current_session_id.clone();
    let target_device = match &app_state.connection_state {
        ConnectionState::Connected { target_device_id, .. } => Some(target_device_id.clone()),
        _ => None,
    };
    
    // 清理状态
    app_state.connection_state = ConnectionState::Disconnected;
    app_state.current_session_id = None;
    drop(app_state);
    
    // 发送 SessionEnd 消息给被控端
    if let (Some(session_id), Some(target_device_id)) = (session_id, target_device) {
        tracing::info!("发送 SessionEnd 到设备: {}", target_device_id);
        let signal_client = get_signal_client().await;
        let client = signal_client.read().await;
        if let Some(c) = client.as_ref() {
            let _ = c.end_session(session_id).await;
        }
    }
    
    // 停止本地屏幕流（如果是被控端）
    if let Some(holder) = ACTIVE_SESSION.get() {
        let mut session = holder.write().await;
        if let Some(s) = session.as_ref() {
            s.stop_flag.store(true, Ordering::SeqCst);
            tracing::info!("已停止本地屏幕流");
        }
        *session = None;
    }
    
    Ok(())
}

/// 获取连接状态
#[tauri::command]
pub async fn get_connection_state(state: State<'_, AppStateHandle>) -> Result<ConnectionState, String> {
    let state = state.read().await;
    Ok(state.connection_state.clone())
}

/// 获取历史记录
#[tauri::command]
pub async fn get_history(
    state: State<'_, AppStateHandle>,
    page: Option<u32>,
    limit: Option<u32>,
) -> Result<Vec<HistoryItem>, String> {
    let app_state = state.read().await;
    
    let token = app_state.auth_token.as_ref()
        .ok_or("未登录")?;
    
    let client = reqwest::Client::new();
    let server_url = &app_state.config.server_url.replace("ws://", "http://").replace("wss://", "https://");
    
    let response = client
        .get(format!("{}/api/sessions/history", server_url))
        .query(&[("page", page.unwrap_or(1)), ("limit", limit.unwrap_or(20))])
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .map_err(|e| e.to_string())?;

    if !response.status().is_success() {
        return Err("获取历史记录失败".to_string());
    }

    let result: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
    let items = result["data"]["items"].as_array()
        .map(|arr| {
            arr.iter().filter_map(|item| {
                Some(HistoryItem {
                    id: item["id"].as_str()?.to_string(),
                    device_id: item["controlled_device_id"].as_str()?.to_string(),
                    device_name: item["controlled_device_name"].as_str()?.to_string(),
                    started_at: item["started_at"].as_str()?.to_string(),
                    ended_at: item["ended_at"].as_str().map(|s| s.to_string()),
                    duration: item["duration"].as_str().map(|s| s.to_string()),
                    connection_type: item["connection_type"].as_str()?.to_string(),
                })
            }).collect()
        })
        .unwrap_or_default();
    
    Ok(items)
}

/// 获取配置
#[tauri::command]
pub async fn get_config(state: State<'_, AppStateHandle>) -> Result<ClientConfig, String> {
    let state = state.read().await;
    Ok(state.config.clone())
}

/// 保存配置
#[tauri::command]
pub async fn save_config(
    state: State<'_, AppStateHandle>,
    config: ClientConfig,
) -> Result<(), String> {
    let mut state = state.write().await;
    state.config = config.clone();
    config.save().map_err(|e| e.to_string())?;
    Ok(())
}

/// 重新连接服务器
#[tauri::command]
pub async fn reconnect_server(
    state: State<'_, AppStateHandle>,
) -> Result<(), String> {
    let app_state = state.read().await;
    let server_url = app_state.config.server_url.clone();
    let device_id = app_state.device_id.0.clone();
    drop(app_state);
    
    tracing::info!("重新连接服务器: {}", server_url);
    
    // 断开现有连接
    {
        let signal_client = get_signal_client().await;
        let mut client = signal_client.write().await;
        if client.is_some() {
            *client = None;
            tracing::info!("已断开旧连接");
        }
    }
    
    // 等待一下再重连
    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    
    // 重新连接
    connect_to_signal_server(device_id, server_url).await?;
    
    tracing::info!("重新连接成功");
    Ok(())
}

/// 获取开机启动状态
#[tauri::command]
pub fn get_autostart() -> Result<bool, String> {
    #[cfg(windows)]
    {
        use winreg::enums::*;
        use winreg::RegKey;
        
        let hkcu = RegKey::predef(HKEY_CURRENT_USER);
        if let Ok(run_key) = hkcu.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run") {
            return Ok(run_key.get_value::<String, _>("EasyRemote").is_ok());
        }
        Ok(false)
    }
    
    #[cfg(target_os = "macos")]
    {
        let plist_path = dirs::home_dir()
            .map(|p| p.join("Library/LaunchAgents/com.easyremote.app.plist"))
            .unwrap_or_default();
        Ok(plist_path.exists())
    }
    
    #[cfg(target_os = "linux")]
    {
        let autostart_path = dirs::config_dir()
            .map(|p| p.join("autostart/easyremote.desktop"))
            .unwrap_or_default();
        Ok(autostart_path.exists())
    }
}

/// 设置开机启动
#[tauri::command]
pub fn set_autostart(enable: bool) -> Result<(), String> {
    let exe_path = std::env::current_exe().map_err(|e| e.to_string())?;
    
    #[cfg(windows)]
    {
        use winreg::enums::*;
        use winreg::RegKey;
        
        let hkcu = RegKey::predef(HKEY_CURRENT_USER);
        let run_key = hkcu
            .open_subkey_with_flags("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", KEY_WRITE)
            .map_err(|e| e.to_string())?;
        
        if enable {
            run_key
                .set_value("EasyRemote", &exe_path.to_string_lossy().to_string())
                .map_err(|e| e.to_string())?;
        } else {
            let _ = run_key.delete_value("EasyRemote");
        }
        return Ok(());
    }
    
    #[cfg(target_os = "macos")]
    {
        let plist_path = dirs::home_dir()
            .ok_or("找不到用户目录")?
            .join("Library/LaunchAgents/com.easyremote.app.plist");
        
        if enable {
            let plist_content = format!(
                r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.easyremote.app</string>
    <key>ProgramArguments</key>
    <array>
        <string>{}</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>"#,
                exe_path.to_string_lossy()
            );
            
            if let Some(parent) = plist_path.parent() {
                std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
            }
            std::fs::write(&plist_path, plist_content).map_err(|e| e.to_string())?;
        } else {
            let _ = std::fs::remove_file(&plist_path);
        }
        return Ok(());
    }
    
    #[cfg(target_os = "linux")]
    {
        let autostart_dir = dirs::config_dir()
            .ok_or("找不到配置目录")?
            .join("autostart");
        let desktop_path = autostart_dir.join("easyremote.desktop");
        
        if enable {
            std::fs::create_dir_all(&autostart_dir).map_err(|e| e.to_string())?;
            
            let desktop_content = format!(
                r#"[Desktop Entry]
Type=Application
Name=EasyRemote
Exec={}
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
"#,
                exe_path.to_string_lossy()
            );
            
            std::fs::write(&desktop_path, desktop_content).map_err(|e| e.to_string())?;
        } else {
            let _ = std::fs::remove_file(&desktop_path);
        }
        return Ok(());
    }
}

/// 测试本地输入控制 - 将鼠标移动到指定位置并点击
#[tauri::command]
pub async fn test_local_input() -> Result<String, String> {
    use easyremote_client_core::InputController;
    
    let mut results = Vec::new();
    results.push("=== 测试本地输入控制 ===".to_string());
    
    // 测试创建 InputController
    match InputController::new() {
        Ok(mut controller) => {
            results.push("✅ InputController 创建成功".to_string());
            
            // 测试移动鼠标到屏幕中央
            let test_x = 960;
            let test_y = 540;
            
            results.push(format!("正在移动鼠标到 ({}, {})...", test_x, test_y));
            
            match controller.move_mouse(test_x, test_y) {
                Ok(_) => {
                    results.push(format!("✅ 鼠标移动到 ({}, {}) 成功", test_x, test_y));
                }
                Err(e) => {
                    results.push(format!("❌ 鼠标移动失败: {}", e));
                }
            }
            
            // 等待一下
            std::thread::sleep(std::time::Duration::from_millis(500));
            
            // 测试鼠标点击
            results.push("正在测试鼠标点击...".to_string());
            match controller.click(easyremote_client_core::MouseButton::Left) {
                Ok(_) => {
                    results.push("✅ 鼠标点击成功".to_string());
                }
                Err(e) => {
                    results.push(format!("❌ 鼠标点击失败: {}", e));
                }
            }
        }
        Err(e) => {
            results.push(format!("❌ 创建 InputController 失败: {}", e));
        }
    }
    
    let result = results.join("\n");
    println!("{}", result);
    Ok(result)
}
