///|
/// The result of a tide prediction for a station over a time window.
pub struct Prediction {
  station : Station
  start_jd : Double
  duration_hours : Double
  tz : Double
  extremes : Array[TideExtreme]
  max_height : Double
  min_height : Double
  tidal_range : Double
  spring_neap : String
  sunrise_hour : Double
  sunset_hour : Double
  activities : Array[Activity]
} derive(Debug)

///|
/// Rough mean tidal range from the dominant constituents.
pub fn mean_tidal_range(station : Station) -> Double {
  let mut total = 0.0
  for hc in station.components {
    if hc.constituent.name == "M2" ||
      hc.constituent.name == "S2" ||
      hc.constituent.name == "K1" ||
      hc.constituent.name == "O1" {
      total = total + hc.amplitude
    }
  }
  total * 2.0
}

///|
fn classify_spring_neap(tidal_range : Double, mean_range : Double) -> String {
  if mean_range <= 0.0 {
    return "中潮"
  }
  let ratio = tidal_range / mean_range
  if ratio > 1.2 {
    "大潮"
  } else if ratio < 0.8 {
    "小潮"
  } else {
    "中潮"
  }
}

///|
/// Predict tides for a station starting at a local clock moment.
pub fn predict(
  station : Station,
  start : Moment,
  tz : Double,
  duration_hours : Double,
) -> Prediction {
  let start_jd = moment_to_jd(start, tz)
  let step_hours = 1.0 / 12.0 // five minutes
  let samples = @math.floor(duration_hours / step_hours).to_int() + 1
  let mut min_height = tide_height(station, start_jd)
  let mut max_height = min_height
  for i = 0; i < samples; i = i + 1 {
    let h = tide_height(station, start_jd + i.to_double() * step_hours / 24.0)
    if h < min_height {
      min_height = h
    }
    if h > max_height {
      max_height = h
    }
  }

  let extremes = find_extremes(station, start_jd, duration_hours, step_hours)
  let tidal_range = max_height - min_height
  let mean_range = mean_tidal_range(station)
  let spring_neap = classify_spring_neap(tidal_range, mean_range)

  let doy = day_of_year(start.year, start.month, start.day)
  let (sunrise_hour, sunset_hour) = match sunrise_sunset(station.lat, doy) {
    Some((rise, set)) => (rise, set)
    None => (6.0, 18.0)
  }
  let activities = recommend(extremes, sunrise_hour, sunset_hour, tz)

  {
    station,
    start_jd,
    duration_hours,
    tz,
    extremes,
    max_height,
    min_height,
    tidal_range,
    spring_neap,
    sunrise_hour,
    sunset_hour,
    activities,
  }
}

///|
/// Convenience wrapper: parse a local date string and predict.
pub fn predict_on(
  station : Station,
  date : String,
  tz : Double,
  duration_hours : Double,
) -> Prediction? {
  match parse_local_datetime(date) {
    Some(start) => Some(predict(station, start, tz, duration_hours))
    None => None
  }
}