import SwiftUI
import SwiftData

/// 「我的 · 个人资料」编辑页。Form 风格,即改即存(无显式 Save 按钮)。
/// UserProfile 是 SwiftData 单例:进入时通过 UserProfileStore.loadOrCreate 拿到。
struct ProfileEditView: View {
    @Environment(\.modelContext) private var ctx
    @Query private var profiles: [UserProfile]

    var body: some View {
        if let p = profiles.first {
            ProfileEditForm(profile: p)
        } else {
            ProgressView()
                .onAppear { _ = UserProfileStore.loadOrCreate(in: ctx) }
        }
    }
}

/// 实际表单。
///
/// 性能要点(为什么拆成一堆小 Row 子视图):
/// SwiftData `@Model` 走 Observation,谁读了某个属性谁才会因它变化而失效。
/// 早期这页把所有字段塞进一个 `body`,任何一次按键(包括「添加过敏/用药」输入框,
/// 它们的 `@State` 当时也挂在父视图上)都会重算整个 `body`,顺带把年份选择器里
/// 126 个 `Text(year)` 全部重建一遍 → 输入卡顿。
///
/// 现在的写法:
/// - `ProfileEditForm.body` 不读任何 `profile.*`、不持有随打字变化的 `@State`,
///   所以编辑过程中它整体不再重算,只是组合一批子视图。
/// - 每个 Row / Section 子视图只读自己那一个字段,Observation 把失效范围收到单行。
/// - 各「添加条目」输入框的 `@State` 下沉进各自的 Section 子视图,敲字只重算那一节。
/// - 年份用「点开展开 .wheel 滚轮」,折叠时不构建 126 项,展开时由原生
///   UIPickerView 虚拟化承载,秒开。
private struct ProfileEditForm: View {
    @Environment(\.modelContext) private var ctx
    @Bindable var profile: UserProfile
    @State private var healthImportDraft: HealthProfileImportDraft?
    @State private var healthImportError: String?
    @State private var isImportingHealthProfile = false

    var body: some View {
        Form {
            Section {
                Button {
                    importHealthProfile()
                } label: {
                    HStack(spacing: 10) {
                        if isImportingHealthProfile {
                            ProgressView()
                        } else {
                            Image(systemName: "heart.text.square")
                                .foregroundStyle(Tj.Palette.ink)
                        }
                        VStack(alignment: .leading, spacing: 2) {
                            Text("从 Apple 健康导入")
                                .foregroundStyle(Tj.Palette.text)
                            Text("只读取生日、性别、身高、血型")
                                .font(.tjScaled( 12))
                                .foregroundStyle(Tj.Palette.text3)
                        }
                    }
                }
                .disabled(isImportingHealthProfile)
                .accessibilityElement(children: .combine)
                .accessibilityLabel("从 Apple 健康导入")
                .accessibilityHint("读取生日、性别、身高和血型,确认后填入个人资料")
            } footer: {
                Text("导入前会先显示预览,确认后才覆盖个人资料。")
            }

            Section {
                BirthYearRow(profile: profile)
                SexRow(profile: profile)
                HeightRow(profile: profile)
                WeightRow(profile: profile)
                BloodTypeRow(profile: profile)
            } header: {
                Text("基本")
            } footer: {
                BMIFooter(profile: profile)
            }

            ChronicSection(profile: profile)

            StringListSection(title: String(appLoc: "过敏史"), placeholder: String(appLoc: "如:青霉素"),
                              items: $profile.allergies)
            StringListSection(title: String(appLoc: "家族史"), placeholder: String(appLoc: "如:母亲 高血压"),
                              items: $profile.familyHistory)
        }
        .navigationTitle("个人资料")
        .navigationBarTitleDisplayMode(.inline)
        .scrollContentBackground(.hidden)
        .background(Tj.Palette.sand.ignoresSafeArea())
        .onDisappear {
            profile.updatedAt = .now
            try? ctx.save()
        }
        .sheet(item: $healthImportDraft) { draft in
            HealthProfileImportPreviewSheet(
                draft: draft,
                profile: profile
            ) {
                draft.apply(to: profile)
                try? ctx.save()
                healthImportDraft = nil
            }
        }
        .alert("无法导入 Apple 健康资料", isPresented: Binding(
            get: { healthImportError != nil },
            set: { if !$0 { healthImportError = nil } }
        )) {
            Button("好", role: .cancel) { healthImportError = nil }
        } message: {
            Text(healthImportError ?? "")
        }
    }

