///|
fn report_lines(source : String) -> Array[String] {
  let lines : Array[String] = []
  source
  .split("\n")
  .each(line_view => {
    let text = match line_view.strip_suffix("\r") {
      Some(value) => value.to_owned()
      None => line_view.to_owned()
    }
    if tokenize(text).length() > 0 {
      lines.push(text)
    }
  })
  lines
}

///|
fn has_warning(diagnostics : Array[Diagnostic]) -> Bool {
  diagnostics.any(fn(diagnostic) { diagnostic.severity == Warning })
}

///|
fn count_category(
  category : FlightCategory,
  report : BatchReport,
) -> BatchReport {
  match category {
    VFR => { ..report, vfr_count: report.vfr_count + 1 }
    MVFR => { ..report, mvfr_count: report.mvfr_count + 1 }
    IFR => { ..report, ifr_count: report.ifr_count + 1 }
    LIFR => { ..report, lifr_count: report.lifr_count + 1 }
    UnknownCategory =>
      { ..report, unknown_category_count: report.unknown_category_count + 1 }
  }
}

///|
fn has_low_visibility(
  report : MetarReport,
  rules : FlightCategoryRules,
) -> Bool {
  match report.visibility {
    Some(value) =>
      match visibility_to_meters(value) {
        Some(meters) => meters < rules.mvfr_visibility_meters
        None => false
      }
    None => false
  }
}

///|
fn has_low_ceiling(report : MetarReport, rules : FlightCategoryRules) -> Bool {
  match ceiling_feet(report) {
    Some(feet) => feet < rules.mvfr_ceiling_feet
    None => false
  }
}

///|
fn empty_batch_report(total_reports : Int) -> BatchReport {
  {
    total_reports,
    parsed_reports: 0,
    error_reports: 0,
    warning_reports: 0,
    diagnostics_count: 0,
    vfr_count: 0,
    mvfr_count: 0,
    ifr_count: 0,
    lifr_count: 0,
    unknown_category_count: 0,
    low_visibility_events: 0,
    low_ceiling_events: 0,
  }
}

///|
/// Parse newline-delimited METAR/SPECI text and return aggregate statistics.
pub fn parse_metar_batch(
  source : String,
  rules? : FlightCategoryRules = standard_rules(),
) -> BatchReport {
  let lines = report_lines(source)
  let mut batch = empty_batch_report(lines.length())
  for line in lines {
    let result = parse_metar(line)
    batch = {
      ..batch,
      diagnostics_count: batch.diagnostics_count + result.diagnostics.length(),
    }
    if result.has_errors() {
      batch = { ..batch, error_reports: batch.error_reports + 1 }
    }
    if has_warning(result.diagnostics) {
      batch = { ..batch, warning_reports: batch.warning_reports + 1 }
    }
    match result.value {
      Some(report) => {
        batch = { ..batch, parsed_reports: batch.parsed_reports + 1 }
        batch = count_category(classify(report, rules~), batch)
        if has_low_visibility(report, rules) {
          batch = {
            ..batch,
            low_visibility_events: batch.low_visibility_events + 1,
          }
        }
        if has_low_ceiling(report, rules) {
          batch = { ..batch, low_ceiling_events: batch.low_ceiling_events + 1 }
        }
      }
      None => ()
    }
  }
  batch
}

///|
/// Summarize newline-delimited METAR/SPECI text directly as Markdown.
pub fn metar_batch_to_markdown(
  source : String,
  rules? : FlightCategoryRules = standard_rules(),
) -> String {
  batch_to_markdown(parse_metar_batch(source, rules~))
}

///|
/// Summarize newline-delimited METAR/SPECI text directly as CSV.
pub fn metar_batch_to_csv(
  source : String,
  rules? : FlightCategoryRules = standard_rules(),
) -> String {
  batch_to_csv(parse_metar_batch(source, rules~))
}