///|
/// 轨迹统计。
///
/// 从已解析的点序列算出距离、爬升、包围盒这类**派生量**。
/// 本模块只读 `Track` / `Waypoint`,不碰 XML。
///
/// 两个容易被做错、这里必须先讲清楚的约定:
///
/// 1. **跨 `trkseg` 不相连**。`trkseg` 表示 GPS 信号中断后重新捕获,
///    两个 segment 之间的直线位移不是真实运动轨迹,计入距离会凭空多出一大截。
///    所以距离和爬升都是**逐 segment 计算再累加**。
/// 2. **爬升必须带阈值**。GPS 高程噪声通常在几米量级,直接累加相邻点高差
///    会把来回抖动放大成几百米的虚假"爬升"——这是开源实现里最常见的 bug。

///|
/// 平均地球半径(IUGG 推荐值),单位米。
///
/// 地球是椭球,任何单一半径都是近似。这个值在几百公里尺度内的误差
/// 远小于 GPS 本身的误差,对轨迹统计够用。
pub let earth_radius_m : Double = 6371008.8

///|
/// 默认的爬升阈值(米)。
///
/// 低于这个幅度的高程变化被视为噪声,不计入累计爬升/下降。
/// `climb_m` 传 `0.0` 可关闭过滤(等价于朴素累加)。
pub let default_climb_threshold_m : Double = 3.0

///|
/// 半正矢公式算出的两点大圆距离,单位米。
///
/// 比余弦定理数值稳定——在两点非常接近时(轨迹点通常就是这种情况)
/// 余弦定理会因浮点精度丢失有效位。
pub fn haversine_m(a : Waypoint, b : Waypoint) -> Double {
  let d2r = 0.017453292519943295
  let lat1 = a.lat.to_double() * d2r
  let lon1 = a.lon.to_double() * d2r
  let lat2 = b.lat.to_double() * d2r
  let lon2 = b.lon.to_double() * d2r
  let s1 = @math.sin((lat2 - lat1) / 2.0)
  let s2 = @math.sin((lon2 - lon1) / 2.0)
  let h = s1 * s1 + @math.cos(lat1) * @math.cos(lat2) * s2 * s2
  // 浮点误差可能让 h 略微超出 [0, 1],asin 会给出 NaN
  let clamped = h.min(1.0).max(0.0)
  2.0 * earth_radius_m * @math.asin(clamped.sqrt())
}

///|
/// 一串点的路径总长(米),逐段相加。
pub fn path_distance_m(points : Array[Waypoint]) -> Double {
  let mut total = 0.0
  let mut i = 1
  while i < points.length() {
    total = total + haversine_m(points[i - 1], points[i])
    i = i + 1
  }
  total
}

///|
/// 累计爬升与下降(米),返回 `(爬升, 下降)`。
///
/// `threshold > 0` 时启用**滞后过滤**:高程变化先累积到 `pending`,
/// 只有累积量超过阈值才计入总数并清零。方向反转时先结算再重新累积。
/// 这样 3 米上 3 米下的来回抖动会被抵消掉,而真实的一次 50 米爬升不会。
///
/// `threshold <= 0` 退化为朴素累加(每一步的高差都直接计入)。
/// 没有 `ele` 的点被跳过,不影响累积。
pub fn climb_m(
  points : Array[Waypoint],
  threshold : Double,
) -> (Double, Double) {
  let mut ascend = 0.0
  let mut descend = 0.0
  let mut pending = 0.0
  let mut prev : Double? = None
  let filter = threshold > 0.0
  for p in points {
    match p.ele {
      Some(e) => {
        match prev {
          Some(pe) =>
            if filter {
              pending = pending + (e - pe)
              if pending >= threshold {
                ascend = ascend + pending
                pending = 0.0
              } else if pending <= 0.0 - threshold {
                descend = descend - pending
                pending = 0.0
              }
            } else {
              let d = e - pe
              if d > 0.0 {
                ascend = ascend + d
              } else {
                descend = descend - d
              }
            }
          None => ()
        }
        prev = Some(e)
      }
      None => ()
    }
  }
  // 收尾:最后一段没凑够阈值的残余,只有超过阈值才计入
  if filter {
    if pending >= threshold {
      ascend = ascend + pending
    } else if pending <= 0.0 - threshold {
      descend = descend - pending
    }
  }
  (ascend, descend)
}