    private func importHealthProfile() {
        guard !isImportingHealthProfile else { return }
        isImportingHealthProfile = true
        healthImportError = nil
        Task {
            do {
                healthImportDraft = try await HealthProfileImportService.shared.fetchDraft()
            } catch {
                healthImportError = error.localizedDescription
            }
            isImportingHealthProfile = false
        }
    }
}

private struct HealthProfileImportPreviewSheet: View {
    @Environment(\.dismiss) private var dismiss
    let draft: HealthProfileImportDraft
    let profile: UserProfile
    let onApply: () -> Void

    private var preview: HealthProfileImportPreview {
        HealthProfileImportPreview(draft: draft, current: profile)
    }

    var body: some View {
        NavigationStack {
            List {
                Section {
                    ForEach(preview.fields, id: \.title) { field in
                        HStack(alignment: .firstTextBaseline) {
                            Text(field.title)
                                .foregroundStyle(Tj.Palette.text)
                            Spacer(minLength: 12)
                            VStack(alignment: .trailing, spacing: 4) {
                                Text(field.imported ?? "未读取到")
                                    .foregroundStyle(field.imported == nil ? Tj.Palette.text3 : Tj.Palette.text)
                                Text("当前: \(field.current)")
                                    .font(.tjScaled( 11))
                                    .foregroundStyle(Tj.Palette.text3)
                            }
                        }
                    }
                } footer: {
                    Text("未读取到的字段不会修改。")
                }
            }
            .navigationTitle("确认导入")
            .navigationBarTitleDisplayMode(.inline)
            .scrollContentBackground(.hidden)
            .background(Tj.Palette.sand.ignoresSafeArea())
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("取消") { dismiss() }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("导入") {
                        onApply()
                        dismiss()
                    }
                    .fontWeight(.semibold)
                }
            }
        }
    }
}

// MARK: - 基本:逐行子视图(各自只读一个字段,失效互不牵连)

/// 出生年份:点击行展开 `.wheel` 滚轮,折叠时只是一行文字 —— 不构建 126 项列表。
private struct BirthYearRow: View {
    @Bindable var profile: UserProfile
    @State private var expanded = false

    private var currentYear: Int {
        Calendar.current.component(.year, from: .now)
    }

    /// 年份倒序数组。本行仅在 birthYear / expanded 变化时重算,与其他字段编辑解耦;
    /// 且 `years` 只在滚轮展开(body 实际读它)时才被遍历构建。
    private var years: [Int] {
        Array((1900...currentYear).reversed())
    }

    private var selectedLabel: String {
        if let y = profile.birthYear {
            let age = currentYear - y
            return age >= 0 ? "\(y)(\(age)\(String(appLoc: "岁")))" : String(y)
        }
        return String(appLoc: "未设置")
    }

    private var yearBinding: Binding<Int> {
        Binding(
            get: { profile.birthYear ?? 0 },
            set: { profile.birthYear = $0 == 0 ? nil : $0 }
        )
    }

    var body: some View {
        Button {
            withAnimation(.easeInOut(duration: 0.2)) { expanded.toggle() }
        } label: {
            HStack {
                Text("出生年份").foregroundStyle(Tj.Palette.text)
                Spacer()
                Text(selectedLabel)
                    .foregroundStyle(profile.birthYear == nil ? Tj.Palette.text3 : Tj.Palette.text2)
                Image(systemName: "chevron.right")
                    .font(.tjScaled( 12, weight: .semibold))
                    .foregroundStyle(Tj.Palette.text3)
                    .rotationEffect(.degrees(expanded ? 90 : 0))
            }
            .contentShape(Rectangle())
        }
        .buttonStyle(.plain)

        if expanded {
            Picker("出生年份", selection: yearBinding) {
                Text("未设置").tag(0)
                ForEach(years, id: \.self) { year in
                    Text(String(year)).tag(year)
                }
            }
            .pickerStyle(.wheel)
            .frame(maxHeight: 140)
        }
    }
}

private struct SexRow: View {
    @Bindable var profile: UserProfile

    var body: some View {
        Picker("性别", selection: Binding(
            get: { profile.sex },
            set: { profile.sex = $0 }
        )) {
            ForEach(UserProfile.Sex.allCases, id: \.self) { s in
                Text(s.label).tag(s)
            }
        }
        .pickerStyle(.segmented)
    }
}

/// 身高:数值输入逻辑不变,只把整行变成可点聚焦区 —— 原先只有右侧 80pt 的输入框
/// 本体能点中,标签与中间空白点了不聚焦,所以显得「不灵敏」。
private struct HeightRow: View {
    @Bindable var profile: UserProfile
    @FocusState private var focused: Bool

