import SwiftUI
import SwiftData
import UIKit
import Combine

/// 拍报告 → VL 识别 → 编辑 → 保存(图 + 结构化文本)
/// 一条统一流程,替代原 A1-A3 / B1-B5 两套 mockup。
///
/// 状态机:
/// ```
/// idle → captured(images) → analyzing → editing(parsed, assets)
///                              ↓ 失败
///                          editing(empty, assets)
/// editing → saved → dismiss
/// ```
struct UnifiedCaptureFlow: View {
    @Environment(\.modelContext) private var ctx
    let onClose: () -> Void

    @AppStorage("hasSeenCaptureTip") private var hasSeenCaptureTip: Bool = false
    @State private var phase: Phase = .idle
    @State private var analyzeTask: Task<Void, Never>? = nil
    @State private var showTip: Bool = false

    /// VL 单次推理超时(防止卡死);超时后 cancel 子任务,UI 走手动录入回退。
    private let analyzeTimeoutSeconds: Int = 30

    enum Phase {
        case idle
        case analyzing(images: [UIImage], assets: [FileVault.SavedAsset]?)
        case editing(parsed: ParsedReport,
                     assets: [FileVault.SavedAsset],
                     warning: String?)
    }

    var body: some View {
        NavigationStack {
            content
                .background(Tj.Palette.sand.ignoresSafeArea())
                .toolbar {
                    ToolbarItem(placement: .topBarLeading) {
                        Button("取消") { cancelAll() }
                            .foregroundStyle(Tj.Palette.text)
                    }
                }
                .navigationTitle(phaseTitle)
                .navigationBarTitleDisplayMode(.inline)
        }
        .onAppear {
            if !hasSeenCaptureTip { showTip = true }
        }
        .sheet(isPresented: $showTip) {
            CaptureTipSheet(onDismiss: {
                hasSeenCaptureTip = true
                showTip = false
            })
            .presentationDetents([.medium])
        }
    }

    private var phaseTitle: String {
        switch phase {
        case .idle:       return String(appLoc: "拍摄报告")
        case .analyzing:  return String(appLoc: "本地识别中…")
        case .editing:    return String(appLoc: "核对报告信息")
        }
    }

    @ViewBuilder
    private var content: some View {
        switch phase {
        case .idle:
            captureEntry
        case .analyzing(let images, _):
            AnalyzingView(
                images: images,
                timeoutSeconds: analyzeTimeoutSeconds,
                onCancel: {
                    analyzeTask?.cancel()
                    analyzeTask = nil
                    phase = .idle
                }
            )
        case .editing(let parsed, let assets, let warning):
            CaptureReviewForm(
                parsed: parsed,
                assets: assets,
                warning: warning,
                metaOnly: true,   // 归档只存原图 + meta,不逐项识别(§见 CaptureService.extractReportMeta)
                onSave: { final in saveAll(parsed: final, assets: assets) },
                onCancel: cancelAll,
                onReanalyze: assets.isEmpty ? nil : { reanalyze(assets: assets) }
            )
        }
    }

    // MARK: - 取消统一入口

    /// 取消推理 + 清理未保存到 SwiftData 的 Vault 孤儿图片,再关闭 sheet。
    /// 工具栏「取消」与编辑表单底部「取消(图片不保留)」都走这里,
    /// 保证「图片不保留」的隐私承诺(§6)真的成立,且 Vault 不被孤儿图片堆爆。
    /// 仅清理 .analyzing/.editing 阶段的 assets;.idle 时还没写图,无需清理。
    private func cancelAll() {
        analyzeTask?.cancel()
        analyzeTask = nil
        switch phase {
        case .idle:
            break
        case .analyzing(_, let maybeAssets):
            if let assets = maybeAssets { removeOrphans(assets) }
        case .editing(_, let assets, _):
            removeOrphans(assets)
        }
        onClose()
    }

    private func removeOrphans(_ assets: [FileVault.SavedAsset]) {
        for a in assets {
            try? FileVault.shared.remove(relativePath: a.relativePath)
        }
    }

    // MARK: - 入口:相机 / 相册

    @ViewBuilder
    private var captureEntry: some View {
        #if targetEnvironment(simulator)
        PhotoPickerSheet(
            onFinish: { startAnalyze(images: $0) },
            onCancel: onClose
        )
        #else
        if DocumentScannerView.isSupported {
            DocumentScannerView(
                onFinish: { startAnalyze(images: $0) },
                onCancel: onClose
            )
            .ignoresSafeArea()
        } else {
            PhotoPickerSheet(
                onFinish: { startAnalyze(images: $0) },
                onCancel: onClose
            )
        }
        #endif
    }

    // MARK: - 启动识别

