///|
/// A scored activity window.
pub struct ScoredWindow {
  window : TideWindow
  score : Double
  reason : String
} derive(Debug)

///|
/// A ranked set of windows for one activity.
pub struct ActivityPlan {
  activity : String
  windows : Array[ScoredWindow]
  note : String
} derive(Debug)

///|
type TideExtremeList = Array[TideExtreme]

///|
fn clamp(value : Double, low : Double, high : Double) -> Double {
  if value < low {
    low
  } else if value > high {
    high
  } else {
    value
  }
}

///|
/// Number of overlapping hours between two windows.
pub fn overlap_hours(a : TideWindow, b : TideWindow) -> Double {
  let start = if a.start_jd > b.start_jd { a.start_jd } else { b.start_jd }
  let end = if a.end_jd < b.end_jd { a.end_jd } else { b.end_jd }
  let hours = (end - start) * 24.0
  if hours > 0.0 {
    hours
  } else {
    0.0
  }
}

///|
fn daylight_overlap(
  window : TideWindow,
  sunrise : Double,
  sunset : Double,
  tz : Double,
) -> Double {
  let start_hour = local_hour_of(window.start_jd, tz)
  let end_hour = local_hour_of(window.end_jd, tz)
  let start = if start_hour < end_hour { start_hour } else { 0.0 }
  let end = if start_hour < end_hour { end_hour } else { 24.0 }
  let overlap_start = if start > sunrise { start } else { sunrise }
  let overlap_end = if end < sunset { end } else { sunset }
  if overlap_end > overlap_start {
    overlap_end - overlap_start
  } else {
    0.0
  }
}

///|
/// Score a window for a low-tide activity such as beachcombing.
pub fn score_low_tide_window(
  window : TideWindow,
  sunrise : Double,
  sunset : Double,
  tz : Double,
  height_span : Double,
) -> Double {
  let day = daylight_overlap(window, sunrise, sunset, tz)
  let duration = (window.end_jd - window.start_jd) * 24.0
  let day_score = if duration <= 0.0 { 0.0 } else { day / duration }
  let span_score = clamp(height_span / 4.0, 0.0, 1.0)
  50.0 * day_score + 30.0 * span_score + 20.0
}

///|
/// Score a window for fishing during the start of a flood tide.
pub fn score_fishing_window(
  window : TideWindow,
  sunrise : Double,
  sunset : Double,
  tz : Double,
  flood_rate : Double,
) -> Double {
  let day = daylight_overlap(window, sunrise, sunset, tz)
  let duration = (window.end_jd - window.start_jd) * 24.0
  let day_score = if duration <= 0.0 { 0.0 } else { day / duration }
  let flow_score = clamp(flood_rate / 0.6, 0.0, 1.0)
  40.0 * day_score + 40.0 * flow_score + 20.0
}

///|
/// Rank windows by descending score.
pub fn rank_windows(windows : Array[ScoredWindow]) -> Array[ScoredWindow] {
  let ranked = windows.copy()
  ranked.sort_by(fn(a, b) {
    if a.score > b.score {
      -1
    } else if a.score < b.score {
      1
    } else {
      0
    }
  })
  ranked
}

///|
/// Build a beachcombing plan from high/low water extremes.
pub fn beach_plan(
  extremes : TideExtremeList,
  sunrise : Double,
  sunset : Double,
  tz : Double,
  range : Double,
) -> ActivityPlan {
  let windows = Array(capacity=extremes.length())
  for extreme in extremes {
    if extreme.kind == ExtremeKind::Low {
      let window = window_around(extreme, 1.5, 1.5, "赶海")
      windows.push({
        window,
        score: score_low_tide_window(window, sunrise, sunset, tz, range),
        reason: "低潮前后滩涂露出,适合赶海",
      })
    }
  }
  let ranked = rank_windows(windows)
  let note = if ranked.is_empty() {
    "本时间窗没有可用的低潮段。"
  } else {
    "优先选择白天且潮差较大的低潮窗口。"
  }
  { activity: "赶海", windows: ranked, note, }
}

///|
/// Build a fishing plan from low-water to high-water transitions.
pub fn fishing_activity_plan(
  extremes : TideExtremeList,
  sunrise : Double,
  sunset : Double,
  tz : Double,
  station : Station,
) -> ActivityPlan {
  let windows = Array(capacity=extremes.length())
  for i = 0; i < extremes.length(); i = i + 1 {
    let extreme = extremes[i]
    if extreme.kind == ExtremeKind::Low &&
      i + 1 < extremes.length() &&
      extremes[i + 1].kind == ExtremeKind::High {
      let window = window_around(extreme, 0.0, 2.0, "钓鱼")
      let mid_jd = (window.start_jd + window.end_jd) / 2.0
      let flood_rate = tide_rate(station, mid_jd, 0.1)
      windows.push({
        window,
        score: score_fishing_window(window, sunrise, sunset, tz, flood_rate),
        reason: "涨潮初期水流恢复,鱼群活动更活跃",
      })
    }
  }
  let ranked = rank_windows(windows)
  let note = if ranked.is_empty() {
    "本时间窗没有识别到明显的涨潮段。"
  } else {
    "涨潮初期的前两小时通常比平潮更容易出鱼。"
  }
  { activity: "钓鱼", windows: ranked, note, }
}

///|
/// Build a bird-watching plan from daytime low-water windows.
pub fn birding_plan(
  extremes : TideExtremeList,
  sunrise : Double,
  sunset : Double,
  tz : Double,
) -> ActivityPlan {
  let windows = Array(capacity=extremes.length())
  for extreme in extremes {
    if extreme.kind == ExtremeKind::Low {
      let window = window_around(extreme, 2.0, 2.0, "观鸟")
      windows.push({
        window,
        score: score_low_tide_window(window, sunrise, sunset, tz, 3.0),
        reason: "低潮滩涂暴露,水鸟集中觅食",
      })
    }
  }
  let ranked = rank_windows(windows)
  let note = if ranked.is_empty() {
    "本时间窗没有可用的白天低潮段。"
  } else {
    "建议在低潮前抵达并保持安静观察。"
  }
  { activity: "观鸟", windows: ranked, note, }
}

///|
/// Build all activity plans for a prediction.
pub fn plans_for(prediction : Prediction) -> Array[ActivityPlan] {
  [
    beach_plan(
      prediction.extremes,
      prediction.sunrise_hour,
      prediction.sunset_hour,
      prediction.tz,
      prediction.tidal_range,
    ),
    fishing_activity_plan(
      prediction.extremes,
      prediction.sunrise_hour,
      prediction.sunset_hour,
      prediction.tz,
      prediction.station,
    ),
    birding_plan(
      prediction.extremes,
      prediction.sunrise_hour,
      prediction.sunset_hour,
      prediction.tz,
    ),
  ]
}