import SwiftUI
import SwiftData

/// 「导出身体档案」全屏 sheet。
/// 状态机:idle → running(extractingIntent → retrieving → generating)→ completed / failed
struct HealthExportSheet: View {
    @Environment(\.modelContext) private var ctx
    @Environment(\.dismiss) private var dismiss

    /// 可选:从历史「重新生成」时传入(暂时未启用,W3 接)。
    let initialPrompt: String

    @State private var prompt: String = ""
    @State private var phase: HealthExportService.Phase?
    @State private var content: String = ""
    @State private var rate: Double = 0
    @State private var task: Task<Void, Never>?
    @State private var error: Error?
    @State private var completed: Bool = false
    @State private var copiedFlash: Bool = false
    @FocusState private var promptFocused: Bool

    init(initialPrompt: String = "") {
        self.initialPrompt = initialPrompt
    }

    private var isRunning: Bool { phase != nil && !completed && error == nil }
    private var isInputMode: Bool { phase == nil && !completed && error == nil }

    var body: some View {
        VStack(spacing: 0) {
            header
            ScrollViewReader { proxy in
                ScrollView {
                    VStack(alignment: .leading, spacing: 18) {
                        if isInputMode {
                            inputSection
                        } else {
                            promptEcho
                            if isRunning { phaseIndicator }
                            if !content.isEmpty {
                                MarkdownView(text: content)
                                    .padding(16)
                                    .frame(maxWidth: .infinity, alignment: .leading)
                                    .background(
                                        RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
                                            .fill(Tj.Palette.paper)
                                    )
                                    .overlay(
                                        RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
                                            .strokeBorder(Tj.Palette.lineSoft, lineWidth: 1)
                                    )
                            }
                            if let err = error { errorRow(err) }
                            // 锚点,让流式输出自动滚到底
                            Color.clear.frame(height: 1).id("bottom")
                        }
                    }
                    .padding(.horizontal, 20)
                    .padding(.vertical, 16)
                }
                .onChange(of: content) { _, _ in
                    withAnimation(.easeOut(duration: 0.12)) {
                        proxy.scrollTo("bottom", anchor: .bottom)
                    }
                }
            }
            if completed { actionRow }
        }
        .background(Tj.Palette.sand.ignoresSafeArea())
        .onAppear {
            if prompt.isEmpty { prompt = initialPrompt }
            if isInputMode { promptFocused = true }
        }
        .onDisappear { task?.cancel() }
    }

    // MARK: - Header

    private var header: some View {
        HStack(alignment: .center, spacing: 12) {
            Button { close() } label: {
                Image(systemName: "xmark")
                    .font(.system(size: 16, weight: .semibold))
                    .foregroundStyle(Tj.Palette.text)
                    .frame(width: 32, height: 32)
                    .background(Circle().fill(Tj.Palette.sand2))
            }
            VStack(alignment: .leading, spacing: 2) {
                Text("导出身体档案")
                    .font(.tjH2())
                    .foregroundStyle(Tj.Palette.text)
                Text("给医生看的就诊摘要")
                    .font(.system(size: 11))
                    .foregroundStyle(Tj.Palette.text3)
            }
            Spacer()
            TjLockChip()
        }
        .padding(.horizontal, 20)
        .padding(.vertical, 14)
        .background(Tj.Palette.sand)
        .overlay(alignment: .bottom) {
            Rectangle().fill(Tj.Palette.lineSoft).frame(height: 1)
        }
    }

    // MARK: - Input section (idle)

    private var inputSection: some View {
        VStack(alignment: .leading, spacing: 14) {
            Text("说说你想给医生看什么")
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(Tj.Palette.text2)

            VStack(alignment: .leading, spacing: 6) {
                Text("例:我感冒3天了,把最近一个月的健康情况给医生看")
                    .font(.system(size: 12))
                    .foregroundStyle(Tj.Palette.text3)
                Text("例:最近血糖好像不稳,把过去三个月的化验单整理一下")
                    .font(.system(size: 12))
                    .foregroundStyle(Tj.Palette.text3)
            }

            ZStack(alignment: .topLeading) {
                if prompt.isEmpty {
                    Text("在这里输入主诉……")
                        .font(.system(size: 15))
                        .foregroundStyle(Tj.Palette.text3)
                        .padding(.horizontal, 14)
                        .padding(.vertical, 14)
                        .allowsHitTesting(false)
                }
                TextEditor(text: $prompt)
                    .font(.system(size: 15))
                    .foregroundStyle(Tj.Palette.text)
                    .scrollContentBackground(.hidden)
                    .padding(.horizontal, 10)
                    .padding(.vertical, 8)
                    .frame(minHeight: 130)
                    .focused($promptFocused)
            }
            .background(
                RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
                    .fill(Tj.Palette.paper)
            )
            .overlay(
                RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
                    .strokeBorder(Tj.Palette.lineSoft, lineWidth: 1)
            )

            HStack {
                Text("本地 RAG · Qwen3 1.7B · 不上传任何数据")
                    .font(.system(size: 11))
                    .foregroundStyle(Tj.Palette.text3)
                Spacer()
                Button { start() } label: {
                    Text("生成报告")
                }
                .buttonStyle(TjPrimaryButton(height: 44, fontSize: 14))
                .disabled(prompt.trimmingCharacters(in: .whitespaces).isEmpty)
                .opacity(prompt.trimmingCharacters(in: .whitespaces).isEmpty ? 0.5 : 1)
            }
        }
    }