///|
/// 一串点的包围盒。点集为空时返回 `None`。
///
/// 直接复用各点已校验过的 `Latitude` / `Longitude`,不重新做范围校验——
/// 输入既然是合法的 `Waypoint`,其极值必然仍在合法区间内。
pub fn bounds_of(points : Array[Waypoint]) -> Bounds? {
  let mut min_lat : Latitude? = None
  let mut max_lat : Latitude? = None
  let mut min_lon : Longitude? = None
  let mut max_lon : Longitude? = None
  for p in points {
    match (min_lat, max_lat, min_lon, max_lon) {
      (None, None, None, None) => {
        min_lat = Some(p.lat)
        max_lat = Some(p.lat)
        min_lon = Some(p.lon)
        max_lon = Some(p.lon)
      }
      (Some(a), Some(b), Some(c), Some(d)) => {
        if p.lat.to_double() < a.to_double() {
          min_lat = Some(p.lat)
        }
        if p.lat.to_double() > b.to_double() {
          max_lat = Some(p.lat)
        }
        if p.lon.to_double() < c.to_double() {
          min_lon = Some(p.lon)
        }
        if p.lon.to_double() > d.to_double() {
          max_lon = Some(p.lon)
        }
      }
      // 四个变量总是同时置位,这里只是让模式穷尽
      _ => ()
    }
  }
  match (min_lat, max_lat, min_lon, max_lon) {
    (Some(a), Some(b), Some(c), Some(d)) =>
      Some({ min_lat: a, min_lon: c, max_lat: b, max_lon: d, })
    _ => None
  }
}

///|
/// 一条轨迹的统计结果。
pub struct TrackStats {
  /// 轨迹点总数(所有 segment 合计)。
  point_count : Int
  /// 总距离(米)。跨 `trkseg` 不相连。
  distance_m : Double
  /// 累计爬升(米),已按默认阈值过滤高程噪声。
  ascend_m : Double
  /// 累计下降(米)。
  descend_m : Double
  /// 最低高程(米)。所有点都没有 `ele` 时为 `None`。
  min_ele : Double?
  /// 最高高程(米)。
  max_ele : Double?
  /// 包围盒。轨迹没有点时返回 `None`。
  bounds : Bounds?
  /// 最早的时间。不是"第一个点"——时间可能不单调,这里取真正的最小值。
  start_time : String?
  /// 最晚的时间。
  end_time : String?
  /// 起止时间差(秒)。时间缺失或无法解析时为 `None`。
  duration_s : Double?
} derive(Eq, Debug)

///|
/// 取两个 `xsd:dateTime` 中较早的一个。无法比较时保守返回 `a`。
fn earlier_of(a : String, b : String) -> String {
  match datetime_compare(a, b) {
    Some(c) => if c <= 0 { a } else { b }
    None => a
  }
}

///|
/// 取两个 `xsd:dateTime` 中较晚的一个。
fn later_of(a : String, b : String) -> String {
  match datetime_compare(a, b) {
    Some(c) => if c >= 0 { a } else { b }
    None => a
  }
}

///|
/// 两个时间之间的秒数。任一为 `None` 或无法解析时区时返回 `None`。
fn duration_between(a : String?, b : String?) -> Double? {
  match (a, b) {
    (Some(x), Some(y)) =>
      match (datetime_to_epoch(x), datetime_to_epoch(y)) {
        (Some(px), Some(py)) => Some(py - px)
        _ => None
      }
    _ => None
  }
}

///|
/// 统计一条轨迹。
///
/// 距离与爬升**逐 `trkseg` 计算再累加**,不跨段相连(见模块顶部说明)。
pub fn track_stats(track : Track) -> TrackStats {
  let mut total_distance = 0.0
  let mut ascend = 0.0
  let mut descend = 0.0
  let mut count = 0
  let mut min_ele : Double? = None
  let mut max_ele : Double? = None
  let mut earliest : String? = None
  let mut latest : String? = None
  let all_points : Array[Waypoint] = []
  for seg in track.segments {
    let pts = seg.points
    count = count + pts.length()
    total_distance = total_distance + path_distance_m(pts)
    let (a, d) = climb_m(pts, default_climb_threshold_m)
    ascend = ascend + a
    descend = descend + d
    for p in pts {
      all_points.push(p)
      match p.ele {
        Some(e) => {
          min_ele = match min_ele {
            None => Some(e)
            Some(m) => Some(if e < m { e } else { m })
          }
          max_ele = match max_ele {
            None => Some(e)
            Some(m) => Some(if e > m { e } else { m })
          }
        }
        None => ()
      }
      match p.time {
        Some(t) => {
          earliest = match earliest {
            None => Some(t)
            Some(e) => Some(earlier_of(e, t))
          }
          latest = match latest {
            None => Some(t)
            Some(l) => Some(later_of(l, t))
          }
        }
        None => ()
      }
    }
  }
  {
    point_count: count,
    distance_m: total_distance,
    ascend_m: ascend,
    descend_m: descend,
    min_ele,
    max_ele,
    bounds: bounds_of(all_points),
    start_time: earliest,
    end_time: latest,
    duration_s: duration_between(earliest, latest),
  }
}

///|
/// 一条轨迹的总距离(米)。等价于 `track_stats(t).distance_m`,但少建一个对象。
pub fn track_distance_m(track : Track) -> Double {
  let mut total = 0.0
  for seg in track.segments {
    total = total + path_distance_m(seg.points)
  }
  total
}