import SwiftUI

/// 从加密 Vault 异步加载并降采样显示原图的通用组件。
///
/// 替代「在 body 里 `try? FileVault.shared.loadImage(...)` 同步读盘 + 全量解码」的旧写法,
/// 解决两个真实问题:
/// 1. **OOM**:全分辨率位图(4000×3000 ≈ 48MB)进内存,翻几页就触发 jetsam。这里按 `maxPixel`
///    降采样,缩略图几百 KB,全屏图几 MB。
/// 2. **主线程卡顿**:读盘 + JPEG 解码在主线程会掉帧。这里放到后台线程,主线程只拿结果绘制。
///
/// 区分「加载中」与「读取失败」两态:加载中显示中性占位,只有真正失败才显示「原图无法读取」,
/// 不会一打开就闪一下吓人的错误文案。`content` 拿到 `UIImage`(而非 `Image`),
/// 方便需要 `image.size` 的调用方(如证据高亮 overlay)按真实宽高比定位。
struct VaultImage<Content: View, Placeholder: View>: View {
    let relativePath: String
    /// 降采样目标最大边(像素)。缩略图给 ~400,全屏查看器给 ~2000。
    var maxPixel: CGFloat = 1024

    @ViewBuilder var content: (UIImage) -> Content
    /// 占位回调,`isLoading == true` 表示仍在加载,`false` 表示加载完成但失败。
    @ViewBuilder var placeholder: (_ isLoading: Bool) -> Placeholder

    @State private var image: UIImage?
    @State private var loading = true

    var body: some View {
        Group {
            if let image {
                content(image)
            } else {
                placeholder(loading)
            }
        }
        // id 变了(TabView 翻到新页 / 行复用换 asset / 同一 path 改目标尺寸)就重新加载;
        // 把 maxPixel 纳入 id(与 FileVault 缓存 key 同构),避免同 path 切尺寸时不刷新显示旧图。
        .task(id: "\(relativePath)@\(Int(maxPixel))") {
            loading = true
            let path = relativePath
            let mp = maxPixel
            let loaded = await Task.detached(priority: .userInitiated) {
                try? FileVault.shared.loadDownsampledImage(relativePath: path, maxPixelSize: mp)
            }.value
            guard !Task.isCancelled else { return }
            image = loaded
            loading = false
        }
    }
}
