import SwiftUI
import SwiftData
import Foundation

enum TimelineKind: String, CaseIterable, Identifiable {
    case diary, symptom, indicator, medication, report
    var id: String { rawValue }

    var label: String {
        switch self {
        case .indicator:  return String(appLoc: "指标")
        case .report:     return String(appLoc: "报告")
        case .symptom:    return String(appLoc: "症状")
        case .diary:      return String(appLoc: "日记")
        case .medication: return String(appLoc: "用药")
        }
    }

    var icon: String {
        switch self {
        case .indicator:  return "drop.fill"
        case .report:     return "doc.fill"
        case .symptom:    return "waveform.path.ecg"
        case .diary:      return "pencil"
        case .medication: return "pills.fill"
        }
    }

    var accent: Color {
        switch self {
        case .indicator:  return Tj.Palette.brick
        case .report:     return Tj.Palette.ink2
        case .symptom:    return Tj.Palette.amber
        case .diary:      return Tj.Palette.leaf
        case .medication: return Tj.Palette.ink
        }
    }
}

struct TimelineEntry: Identifiable, Hashable {
    let id: String
    let kind: TimelineKind
    let date: Date
    let title: String
    var subtitle: String
    let trailing: String?
    let trailingIsAlert: Bool
    let isOngoing: Bool
    /// 同名指标聚合后的累计次数(>1 时副标题附「共 N 次」)。非聚合条目恒为 1。
    var aggregateCount: Int = 1

    static func from(indicator i: Indicator) -> TimelineEntry {
        TimelineEntry(
            id: "indicator-\(i.persistentModelID)",
            kind: .indicator,
            date: i.capturedAt,
            title: i.name,
            subtitle: typeSubtitle(for: i),
            trailing: indicatorValue(i),
            trailingIsAlert: i.status != .normal,
            isOngoing: false
        )
    }

    /// 批处理 Indicator 列表,把 bp.systolic + bp.diastolic 同 capturedAt 合并成
    /// 一条 "血压 120/80 mmHg" timeline entry。其他 series 逐条 from(indicator:)。
    /// 合并条件:capturedAt 差 ≤ 5 秒(防止跨次混淆)。
    static func from(indicators: [Indicator]) -> [TimelineEntry] {
        var entries: [TimelineEntry] = []
        var consumed = Set<PersistentIdentifier>()

        // 先找 bp.systolic,配 bp.diastolic
        for sys in indicators where sys.seriesKey == "bp.systolic" {
            if consumed.contains(sys.persistentModelID) { continue }
            guard let dia = indicators.first(where: {
                $0.seriesKey == "bp.diastolic" &&
                !consumed.contains($0.persistentModelID) &&
                abs($0.capturedAt.timeIntervalSince(sys.capturedAt)) <= 5
            }) else { continue }
            consumed.insert(sys.persistentModelID)
            consumed.insert(dia.persistentModelID)
            entries.append(mergedBP(systolic: sys, diastolic: dia))
        }

        // 剩下的 indicator(含未配对的 systolic/diastolic、其他 series、自由输入)
        for i in indicators where !consumed.contains(i.persistentModelID) {
            entries.append(from(indicator: i))
        }
        return entries
    }

    /// 「记录」列表 / 首页最近记录用:把同名(同类组)指标聚合成一条,代表取最新一次,
    /// 附带该组累计次数(`aggregateCount`,>1 时副标题缀「共 N 次」)。
    /// 点代表条目跳 `IndicatorSeriesDetailView` 看历次。分组口径与聚合详情 / 趋势一致
    /// (`IndicatorGroup`):血压(bp.*)并一组、有 seriesKey 的按 key、无 seriesKey 的按 name+unit 归一。
    static func aggregatedIndicators(_ indicators: [Indicator]) -> [TimelineEntry] {
        var order: [String] = []
        var groups: [String: [Indicator]] = [:]
        for i in indicators {
            let key = IndicatorGroup.of(i).id
            if groups[key] == nil { order.append(key) }
            groups[key, default: []].append(i)
        }
        return order.compactMap { key -> TimelineEntry? in
            guard let members = groups[key] else { return nil }
            // 该组逐条条目(血压已合并 sys/dia),取最新一条作代表。
            guard var rep = from(indicators: members).max(by: { $0.date < $1.date }) else { return nil }
            // 次数:血压按测量次数(bp.systolic 条数),其余按成员条数。
            let count = key == IndicatorGroup.bloodPressure.id
                ? members.filter { $0.seriesKey == "bp.systolic" }.count
                : members.count
            rep.aggregateCount = count
            if count > 1 {
                rep.subtitle += " · " + String(appLoc: "共 \(count) 次")
            }
            return rep
        }
    }

