///|
pub(all) struct WeightedRainEvent {
  event : RainEvent
  weight : Double
} derive(Eq, Debug)

///|
pub(all) struct EventSizing {
  event_name : String
  depth_mm : Double
  weight : Double
  runoff_volume_m3 : Double
  recommended_area_m2 : Double
  capture_ratio : Double
  drawdown_hours : Double
  status : PlanStatus
} derive(Eq, Debug)

///|
pub(all) struct SeriesSummary {
  event_count : Int
  ready_count : Int
  review_count : Int
  controlling_event_name : String
  max_recommended_area_m2 : Double
  weighted_runoff_m3 : Double
  weighted_capture_ratio : Double
  results : Array[EventSizing]
} derive(Eq, Debug)

///|
pub fn weighted_event(
  name : String,
  depth_mm : Double,
  weight : Double,
) -> WeightedRainEvent {
  { event: rain_event(name, depth_mm), weight }
}

///|
pub fn evaluate_event_series(
  catchments : Array[Catchment],
  events : Array[WeightedRainEvent],
  spec : GardenSpec,
) -> Result[SeriesSummary, SpongeError] {
  if events.length() == 0 {
    return Err(InvalidWeight)
  }
  let results : Array[EventSizing] = []
  let mut total_weight = 0.0
  let mut weighted_runoff = 0.0
  let mut weighted_capture = 0.0
  let mut max_area = -1.0
  let mut controlling_event = ""
  let mut ready_count = 0
  let mut review_count = 0
  for event in events {
    if event.weight < 0.0 {
      return Err(InvalidWeight)
    }
    match size_rain_garden(catchments, event.event, spec) {
      Err(error) => return Err(error)
      Ok(plan) => {
        let sizing = {
          event_name: event.event.name,
          depth_mm: event.event.depth_mm,
          weight: event.weight,
          runoff_volume_m3: plan.runoff_volume_m3,
          recommended_area_m2: plan.recommended_area_m2,
          capture_ratio: plan.capture_ratio,
          drawdown_hours: plan.drawdown_hours,
          status: plan.status,
        }
        results.push(sizing)
        total_weight = total_weight + event.weight
        weighted_runoff = weighted_runoff + plan.runoff_volume_m3 * event.weight
        weighted_capture = weighted_capture + plan.capture_ratio * event.weight
        if plan.recommended_area_m2 > max_area {
          max_area = plan.recommended_area_m2
          controlling_event = event.event.name
        }
        if plan.status == Ready {
          ready_count = ready_count + 1
        } else {
          review_count = review_count + 1
        }
      }
    }
  }
  if total_weight <= 0.0 {
    return Err(InvalidWeight)
  }
  Ok({
    event_count: events.length(),
    ready_count,
    review_count,
    controlling_event_name: controlling_event,
    max_recommended_area_m2: max_area,
    weighted_runoff_m3: weighted_runoff / total_weight,
    weighted_capture_ratio: weighted_capture / total_weight,
    results,
  })
}

///|
pub fn SeriesSummary::to_json(self : SeriesSummary) -> String {
  let out = StringBuilder::new()
  out.write_string("{")
  out.write_string("\"event_count\":" + self.event_count.to_string() + ",")
  out.write_string("\"ready_count\":" + self.ready_count.to_string() + ",")
  out.write_string("\"review_count\":" + self.review_count.to_string() + ",")
  out.write_string(
    "\"controlling_event\":\"" +
    escape_json(self.controlling_event_name) +
    "\",",
  )
  out.write_string(
    "\"max_recommended_area_m2\":" +
    self.max_recommended_area_m2.to_string() +
    ",",
  )
  out.write_string(
    "\"weighted_runoff_m3\":" + self.weighted_runoff_m3.to_string() + ",",
  )
  out.write_string(
    "\"weighted_capture_ratio\":" +
    self.weighted_capture_ratio.to_string() +
    ",",
  )
  out.write_string("\"results\":[")
  for index, result in self.results {
    if index > 0 {
      out.write_string(",")
    }
    out.write_string(event_sizing_to_json(result))
  }
  out.write_string("]}")
  out.to_string()
}

///|
pub fn SeriesSummary::to_markdown(self : SeriesSummary) -> String {
  let out = StringBuilder::new()
  out.write_string("# SpongeKit Event Series\n\n")
  out.write_string("- Events: " + self.event_count.to_string() + "\n")
  out.write_string("- Controlling event: " + self.controlling_event_name + "\n")
  out.write_string(
    "- Max recommended area: " +
    self.max_recommended_area_m2.to_string() +
    " m2\n",
  )
  out.write_string(
    "- Weighted runoff: " + self.weighted_runoff_m3.to_string() + " m3\n",
  )
  out.write_string(
    "- Weighted capture: " + self.weighted_capture_ratio.to_string() + "\n\n",
  )
  out.write_string("| Event | Rain mm | Area m2 | Capture | Status |\n")
  out.write_string("| --- | ---: | ---: | ---: | --- |\n")
  for result in self.results {
    out.write_string(
      "| " +
      result.event_name +
      " | " +
      result.depth_mm.to_string() +
      " | " +
      result.recommended_area_m2.to_string() +
      " | " +
      result.capture_ratio.to_string() +
      " | " +
      result.status.label() +
      " |\n",
    )
  }
  out.to_string()
}

///|
fn event_sizing_to_json(result : EventSizing) -> String {
  let out = StringBuilder::new()
  out.write_string("{")
  out.write_string("\"event\":\"" + escape_json(result.event_name) + "\",")
  out.write_string("\"depth_mm\":" + result.depth_mm.to_string() + ",")
  out.write_string("\"weight\":" + result.weight.to_string() + ",")
  out.write_string(
    "\"runoff_volume_m3\":" + result.runoff_volume_m3.to_string() + ",",
  )
  out.write_string(
    "\"recommended_area_m2\":" + result.recommended_area_m2.to_string() + ",",
  )
  out.write_string(
    "\"capture_ratio\":" + result.capture_ratio.to_string() + ",",
  )
  out.write_string(
    "\"drawdown_hours\":" + result.drawdown_hours.to_string() + ",",
  )
  out.write_string("\"status\":\"" + result.status.label() + "\"}")
  out.to_string()
}