    var body: some View {
        HStack {
            Text("身高")
            Spacer()
            TextField("cm", value: $profile.heightCM, format: .number)
                .keyboardType(.numberPad)
                .multilineTextAlignment(.trailing)
                .frame(width: 80)
                .focused($focused)
            Text("cm").foregroundStyle(Tj.Palette.text3)
        }
        .contentShape(Rectangle())
        .onTapGesture { focused = true }
    }
}

private struct WeightRow: View {
    @Bindable var profile: UserProfile
    @FocusState private var focused: Bool

    var body: some View {
        HStack {
            Text("体重")
            Spacer()
            TextField("kg", value: $profile.weightKG, format: .number.precision(.fractionLength(0...1)))
                .keyboardType(.decimalPad)
                .multilineTextAlignment(.trailing)
                .frame(width: 80)
                .focused($focused)
            Text("kg").foregroundStyle(Tj.Palette.text3)
        }
        .contentShape(Rectangle())
        .onTapGesture { focused = true }
    }
}

private struct BloodTypeRow: View {
    @Bindable var profile: UserProfile

    var body: some View {
        Picker("血型", selection: $profile.bloodTypeRaw) {
            Text("不知道").tag("")
            Text("A 型").tag("A")
            Text("B 型").tag("B")
            Text("AB 型").tag("AB")
            Text("O 型").tag("O")
        }
    }
}

/// BMI 页脚:只读 heightCM + weightKG,只有这两项变化时才重算。
private struct BMIFooter: View {
    @Bindable var profile: UserProfile

    var body: some View {
        if let bmi = profile.bmi {
            Text("BMI: \(String(format: "%.1f", bmi))  \(label(bmi))")
                .font(.tjScaled( 11))
        }
    }

    private func label(_ bmi: Double) -> String {
        switch bmi {
        case ..<18.5:  return String(appLoc: "(偏瘦)")
        case ..<24:    return String(appLoc: "(正常)")
        case ..<28:    return String(appLoc: "(超重)")
        default:       return String(appLoc: "(肥胖)")
        }
    }
}

// MARK: - 慢病

private struct ChronicSection: View {
    @Bindable var profile: UserProfile
    @State private var newCustomCondition = ""

    /// 计算属性形式:每次按当前语言解析,语言切换即时更新(不可用 static/let 缓存)。
    private var presets: [String] {
        [String(appLoc: "高血压"), String(appLoc: "糖尿病"), String(appLoc: "冠心病"), String(appLoc: "高血脂"),
         String(appLoc: "甲状腺疾病"), String(appLoc: "哮喘"), String(appLoc: "慢性肾病"), String(appLoc: "抑郁/焦虑")]
    }

    var body: some View {
        Section {
            FlexibleChipGrid {
                ForEach(presets, id: \.self) { name in
                    chip(label: name,
                         selected: profile.chronicConditions.contains(name)) {
                        toggle(name)
                    }
                }
                ForEach(profile.chronicConditions.filter { !presets.contains($0) },
                        id: \.self) { name in
                    chip(label: name, selected: true) {
                        profile.chronicConditions.removeAll { $0 == name }
                    }
                }
            }

            EntryInputField(placeholder: String(appLoc: "自定义慢病"), text: $newCustomCondition) {
                let trimmed = newCustomCondition.trimmingCharacters(in: .whitespaces)
                guard !trimmed.isEmpty,
                      !profile.chronicConditions.contains(trimmed) else { return }
                profile.chronicConditions.append(trimmed)
                newCustomCondition = ""
            }
            .listRowBackground(Color.clear)
        } header: {
            Text("慢病(影响参考范围与 AI 解读)")
        }
    }

    private func toggle(_ name: String) {
        if profile.chronicConditions.contains(name) {
            profile.chronicConditions.removeAll { $0 == name }
        } else {
            profile.chronicConditions.append(name)
        }
    }

    private func chip(label: String, selected: Bool, action: @escaping () -> Void) -> some View {
        Button(action: action) {
            Text(label)
                .font(.tjScaled( 13, weight: selected ? .semibold : .regular))
                .foregroundStyle(selected ? Tj.Palette.paper : Tj.Palette.text)
                .padding(.horizontal, 12)
                .padding(.vertical, 6)
                .background(Capsule().fill(selected ? Tj.Palette.ink : Tj.Palette.paper))
                .overlay(Capsule().strokeBorder(Tj.Palette.line, lineWidth: selected ? 0 : 1))
        }
        .buttonStyle(.plain)
    }
}