    private static func mergedBP(systolic sys: Indicator, diastolic dia: Indicator) -> TimelineEntry {
        let abnormal = sys.status != .normal || dia.status != .normal
        // 方向箭头按实际 status 给:两值同向才标 ↑/↓;一高一低只标红不给方向
        // (旧实现异常一律 ↑,低血压 85/55 会错误显示 ↑)。
        let arrow: String
        switch (sys.status, dia.status) {
        case (.high, .high), (.high, .normal), (.normal, .high): arrow = " ↑"
        case (.low, .low), (.low, .normal), (.normal, .low):     arrow = " ↓"
        default:                                                  arrow = ""
        }
        return TimelineEntry(
            id: "bp-\(sys.persistentModelID)-\(dia.persistentModelID)",
            kind: .indicator,
            date: sys.capturedAt,
            title: String(appLoc: "血压"),
            subtitle: String(appLoc: "长期监测"),
            trailing: "\(sys.value)/\(dia.value) mmHg" + arrow,
            trailingIsAlert: abnormal,
            isOngoing: false
        )
    }

    static func from(report r: Report) -> TimelineEntry {
        let highCount = r.indicators.filter { $0.status == .high }.count
        let lowCount  = r.indicators.filter { $0.status == .low }.count
        return TimelineEntry(
            id: "report-\(r.persistentModelID)",
            kind: .report,
            date: r.reportDate,
            title: r.title,
            subtitle: "\(r.type.label) · " + String(appLoc: "共 \(r.pageCount) 页"),
            trailing: abnormalSummary(high: highCount, low: lowCount),
            trailingIsAlert: highCount + lowCount > 0,
            isOngoing: false
        )
    }

    /// 异常计数 → trailing 文案。只高→「N 项偏高」、只低→「N 项偏低」、混合→「N 项异常」、无→nil。
    /// 旧实现一律写「N 项偏高」,只含偏低指标的报告会显示与事实相反的结论(demo 翻车点)。
    static func abnormalSummary(high: Int, low: Int) -> String? {
        switch (high, low) {
        case (0, 0):         return nil
        case (let h, 0):     return String(appLoc: "\(h) 项偏高")
        case (0, let l):     return String(appLoc: "\(l) 项偏低")
        case (let h, let l): return String(appLoc: "\(h + l) 项异常")
        }
    }

    /// 带「用药」tag 的日记(拍药盒入档)归到 .medication 分类,其余是普通文字日记。
    /// id 统一用 "diary-" 前缀:TimelineDetail.resolve 两个分类都反查 diaries。
    static func from(diary d: DiaryEntry) -> TimelineEntry {
        let isMed = d.isMedicationLog
        return TimelineEntry(
            id: "diary-\(d.persistentModelID)",
            kind: isMed ? .medication : .diary,
            date: d.createdAt,
            title: d.content.firstLine(),
            subtitle: isMed ? String(appLoc: "用药记录") : String(appLoc: "文字日记"),
            trailing: nil,
            trailingIsAlert: false,
            isOngoing: false
        )
    }

    static func from(symptom s: Symptom) -> TimelineEntry {
        let ongoing = s.isOngoing
        let date = s.endedAt ?? s.startedAt
        let subtitle: String
        let trailing: String?
        if ongoing {
            subtitle = String(appLoc: "症状 · 持续中")
            trailing = String(appLoc: "持续 \(formatDuration(s.duration))")
        } else {
            subtitle = String(appLoc: "症状 · 已结束")
            trailing = String(appLoc: "持续 \(formatDuration(s.duration))")
        }
        return TimelineEntry(
            id: "symptom-\(s.persistentModelID)",
            kind: .symptom,
            date: date,
            title: s.name,
            subtitle: subtitle,
            trailing: trailing,
            trailingIsAlert: ongoing,
            isOngoing: ongoing
        )
    }

    private static func typeSubtitle(for i: Indicator) -> String {
        if let report = i.report {
            return String(appLoc: "指标 · \(report.title)")
        }
        return i.source.label
    }

    private static func indicatorValue(_ i: Indicator) -> String {
        let unit = i.unit.isEmpty ? "" : " \(i.unit)"
        let arrow: String
        switch i.status {
        case .high:   arrow = " ↑"
        case .low:    arrow = " ↓"
        case .normal: arrow = ""
        }
        return "\(i.value)\(unit)\(arrow)"
    }
}

private extension String {
    func firstLine() -> String {
        let trimmed = trimmingCharacters(in: .whitespacesAndNewlines)
        if let line = trimmed.split(whereSeparator: \.isNewline).first {
            let s = String(line)
            return s.count > 40 ? String(s.prefix(40)) + "…" : s
        }
        return trimmed.isEmpty ? String(appLoc: "(空日记)") : trimmed
    }
}
