//! ConPTY 绑定与本地会话实现。
//!
//! 直接绑定 `windows-sys` 的 `CreatePseudoConsole` 系列 API(而非引入
//! `portable-pty` 之类的封装)。理由:本项目已有大量原生 Win32 调用
//! (`win32_util.rs` / `screenshot/wgc_capture.rs` / `translate/capture/uia_capture.rs`),
//! 这条路径熟悉;而 ConPTY 的三个坑(见下)无论加不加封装都要踩,多一层只增加定位难度。
//!
//! # ConPTY 的三个坑(全部已在实现中处理)
//!
//! 1. **`ClosePseudoConsole` 会阻塞**,直到所有引用该 PTY 的句柄被关闭。若在读线程
//! 仍挂起于 `ReadFile` 时调用,就会永久卡住。处理:先 `CancelIoEx` 取消挂起的读,
//! 再在**独立线程**里调用 `ClosePseudoConsole`,调用方不等待(见 [`ConPtySession::kill`])。
//!
//! 2. **`ResizePseudoConsole` 有早期竞态**:进程刚创建、还没开始读 stdout 时调用,
//! 尺寸可能被吞掉(表现为 TUI 程序启动后按 80×24 而不是实际尺寸绘制,vim/htop 花屏)。
//! 处理:首帧输出到达前,resize 请求只入队不执行;首帧到达后再应用队列中的最新值
//! (见 [`PtyInner::pending_size`])。
//!
//! 3. **进程退出 ≠ PTY 关闭**:子进程退出后,管道里可能还有未读完的输出(如最后一行
//! 提示符、错误信息)。必须等 `ReadFile` 返回 0 或 `ERROR_BROKEN_PIPE` 才算真正结束,
//! 否则会丢掉尾部输出——这正是很多自制终端「退出时少一行」的原因。
//!
//! # 线程模型
//!
//! 每个会话起 **两个** 后台线程:
//! - 输出读线程:循环 `ReadFile`,把数据推入聚合缓冲,按 8~16ms 窗口发批次事件。
//! - 退出等待线程:`WaitForSingleObject` 等子进程句柄,拿退出码,等读线程自然结束
//! (即坑 3)后把状态置为 `Closed` 并发事件。
//!
//! 写操作不单独起线程:`WriteFile` 在 ConPTY 上通常不阻塞(有内部缓冲),
//! 由命令层直接同步调用。若未来证实大块粘贴会阻塞,再改成写队列。
use std::io::{ErrorKind, Read, Write};
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tauri::{AppHandle, Emitter};
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
use windows_sys::Win32::System::Console::{
ClosePseudoConsole, CreatePseudoConsole, ResizePseudoConsole, COORD, HPCON,
};
use windows_sys::Win32::System::Threading::{
CreateProcessW, GetExitCodeProcess, TerminateProcess, WaitForSingleObject,
CREATE_UNICODE_ENVIRONMENT, EXTENDED_STARTUPINFO_PRESENT, PROCESS_INFORMATION,
STARTUPINFOEXW,
};
use super::super::session::{
local_info, LocalSessionState, Session, SessionId, SessionInfo, SessionKind, SessionState,
};
/// 输出聚合窗口。
///
/// 8ms 是权衡值:`cat` 大文件时每秒可产生数万次 `ReadFile` 返回,逐条 emit 会压垮
/// WebView;但窗口太长(如 50ms)会让交互式输入出现可感知的延迟。8ms 约等于
/// 一帧(120Hz),人眼无法分辨,同时能把数千次读合并成一次事件。
const AGGREGATE_WINDOW: Duration = Duration::from_millis(8);
/// 单次读缓冲区大小。64KB 与 ConPTY 内部缓冲匹配,避免多次往返。
const READ_BUF_SIZE: usize = 64 * 1024;
/// 聚合缓冲上限:防止「疯狂输出且前端卡住」时内存无限增长。
/// 超出后丢弃**最旧**的数据(终端语义:用户更关心最新的输出)。
const MAX_PENDING_BYTES: usize = 4 * 1024 * 1024;
/// 会话终止时附加到输出的提示(由 Rust 侧统一给出,避免前端各写一套)。
const EXIT_HINT: &str = "\r\n";
// ===== 共享状态 =====
struct PtyInner {
/// PTY 句柄。`Mutex` 保护是因为 resize 需要并发访问,而 ClosePseudoConsole
/// 会把它置为 None(表示已关闭,后续调用应静默忽略)。
hpc: Mutex