    // MARK: - Prompt echo (after start)

    private var promptEcho: some View {
        HStack(alignment: .top, spacing: 8) {
            Image(systemName: "quote.opening")
                .font(.system(size: 12))
                .foregroundStyle(Tj.Palette.text3)
            Text(prompt)
                .font(.system(size: 13))
                .foregroundStyle(Tj.Palette.text2)
                .lineLimit(3)
        }
        .padding(12)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(
            RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
                .fill(Tj.Palette.sand2)
        )
    }

    // MARK: - Phase indicator

    private var phaseIndicator: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack(spacing: 10) {
                phasePill(.extractingIntent)
                arrow
                phasePill(.retrieving)
                arrow
                phasePill(.generating)
            }
            if phase == .generating && rate > 0 {
                Text(String(format: String(appLoc: "本地推理 · %.1f tok/s"), rate))
                    .font(.system(size: 11, design: .monospaced))
                    .foregroundStyle(Tj.Palette.leaf)
            } else {
                Text(phase?.label ?? "")
                    .font(.system(size: 11))
                    .foregroundStyle(Tj.Palette.text3)
            }
        }
    }

    private func phasePill(_ p: HealthExportService.Phase) -> some View {
        let active = (p == phase)
        let done = phaseOrder(p) < phaseOrder(phase ?? .extractingIntent)
        let fill = active ? Tj.Palette.ink : (done ? Tj.Palette.leaf : Tj.Palette.sand2)
        let fg = (active || done) ? Tj.Palette.paper : Tj.Palette.text3
        return Text(p.label)
            .font(.system(size: 11, weight: active ? .semibold : .regular))
            .foregroundStyle(fg)
            .padding(.horizontal, 10)
            .padding(.vertical, 5)
            .background(Capsule().fill(fill))
    }

    private var arrow: some View {
        Image(systemName: "chevron.right")
            .font(.system(size: 10, weight: .semibold))
            .foregroundStyle(Tj.Palette.text3)
    }

    private func phaseOrder(_ p: HealthExportService.Phase) -> Int {
        switch p {
        case .extractingIntent: return 0
        case .retrieving:       return 1
        case .generating:       return 2
        case .completed:        return 3
        }
    }

    // MARK: - Error

    private func errorRow(_ err: Error) -> some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack(spacing: 8) {
                Image(systemName: "exclamationmark.triangle.fill")
                    .foregroundStyle(Tj.Palette.brick)
                Text(err.localizedDescription)
                    .font(.system(size: 13))
                    .foregroundStyle(Tj.Palette.text)
            }
            Button { reset() } label: { Text("返回修改") }
                .buttonStyle(TjGhostButton(height: 40, fontSize: 13))
        }
        .padding(14)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(
            RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
                .fill(Tj.Palette.brickSoft.opacity(0.6))
        )
    }

    // MARK: - Action row (completed)

    private var actionRow: some View {
        HStack(spacing: 10) {
            Button { copy() } label: {
                Label(copiedFlash ? "已复制" : "复制", systemImage: copiedFlash ? "checkmark" : "doc.on.doc")
            }
            .buttonStyle(TjGhostButton(height: 44, fontSize: 13, horizontalPadding: 14))

            ShareLink(item: content) {
                Label("分享", systemImage: "square.and.arrow.up")
                    .font(.system(size: 13, weight: .semibold))
                    .tracking(1)
                    .foregroundStyle(Tj.Palette.ink)
                    .padding(.horizontal, 14)
                    .frame(height: 44)
                    .background(Capsule().strokeBorder(Tj.Palette.ink, lineWidth: 1))
                    .contentShape(Capsule())   // 纯描边胶囊:内边距区也可点
            }

            Spacer()
            Button { regenerate() } label: {
                Label("重新生成", systemImage: "arrow.clockwise")
            }
            .buttonStyle(TjPrimaryButton(height: 44, fontSize: 13, horizontalPadding: 16))
        }
        .padding(.horizontal, 20)
        .padding(.vertical, 12)
        .background(Tj.Palette.paper)
        .overlay(alignment: .top) {
            Rectangle().fill(Tj.Palette.lineSoft).frame(height: 1)
        }
    }

    // MARK: - Actions

    private func start() {
        let p = prompt.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !p.isEmpty else { return }
        promptFocused = false
        content = ""
        rate = 0                 // 重新生成时清零,避免旧 tok/s 残留显示
        error = nil
        completed = false
        phase = .extractingIntent

        let stream = HealthExportService.shared.export(prompt: p, in: ctx)
        task = Task { @MainActor in
            do {
                for try await event in stream {
                    switch event {
                    case .phaseChanged(let ph):
                        phase = ph
                    case .token(let chunk):
                        content += chunk.text
                        if chunk.decodeRate > 0 { rate = chunk.decodeRate }
                    case .completed:
                        completed = true
                    }
                }
            } catch {
                self.error = error
                self.phase = nil
            }
        }
    }

    private func regenerate() {
        completed = false
        start()
    }

    private func reset() {
        task?.cancel()
        task = nil
        phase = nil
        content = ""
        rate = 0
        error = nil
        completed = false
        promptFocused = true
    }

    private func copy() {
        UIPasteboard.general.string = content
        copiedFlash = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.4) {
            copiedFlash = false
        }
    }

    private func close() {
        task?.cancel()
        dismiss()
    }
}