    private func startAnalyze(images: [UIImage]) {
        guard !images.isEmpty else { onClose(); return }
        analyzeTask?.cancel()
        phase = .analyzing(images: images, assets: nil)
        let timeout = analyzeTimeoutSeconds
        analyzeTask = Task {
            // Step 1: 把图写进 Vault(归档核心价值:先把原图存下来),并立刻降采样出预览缩略图。
            // 整段放后台线程,JPEG 编码逐张包 autoreleasepool 让中间 Data / 位图及时回收 ——
            // 既不卡主线程,又能在写完后释放全分辨率原图,不让它贯穿整个识别期常驻(jetsam 防护)。
            let inputBox = UncheckedImageBox(images: images)
            let written: (assets: [FileVault.SavedAsset], thumbs: UncheckedImageBox) =
                await Task.detached(priority: .userInitiated) {
                    let assets = inputBox.images.compactMap { img in
                        autoreleasepool { try? FileVault.shared.writeJPEG(img) }
                    }
                    let thumbs = assets.compactMap {
                        try? FileVault.shared.loadDownsampledImage(relativePath: $0.relativePath, maxPixelSize: 600)
                    }
                    return (assets, UncheckedImageBox(images: thumbs))
                }.value
            let assets = written.assets
            // 极端情况:用户在写图过程中按了「取消」,View 已 dismiss、cancelAll 看到的
            // phase 还是 .analyzing(_, nil),清不到这批刚写完的图 — 这里手动收尾。
            if Task.isCancelled {
                for a in assets { try? FileVault.shared.remove(relativePath: a.relativePath) }
                return
            }
            guard !assets.isEmpty else {
                await MainActor.run {
                    phase = .editing(
                        parsed: .empty(),
                        assets: [],
                        warning: String(appLoc: "图片保存失败,请重试")
                    )
                }
                return
            }
            // 原图已落盘:phase 改持 600px 缩略图(释放全分辨率原图),同时把 assets 暴露给「取消」做孤儿清理。
            await MainActor.run {
                phase = .analyzing(images: written.thumbs.images, assets: assets)
            }

            // Step 2: 轻量 meta 提取(OCR + 文本 LLM,只抽日期/机构/类型/标题)。
            // 不再跑多模态逐项识别 —— 那在 2B 上又慢又会 OOM 卡死。watchdog 到点 cancel。
            let watchdog = Task {
                try? await Task.sleep(for: .seconds(timeout))
                analyzeTask?.cancel()
            }
            defer { watchdog.cancel() }

            let (meta, recognized) = await CaptureService.shared.extractReportMeta(assets: assets)
            if Task.isCancelled {
                await MainActor.run {
                    phase = .editing(parsed: .empty(), assets: assets,
                                     warning: String(appLoc: "识别超时,已保存原图,请手动填写信息"))
                }
                return
            }
            await MainActor.run {
                phase = .editing(
                    parsed: meta,
                    assets: assets,
                    warning: recognized ? nil
                        : String(appLoc: "未能自动识别报告信息,已保存原图,可手动填写日期 / 机构")
                )
            }
        }
    }

    /// 「重新识别信息」:复用已存 assets,不再写图,只重跑一次轻量 meta 提取。
    private func reanalyze(assets: [FileVault.SavedAsset]) {
        analyzeTask?.cancel()
        // 这里没有原始 UIImage,AnalyzingView 只把首张缩略图模糊后当背景,降采样到 600px 足够,
        // 避免为「重新识别」把整页全分辨率原图(数十 MB)载进内存。
        let thumbnails: [UIImage] = assets.compactMap {
            try? FileVault.shared.loadDownsampledImage(relativePath: $0.relativePath, maxPixelSize: 600)
        }
        phase = .analyzing(images: thumbnails, assets: assets)
        let timeout = analyzeTimeoutSeconds
        analyzeTask = Task {
            let watchdog = Task {
                try? await Task.sleep(for: .seconds(timeout))
                analyzeTask?.cancel()
            }
            defer { watchdog.cancel() }

            let (meta, recognized) = await CaptureService.shared.extractReportMeta(assets: assets)
            if Task.isCancelled {
                await MainActor.run {
                    phase = .editing(parsed: .empty(), assets: assets,
                                     warning: String(appLoc: "识别超时,已保留原图"))
                }
                return
            }
            await MainActor.run {
                phase = .editing(parsed: meta, assets: assets,
                                 warning: recognized ? nil
                                    : String(appLoc: "未能自动识别报告信息,可手动填写"))
            }
        }
    }

    // MARK: - 持久化

