import Foundation

/// 导出身体档案「## 指标趋势」段的一条趋势摘要。
///
/// 设计见 `docs/superpowers/specs/2026-06-07-export-indicator-trends-design.md`:
/// 对本次就诊相关、且时间窗内有 ≥2 次记录的指标,给一行确定性摘要
/// (首值→末值 + 方向 + 时间跨度 + 次数),**不经 LLM**,与 `ReportCompareService` 同思路,
/// 从根上杜绝小模型编造趋势数字(§10#5 失败回退 / §12#6 禁止编造)。
struct ExportTrend: Sendable {

    enum Direction: Sendable {
        case up, down, flat
        var arrow: String {
            switch self {
            case .up:   return "↑"
            case .down: return "↓"
            case .flat: return "→"
            }
        }
    }

    let title: String
    let unit: String
    /// "152→138" 或血压双值 "152/96→138/88"。
    let valueText: String
    let direction: Direction
    /// 参考范围文本,如 "90-140";无(单边范围解析不出 / 血压双范围)则 nil。
    let rangeText: String?
    /// 首末两次记录之间的天数。
    let spanDays: Int
    /// 时间窗内记录次数。
    let count: Int
    /// 末值仍异常,或状态跨越了参考范围边界 → 行首加 ⚠️。
    let flagged: Bool

    /// 一行中文:`⚠️ 收缩压 152→138 mmHg ↓（参考 90-140），近 21 天 4 次`
    func line() -> String {
        var s = flagged ? "⚠️ " : ""
        s += title
        s += " \(valueText)"
        if !unit.isEmpty { s += " \(unit)" }
        s += " \(direction.arrow)"
        if let r = rangeText, !r.isEmpty { s += "（参考 \(r)）" }
        s += "，近 \(spanDays) 天 \(count) 次"
        return s
    }
}

enum ExportTrendBuilder {

    /// 平稳阈值:首末相对变化 < 5% 视为「平稳(→)」。
    static let flatThreshold = 0.05

    /// 构建趋势摘要列表。
    /// - Parameters:
    ///   - allInWindow: 时间窗内**全部**指标(裁剪前)—— 用来还原完整时间序列。
    ///   - relevant: 本次就诊**相关**指标集(裁剪后)—— 只对这些 series 出趋势。
    ///   - profile: 用于解析性别相关的参考范围(交给 SeriesBucket)。
    ///   - customMetrics: 自定义监测项,用于解析自定义 series 的名称/范围。
    /// - Returns: 已按「异常优先,其次最近」排序的趋势行。
    static func build(allInWindow: [Indicator],
                      relevant: [Indicator],
                      profile: UserProfile? = nil,
                      customMetrics: [CustomMonitorMetric] = []) -> [ExportTrend] {
        let relevantIDs = Set(relevant.compactMap { bucketID(for: $0) })
        guard !relevantIDs.isEmpty else { return [] }

        // 复用 Trends 的分组逻辑:同 seriesKey 分组、血压合并、name+unit 回退、minPoints≥2、点按时间升序。
        let buckets = SeriesBucket.build(from: allInWindow,
                                         profile: profile,
                                         customMetrics: customMetrics,
                                         minPoints: 2)

        let trends = buckets
            .filter { relevantIDs.contains($0.id) }
            .compactMap { trend(from: $0) }

        // 异常优先,其次最近。
        return trends.sorted { lhs, rhs in
            if lhs.flagged != rhs.flagged { return lhs.flagged }
            return lhs.spanDays >= rhs.spanDays   // 仅作稳定次序,实际新近性已由 buckets 顺序保证
        }
    }

    /// 指标 → 其所属 SeriesBucket 的 id(与 `SeriesBucket.build` 的 id 方案一致)。
    /// nil 表示该指标无法归入任何 series(空名)。
    static func bucketID(for i: Indicator) -> String? {
        if let k = i.seriesKey, !k.isEmpty {
            if k == "bp.systolic" || k == "bp.diastolic" { return "bp" }
            return k
        }
        let nk = SeriesBucket.normalizedKey(name: i.name, unit: i.unit)
        return nk.isEmpty ? nil : "lab:\(nk)"
    }

    // MARK: - Private

    private static func trend(from bucket: SeriesBucket) -> ExportTrend? {
        if bucket.id == "bp" { return bpTrend(from: bucket) }

        guard let line = bucket.lines.first,
              line.points.count >= 2,
              let first = line.points.first,
              let last = line.points.last else { return nil }

        return ExportTrend(
            title: bucket.title,
            unit: bucket.unit,
            valueText: "\(num(first.value))→\(num(last.value))",
            direction: direction(first: first.value, last: last.value),
            rangeText: rangeText(line.referenceRange),
            spanDays: spanDays(first.date, last.date),
            count: line.points.count,
            flagged: last.status != .normal
                || crossedBoundary(first: first.status, last: last.status)
        )
    }

    /// 血压:收缩 + 舒张合成一行,方向以收缩压为准;不展示参考(收缩/舒张范围不同,保持简洁)。
    private static func bpTrend(from bucket: SeriesBucket) -> ExportTrend? {
        guard let sys = bucket.lines.first(where: { $0.seriesKey == "bp.systolic" }),
              sys.points.count >= 2,
              let sFirst = sys.points.first,
              let sLast = sys.points.last else { return nil }

        let dia = bucket.lines.first { $0.seriesKey == "bp.diastolic" }
        let dFirst = dia?.points.first
        let dLast = dia?.points.last

        let valueText: String
        if let dFirst, let dLast {
            valueText = "\(num(sFirst.value))/\(num(dFirst.value))→\(num(sLast.value))/\(num(dLast.value))"
        } else {
            valueText = "\(num(sFirst.value))→\(num(sLast.value))"
        }

        let sysFlag = sLast.status != .normal
            || crossedBoundary(first: sFirst.status, last: sLast.status)
        let diaFlag = dLast.map { $0.status != .normal } ?? false

        return ExportTrend(
            title: bucket.title,
            unit: bucket.unit,
            valueText: valueText,
            direction: direction(first: sFirst.value, last: sLast.value),
            rangeText: nil,
            spanDays: spanDays(sFirst.date, sLast.date),
            count: sys.points.count,
            flagged: sysFlag || diaFlag
        )
    }

    static func direction(first: Double, last: Double) -> ExportTrend.Direction {
        let delta = last - first
        let base = abs(first)
        let rel = base > 0 ? abs(delta) / base : abs(delta)
        if rel < flatThreshold { return .flat }
        return delta > 0 ? .up : .down
    }

    /// 状态是否跨越了参考范围边界(正常↔异常之间发生切换)。
    static func crossedBoundary(first: IndicatorStatus, last: IndicatorStatus) -> Bool {
        (first == .normal) != (last == .normal)
    }

    static func spanDays(_ from: Date, _ to: Date) -> Int {
        let days = to.timeIntervalSince(from) / 86400
        return max(1, Int(days.rounded()))
    }

    static func rangeText(_ r: ClosedRange<Double>?) -> String? {
        guard let r else { return nil }
        return "\(num(r.lowerBound))-\(num(r.upperBound))"
    }

    /// 数值格式化:整数去小数点,其余去掉尾随 0(138.0→"138",6.10→"6.1")。
    static func num(_ v: Double) -> String {
        if v.truncatingRemainder(dividingBy: 1) == 0 { return String(Int(v)) }
        return String(format: "%g", v)
    }
}