// MARK: - 简易 Markdown 渲染(行级)

/// 极简 Markdown 渲染器,够给医生看的报告就行。
/// 支持: `# 一级`、`## 二级`、`-` 列表、`**粗体**`(走 AttributedString 的 inline 解析)。
/// 不支持表格、代码块、链接 —— 报告生成 prompt 也不会让 LLM 输出这些。
struct MarkdownView: View {
    let text: String

    var body: some View {
        let blocks = Self.parse(text)
        VStack(alignment: .leading, spacing: 10) {
            ForEach(Array(blocks.enumerated()), id: \.offset) { _, block in
                renderBlock(block)
            }
        }
    }

    @ViewBuilder
    private func renderBlock(_ block: Block) -> some View {
        switch block {
        case .h1(let s):
            VStack(alignment: .leading, spacing: 8) {
                Text(inline(s))
                    .font(.system(size: 22, weight: .bold))
                    .foregroundStyle(Tj.Palette.text)
                    .fixedSize(horizontal: false, vertical: true)
                Rectangle()
                    .fill(Tj.Palette.ink)
                    .frame(height: 1)
                    .frame(maxWidth: .infinity)
            }
            .padding(.top, 2)
            .padding(.bottom, 4)

        case .h2(let s):
            HStack(alignment: .center, spacing: 8) {
                RoundedRectangle(cornerRadius: 1.5, style: .continuous)
                    .fill(Tj.Palette.brick)
                    .frame(width: 3, height: 16)
                Text(inline(s))
                    .font(.system(size: 16, weight: .semibold))
                    .foregroundStyle(Tj.Palette.text)
            }
            .padding(.top, 10)
            .padding(.bottom, 2)

        case .bullet(let s):
            if let abnormalText = Self.extractAbnormal(s) {
                HStack(alignment: .firstTextBaseline, spacing: 8) {
                    Image(systemName: "exclamationmark.triangle.fill")
                        .font(.system(size: 11))
                        .foregroundStyle(Tj.Palette.brick)
                    Text(inline(abnormalText))
                        .font(.system(size: 14, weight: .medium))
                        .foregroundStyle(Tj.Palette.text)
                        .fixedSize(horizontal: false, vertical: true)
                    Spacer(minLength: 0)
                }
                .padding(.horizontal, 10)
                .padding(.vertical, 7)
                .frame(maxWidth: .infinity, alignment: .leading)
                .background(
                    RoundedRectangle(cornerRadius: 6, style: .continuous)
                        .fill(Tj.Palette.brickSoft.opacity(0.55))
                )
                .overlay(alignment: .leading) {
                    RoundedRectangle(cornerRadius: 1.5, style: .continuous)
                        .fill(Tj.Palette.brick)
                        .frame(width: 3)
                }
            } else {
                HStack(alignment: .firstTextBaseline, spacing: 10) {
                    Circle()
                        .fill(Tj.Palette.text3)
                        .frame(width: 4, height: 4)
                        .padding(.top, 6)
                    Text(inline(s))
                        .font(.system(size: 14))
                        .foregroundStyle(Tj.Palette.text)
                        .fixedSize(horizontal: false, vertical: true)
                }
                .padding(.leading, 2)
            }

        case .body(let s):
            Text(inline(s))
                .font(.system(size: 14))
                .lineSpacing(3)
                .foregroundStyle(Tj.Palette.text)
                .fixedSize(horizontal: false, vertical: true)

        case .gap:
            Spacer().frame(height: 4)
        }
    }