// MARK: - 聊天框风格的条目输入(圆角容器 + 多行增长 + 圆形发送按钮)

/// 替代原先单行 `TextField + “加”` 的搜索框观感:文字随内容换行增长(1~4 行),
/// 右侧圆形发送按钮(内容为空时禁用变灰)。过敏 / 家族史 / 用药 / 自定义慢病共用。
private struct EntryInputField: View {
    let placeholder: String
    @Binding var text: String
    var onSubmit: () -> Void

    private var canSubmit: Bool {
        !text.trimmingCharacters(in: .whitespaces).isEmpty
    }

    var body: some View {
        HStack(alignment: .bottom, spacing: 8) {
            TextField(placeholder, text: $text, axis: .vertical)
                .lineLimit(1...5)
                .foregroundStyle(Tj.Palette.text)   // 固定深色:避免深色模式下继承系统 .primary 变白看不清
                .tint(Tj.Palette.ink)
                .frame(minHeight: 40, alignment: .top)   // 初始就有聊天框体量,内容多了随 axis 增长
                .padding(.horizontal, 14)
                .padding(.vertical, 10)
                .background(
                    RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
                        .fill(Tj.Palette.paper)
                )
                .overlay(
                    RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
                        .strokeBorder(Tj.Palette.line, lineWidth: 1)
                )

            Button {
                if canSubmit { onSubmit() }
            } label: {
                Image(systemName: "arrow.up.circle.fill")
                    .font(.tjScaled(28))
                    .foregroundStyle(canSubmit ? Tj.Palette.ink : Tj.Palette.text3)
            }
            .buttonStyle(.plain)
            .disabled(!canSubmit)
        }
        .padding(.vertical, 2)
    }
}

// MARK: - 过敏 / 家族史 / 用药(每节自带 @State,敲字只重算本节)

private struct StringListSection: View {
    let title: String
    let placeholder: String
    @Binding var items: [String]
    @State private var newInput = ""

    var body: some View {
        Section(title) {
            ForEach(items, id: \.self) { item in
                HStack {
                    Text(item)
                    Spacer()
                    Button(role: .destructive) {
                        items.removeAll { $0 == item }
                    } label: {
                        Image(systemName: "minus.circle")
                            .foregroundStyle(Tj.Palette.brick)
                    }
                    .buttonStyle(.borderless)
                }
            }
            EntryInputField(placeholder: placeholder, text: $newInput) {
                let trimmed = newInput.trimmingCharacters(in: .whitespaces)
                guard !trimmed.isEmpty, !items.contains(trimmed) else { return }
                items.append(trimmed)
                newInput = ""
            }
            .listRowBackground(Color.clear)
        }
    }
}

// MARK: - 流式 chip 布局(SwiftUI 无原生 Wrap,用 Layout 协议自实现)

struct FlexibleChipGrid<Content: View>: View {
    @ViewBuilder let content: () -> Content

    var body: some View {
        FlowLayout { content() }
    }
}

private struct FlowLayout: Layout {
    var spacing: CGFloat = 6

    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let maxWidth = proposal.width ?? .infinity
        var rows: [CGFloat] = [0]
        var rowMaxHeight: [CGFloat] = [0]
        var x: CGFloat = 0
        for s in subviews {
            let size = s.sizeThatFits(.unspecified)
            if x + size.width > maxWidth, x > 0 {
                rows.append(0); rowMaxHeight.append(0)
                x = 0
            }
            rows[rows.count - 1] = max(rows[rows.count - 1], x + size.width)
            rowMaxHeight[rowMaxHeight.count - 1] = max(rowMaxHeight.last ?? 0, size.height)
            x += size.width + spacing
        }
        let totalHeight = rowMaxHeight.reduce(0, +) + spacing * CGFloat(max(0, rows.count - 1))
        let totalWidth = rows.max() ?? 0
        return CGSize(width: totalWidth, height: totalHeight)
    }

    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        var x: CGFloat = bounds.minX
        var y: CGFloat = bounds.minY
        var rowHeight: CGFloat = 0
        for s in subviews {
            let size = s.sizeThatFits(.unspecified)
            if x + size.width > bounds.maxX, x > bounds.minX {
                x = bounds.minX
                y += rowHeight + spacing
                rowHeight = 0
            }
            s.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(size))
            x += size.width + spacing
            rowHeight = max(rowHeight, size.height)
        }
    }
}

#Preview {
    NavigationStack {
        ProfileEditView()
    }
    .modelContainer(for: [UserProfile.self], inMemory: true)
}
