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,
                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。
            // 在 UI 这一层写,而不是塞进 CaptureService.analyze —— 这样取消/失败回退时,
            // assets 已经在 phase 里,cancelAll 能清理孤儿,editingFallback 也不必再补写。
            let assets = images.compactMap { try? FileVault.shared.writeJPEG($0) }
            // 极端情况:用户在写图过程中按了「取消」,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
            }
            // 把 assets 暴露给 phase,使工具栏「取消」也能找到孤儿清理。
            await MainActor.run {
                if case .analyzing(let imgs, _) = phase {
                    phase = .analyzing(images: imgs, assets: assets)
                }
            }

            // Step 2: VL 推理(timeout 哨兵到点 cancel 父任务,VLSession 在下一个 token break)。
            let watchdog = Task {
                try? await Task.sleep(for: .seconds(timeout))
                analyzeTask?.cancel()
            }
            defer { watchdog.cancel() }

            do {
                let parsed = try await CaptureService.shared.reanalyze(assets: assets)
                if Task.isCancelled {
                    await editingFallback(assets: assets,
                                          msg: String(appLoc: "识别超时(>\(timeout)s),先手动录入"))
                    return
                }
                await MainActor.run {
                    phase = .editing(
                        parsed: parsed,
                        assets: assets,
                        warning: parsed.isEmpty ? String(appLoc: "识别没有读出指标,请手动补充") : nil
                    )
                }
            } catch let CaptureError.parseFailed(msg) {
                await editingFallback(assets: assets, msg: String(appLoc: "VL 输出无法解析:\(msg)"))
            } catch let CaptureError.inferenceFailed(msg) {
                await editingFallback(assets: assets,
                                      msg: Task.isCancelled
                                          ? String(appLoc: "识别超时(>\(timeout)s),先手动录入")
                                          : String(appLoc: "推理失败:\(msg)"))
            } catch CaptureError.modelNotReady {
                await editingFallback(assets: assets, msg: String(appLoc: "VL 模型未就绪,先手动录入"))
            } catch {
                await editingFallback(assets: assets,
                                      msg: String(appLoc: "未知错误:\(error.localizedDescription)"))
            }
        }
    }

    /// 「重新识别」:复用已存 assets,不再写图,只重跑 VL。
    private func reanalyze(assets: [FileVault.SavedAsset]) {
        analyzeTask?.cancel()
        // 这里没有原始 UIImage,AnalyzingView 显示首张缩略图即可
        let thumbnails: [UIImage] = assets.compactMap {
            try? FileVault.shared.loadImage(relativePath: $0.relativePath)
        }
        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() }

            do {
                let parsed = try await CaptureService.shared.reanalyze(assets: assets)
                if Task.isCancelled {
                    await editingFallback(assets: assets,
                                         msg: String(appLoc: "识别超时(>\(timeout)s),保留旧编辑"))
                    return
                }
                await MainActor.run {
                    phase = .editing(
                        parsed: parsed,
                        assets: assets,
                        warning: parsed.isEmpty ? String(appLoc: "重新识别没有读出新指标") : nil
                    )
                }
            } catch CaptureError.modelNotReady {
                await editingFallback(assets: assets, msg: String(appLoc: "VL 模型未就绪"))
            } catch let CaptureError.parseFailed(msg) {
                await editingFallback(assets: assets, msg: String(appLoc: "VL 输出无法解析:\(msg)"))
            } catch let CaptureError.inferenceFailed(msg) {
                await editingFallback(assets: assets,
                                     msg: Task.isCancelled
                                         ? String(appLoc: "识别超时(>\(timeout)s)")
                                         : String(appLoc: "推理失败:\(msg)"))
            } catch {
                await editingFallback(assets: assets,
                                     msg: String(appLoc: "未知错误:\(error.localizedDescription)"))
            }
        }
    }

    /// reanalyze 失败时回到 editing,保留 assets 但清空 parsed。
    private func editingFallback(assets: [FileVault.SavedAsset], msg: String) async {
        await MainActor.run {
            phase = .editing(parsed: .empty(), assets: assets, warning: msg)
        }
    }

    // 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
            )
            ctx.insert(i)
        }

        try? ctx.save()
        onClose()
    }
}

// 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(.system(size: 12))
                    .foregroundStyle(Tj.Palette.text3)
                if elapsed >= timeoutSeconds - 5 {
                    Text("快超时了,>\(timeoutSeconds)s 会自动转为手动录入")
                        .font(.system(size: 11))
                        .foregroundStyle(Tj.Palette.amber)
                }
            }
            Button("取消识别 · 改为手动录入", action: onCancel)
                .font(.system(size: 13, weight: .medium))
                .foregroundStyle(Tj.Palette.text3)
                .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(.system(size: 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()
        }
    }
}