    /// 如果 bullet 文本以 ⚠️ 或常见异常关键词开头,返回 strip 掉前缀后的纯文本。
    /// 否则返回 nil(表示不是异常项)。
    private static func extractAbnormal(_ s: String) -> String? {
        let trimmed = s.trimmingCharacters(in: .whitespaces)
        if trimmed.hasPrefix("⚠️") {
            return trimmed.replacingOccurrences(of: "⚠️", with: "")
                          .trimmingCharacters(in: .whitespaces)
        }
        // 关键词兜底高亮,但排除否定语境(「无异常」「未见偏高」「没有偏低」等),
        // 否则正常结论会被误标红。判断:信号词前最近 4 字内出现否定词即视为否定。
        let negations = ["无", "未", "没"]
        let abnormalSignals = ["偏高", "偏低", "异常", "过高", "过低"]
        for sig in abnormalSignals {
            guard let r = trimmed.range(of: sig) else { continue }
            let window = String(trimmed[..<r.lowerBound].suffix(4))
            if negations.contains(where: { window.contains($0) }) { continue }
            return trimmed
        }
        return nil
    }

    private func inline(_ s: String) -> AttributedString {
        // **bold** / *italic* / [text](url) 走 AttributedString markdown 解析
        if let attr = try? AttributedString(
            markdown: s,
            options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
        ) {
            return attr
        }
        return AttributedString(s)
    }

    // MARK: - 行级解析

    enum Block {
        case h1(String)
        case h2(String)
        case bullet(String)
        case body(String)
        case gap
    }

    static func parse(_ raw: String) -> [Block] {
        var out: [Block] = []
        let lines = raw.replacingOccurrences(of: "\r\n", with: "\n").components(separatedBy: "\n")
        for line in lines {
            let t = line.trimmingCharacters(in: .whitespaces)
            if t.isEmpty {
                // 连续空行折叠成一个 gap
                if case .gap = out.last { continue }
                out.append(.gap)
                continue
            }
            if t.hasPrefix("# ") {
                out.append(.h1(String(t.dropFirst(2))))
            } else if t.hasPrefix("## ") {
                out.append(.h2(String(t.dropFirst(3))))
            } else if t.hasPrefix("### ") {
                out.append(.h2(String(t.dropFirst(4))))
            } else if t.hasPrefix("- ") || t.hasPrefix("* ") {
                out.append(.bullet(String(t.dropFirst(2))))
            } else {
                out.append(.body(t))
            }
        }
        return out
    }
}

#Preview("HealthExportSheet · 空状态") {
    HealthExportSheet()
        .modelContainer(for: [
            Indicator.self, Report.self, DiaryEntry.self, Asset.self,
            ChatTurn.self, Symptom.self, UserProfile.self,
            MetricReminder.self, CustomMonitorMetric.self, HealthExport.self
        ], inMemory: true)
}

#Preview("MarkdownView · 演示") {
    ScrollView {
        MarkdownView(text: """
        # 就诊摘要 — 感冒就诊

        ## 主诉
        患者男,38 岁,感冒 3 天未愈,主诉鼻塞、咳嗽、低烧。

        ## 患者背景
        - 高血压 2 年
        - 在服药:**缬沙坦 80mg qd**
        - 过敏:青霉素

        ## 近期症状
        - 2026-05-24 感冒(进行中,severity 2):鼻塞、低烧
        - 2026-05-20 头痛(已结束)

        ## 关键指标
        - ⚠️ 收缩压 142 mmHg (参考 <140) — 2026-05-26
        - 体温 37.2 ℃ (参考 36-37) — 2026-05-25
        """)
        .padding()
    }
    .background(Tj.Palette.sand)
}
