import SwiftTerm
import UIKit

/// T-iOS-28 · 离屏 SwiftTerm 渲染器：把一段 preview tail（不可信 ANSI 字节，
/// **只喂终端解释器，绝不字符串处理**）喂进一个从不进视图层级的
/// `TerminalView`，再用 `UIGraphicsImageRenderer` 对已布局的 view 出快照。
///
/// 与 web 预览逐点对齐（public/preview-grid.ts makePreviewCard/renderPreview）：
/// - 网格 = 服务器回报的 cols×rows（上游已钳制）——换行与真屏一致；
/// - `scrollback: 0` ↔ `changeScrollback(nil)`：buffer 只有当前屏，
///   `contentOffset` 恒为 0，离屏 draw 恰取「现在这一屏」；
/// - 暗底主题 + 隐藏光标（web 用 cursor==background；此处喂 DECTCEM 隐藏序列，
///   iOS 端直接把 caret 子视图摘掉）；
/// - 快照按 `snapshotTargetWidth` 缩放——缓存的是小位图，不是整格点阵。
///
/// 生命周期：`TerminalView` 在初始化时向主 runloop 挂 CADisplayLink（暂停态），
/// 离屏用完必须 `updateUiClosed()` 注销，否则每次渲染泄漏一个 runloop 源。
@MainActor
enum SessionThumbnailRenderer {
    /// 快照宽度预算（pt）：列表槽位 88pt 的 2 倍，配 `snapshotScale`=2 后
    /// 像素密度足够，单张缓存 ≈ 数百 KB 有界。
    static let snapshotTargetWidth: CGFloat = 176
    /// 固定 2x：快照是缩略图，不追设备 3x（内存 × 2.25 不值）。
    static let snapshotScale: CGFloat = 2
    /// 镜像 web 预览字号（public/preview-grid.ts fontSize: 12）。
    static let fontSize: CGFloat = 12
    /// 布局余量（pt）：frame 取 optimal + slack，抵消 `Int(width/cellW)` 的
    /// 浮点截断——必须严格小于一个 cell 宽，否则网格会多出一列。
    static let layoutSlack: CGFloat = 0.5
    /// DECTCEM 隐藏光标（数据喂完后追加；iOS 端会摘掉 caret 子视图）。
    /// 这是我们自己的常量序列，不是对服务器数据的处理。
    static let hideCursorSequence = "\u{1b}[?25l"
    /// 镜像 web PREVIEW_THEME（#0e0f13 / #e7e8ec）。
    static let backgroundColor = UIColor(
        red: 14 / 255, green: 15 / 255, blue: 19 / 255, alpha: 1
    )
    static let foregroundColor = UIColor(
        red: 231 / 255, green: 232 / 255, blue: 236 / 255, alpha: 1
    )
    private static let initialProbeFrame = CGRect(x: 0, y: 0, width: 64, height: 64)

    /// 渲染一张快照。`cols`/`rows` 必须已过 `clampedGeometry`（有界）；失败
    /// 返回 nil（上游显式降级为占位图）。
    static func render(data: String, cols: Int, rows: Int) -> UIImage? {
        let font = UIFont.monospacedSystemFont(ofSize: fontSize, weight: .regular)
        let terminal = TerminalView(frame: initialProbeFrame, font: font)
        defer { terminal.updateUiClosed() } // 注销 CADisplayLink，绝不泄漏
        terminal.changeScrollback(nil)      // ↔ web scrollback: 0
        terminal.resize(cols: cols, rows: rows)
        terminal.nativeBackgroundColor = backgroundColor
        terminal.nativeForegroundColor = foregroundColor

        let optimal = terminal.getOptimalFrameSize()
        guard optimal.width > 0, optimal.height > 0 else { return nil }
        terminal.frame = CGRect(
            x: 0, y: 0,
            width: optimal.width + layoutSlack, height: optimal.height + layoutSlack
        )
        terminal.layoutIfNeeded()

        terminal.feed(text: data)              // 唯一合法去处：ANSI 解释器
        terminal.feed(text: hideCursorSequence)

        return snapshot(of: terminal, contentSize: optimal.size)
    }

    /// `UIGraphicsImageRenderer` + `layer.render(in:)`：离屏（无 window）视图
    /// 走 CoreGraphics draw 路径（SwiftTerm 默认不启 Metal——Metal 层无法被
    /// `layer.render` 捕获，此处也永不 `setUseMetal`）。
    private static func snapshot(of view: UIView, contentSize: CGSize) -> UIImage? {
        let scale = min(1, snapshotTargetWidth / contentSize.width)
        let size = CGSize(
            width: contentSize.width * scale, height: contentSize.height * scale
        )
        guard size.width >= 1, size.height >= 1 else { return nil }
        let format = UIGraphicsImageRendererFormat()
        format.scale = snapshotScale
        format.opaque = true
        let renderer = UIGraphicsImageRenderer(size: size, format: format)
        return renderer.image { context in
            context.cgContext.scaleBy(x: scale, y: scale)
            view.layer.render(in: context.cgContext)
        }
    }
}