    private func saveAll(parsed final: ParsedReport,
                         assets: [FileVault.SavedAsset]) {
        let report = Report(
            title: final.title.isEmpty ? String(appLoc: "拍摄识别") : final.title,
            type: ReportType(rawValue: final.typeRaw) ?? .other,
            reportDate: final.reportDate,
            institution: final.institution.isEmpty ? nil : final.institution,
            summary: final.summary.isEmpty ? nil : final.summary,
            pageCount: final.pageCount
        )
        ctx.insert(report)

        // 关联 Asset
        for a in assets {
            let asset = Asset(relativePath: a.relativePath, bytes: a.bytes)
            ctx.insert(asset)
            report.assets.append(asset)
        }

        // 关联 Indicator
        for ind in final.indicators {
            let i = Indicator(
                name: ind.name,
                value: ind.value,
                unit: ind.unit,
                range: ind.range,
                status: ind.status,
                capturedAt: final.reportDate,
                report: report,
                source: .report,
                sourcePageIndex: ind.sourcePageIndex,
                sourceBoxX: ind.sourceBoxX,
                sourceBoxY: ind.sourceBoxY,
                sourceBoxWidth: ind.sourceBoxWidth,
                sourceBoxHeight: ind.sourceBoxHeight
            )
            ctx.insert(i)
        }

        try? ctx.save()
        // 后台预生成大白话摘要:用户继续操作,详情页打开时秒开。
        // 低优先级 —— 任何前台 AI 任务(再次拍照/问答)都会让它在下一个 token 让位。
        Task { await ReportInsightService.shared.pregenerateIfNeeded(report: report, in: ctx) }
        onClose()
    }
}

/// 跨 detached 边界安全携带 UIImage 数组:图片只读、不并发改,封装免 Sendable 报错
/// (同 MNNBackend.MNNUncheckedBox 思路)。
private struct UncheckedImageBox: @unchecked Sendable {
    let images: [UIImage]
}

// MARK: - 分析中视图

private struct AnalyzingView: View {
    let images: [UIImage]
    let timeoutSeconds: Int
    let onCancel: () -> Void

    @State private var elapsed: Int = 0
    private let tick = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        VStack(spacing: 20) {
            Spacer()
            if let first = images.first {
                Image(uiImage: first)
                    .resizable()
                    .scaledToFit()
                    .frame(maxHeight: 240)
                    .clipShape(RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous))
                    .overlay(
                        RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
                            .strokeBorder(Tj.Palette.line, lineWidth: 1)
                    )
                    .overlay(
                        RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
                            .fill(.ultraThinMaterial)
                            .overlay(
                                ProgressView().tint(Tj.Palette.ink).scaleEffect(1.4)
                            )
                    )
            }
            VStack(spacing: 6) {
                Text("本地识别中")
                    .font(.tjH2())
                    .foregroundStyle(Tj.Palette.text)
                Text("\(images.count) 页 · 100% 本地推理 · 已用 \(elapsed)s")
                    .font(.tjScaled( 12))
                    .foregroundStyle(Tj.Palette.text3)
                if elapsed >= timeoutSeconds - 5 {
                    Text("快超时了,>\(timeoutSeconds)s 会自动转为手动录入")
                        .font(.tjScaled( 11))
                        .foregroundStyle(Tj.Palette.amber)
                }
            }
            Button(action: onCancel) {
                Text("取消识别 · 改为手动录入")
                    .font(.tjScaled( 13, weight: .medium))
                    .foregroundStyle(Tj.Palette.text3)
                    .padding(.horizontal, 20)
                    .frame(minHeight: 44)   // HIG 最小命中区
                    .contentShape(Rectangle())
            }
            .buttonStyle(.plain)
            .padding(.top, 4)
            Spacer()
        }
        .padding(.horizontal, 20)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .onReceive(tick) { _ in elapsed += 1 }
    }
}

// MARK: - 一次性使用提示

private struct CaptureTipSheet: View {
    let onDismiss: () -> Void

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            HStack(spacing: 10) {
                Image(systemName: "doc.viewfinder")
                    .font(.tjScaled( 28))
                    .foregroundStyle(Tj.Palette.ink)
                Text("拍报告的小贴士")
                    .font(.tjH2())
                    .foregroundStyle(Tj.Palette.text)
            }
            VStack(alignment: .leading, spacing: 12) {
                tip(String(appLoc: "纸张铺平,避免反光、阴影"))
                tip(String(appLoc: "整页入框,避免裁切到指标"))
                tip(String(appLoc: "多页报告可连拍,系统自动透视校正"))
                tip(String(appLoc: "识别全程在本地,图片不会上传"))
            }
            Spacer()
            Button {
                onDismiss()
            } label: {
                Text("我知道了,开始拍")
                    .frame(maxWidth: .infinity)
            }
            .buttonStyle(TjPrimaryButton())
        }
        .padding(24)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
        .background(Tj.Palette.sand.ignoresSafeArea())
    }

    private func tip(_ text: String) -> some View {
        HStack(alignment: .top, spacing: 10) {
            Image(systemName: "checkmark.circle.fill")
                .foregroundStyle(Tj.Palette.leaf)
                .padding(.top, 2)
            Text(text)
                .font(.tjSerifBody())
                .foregroundStyle(Tj.Palette.text2)
                .fixedSize(horizontal: false, vertical: true)
            Spacer()
        }
    }
}
