///|
pub struct HarWaterfallItem {
  index_value : Int
  page_ref_value : String?
  url_value : String
  status_value : Double
  duration_value : Double
  dominant_phase_value : String
  transferred_bytes_value : Double?
} derive(Eq, Debug)

///|
pub fn HarWaterfallItem::index(self : HarWaterfallItem) -> Int {
  self.index_value
}

///|
pub fn HarWaterfallItem::page_ref(self : HarWaterfallItem) -> String? {
  self.page_ref_value
}

///|
pub fn HarWaterfallItem::url(self : HarWaterfallItem) -> String {
  self.url_value
}

///|
pub fn HarWaterfallItem::status(self : HarWaterfallItem) -> Double {
  self.status_value
}

///|
pub fn HarWaterfallItem::duration(self : HarWaterfallItem) -> Double {
  self.duration_value
}

///|
pub fn HarWaterfallItem::dominant_phase(self : HarWaterfallItem) -> String {
  self.dominant_phase_value
}

///|
pub fn HarWaterfallItem::transferred_bytes(self : HarWaterfallItem) -> Double? {
  self.transferred_bytes_value
}

///|
pub fn build_waterfall(document : HarDocument) -> Array[HarWaterfallItem] {
  let items : Array[HarWaterfallItem] = []
  for index, entry in document.entries_value {
    items.push({
      index_value: index,
      page_ref_value: entry.page_ref_value,
      url_value: entry.request_value.url_value,
      status_value: entry.response_value.status_value,
      duration_value: entry.time_value,
      dominant_phase_value: entry.timings_value.dominant_phase(),
      transferred_bytes_value: entry.response_value.known_body_size(),
    })
  }
  items
}

///|
fn bucket_json(bucket : HarBucket) -> Json {
  Json::object({
    "label": Json::string(bucket.label_value),
    "count": Json::number(bucket.count_value.to_double()),
    "bytes": Json::number(bucket.bytes_value),
    "duration": Json::number(bucket.duration_value),
  })
}

///|
fn bucket_array_json(buckets : Array[HarBucket]) -> Json {
  let values : Array[Json] = []
  for bucket in buckets {
    values.push(bucket_json(bucket))
  }
  Json::array(values)
}

///|
pub fn render_statistics_json(statistics : HarStatistics) -> String {
  Json::object({
    "type": Json::string("har_statistics"),
    "entries": Json::number(statistics.entry_count_value.to_double()),
    "pages": Json::number(statistics.page_count_value.to_double()),
    "successful": Json::number(statistics.successful_count_value.to_double()),
    "redirects": Json::number(statistics.redirect_count_value.to_double()),
    "errors": Json::number(statistics.error_count_value.to_double()),
    "secure": Json::number(statistics.secure_count_value.to_double()),
    "cached": Json::number(statistics.cached_count_value.to_double()),
    "unknown_body_sizes": Json::number(
      statistics.unknown_body_size_count_value.to_double(),
    ),
    "request_bytes": Json::number(statistics.request_bytes_value),
    "response_bytes": Json::number(statistics.response_bytes_value),
    "decoded_bytes": Json::number(statistics.decoded_bytes_value),
    "duration": Json::number(statistics.duration_value),
    "hosts": bucket_array_json(statistics.hosts_value),
    "status_classes": bucket_array_json(statistics.status_classes_value),
    "content_types": bucket_array_json(statistics.content_types_value),
  }).stringify()
}

///|
pub fn render_statistics_text(statistics : HarStatistics) -> String {
  let mut output = "HAR statistics" +
    "\nentries: " +
    statistics.entry_count_value.to_string() +
    "\npages: " +
    statistics.page_count_value.to_string() +
    "\nsuccessful: " +
    statistics.successful_count_value.to_string() +
    "\nredirects: " +
    statistics.redirect_count_value.to_string() +
    "\nerrors: " +
    statistics.error_count_value.to_string() +
    "\nrequest bytes: " +
    statistics.request_bytes_value.to_string() +
    "\nresponse bytes: " +
    statistics.response_bytes_value.to_string() +
    "\ndecoded bytes: " +
    statistics.decoded_bytes_value.to_string() +
    "\nduration ms: " +
    statistics.duration_value.to_string() +
    "\nhosts:"
  for bucket in statistics.hosts_value {
    output = output +
      "\n  " +
      bucket.label_value +
      ": count=" +
      bucket.count_value.to_string() +
      ", bytes=" +
      bucket.bytes_value.to_string() +
      ", duration=" +
      bucket.duration_value.to_string()
  }
  output
}

///|
fn issue_json(issue : ValidationIssue) -> Json {
  Json::object({
    "severity": Json::string(issue.severity_value),
    "code": Json::string(issue.code_value),
    "path": Json::string(issue.path_value),
    "message": Json::string(issue.message_value),
  })
}

///|
pub fn render_validation_json(result : ValidationResult) -> String {
  let issues : Array[Json] = []
  for issue in result.issues_value {
    issues.push(issue_json(issue))
  }
  Json::object({
    "type": Json::string("har_validation"),
    "valid": Json::boolean(result.is_valid()),
    "error_count": Json::number(result.error_count().to_double()),
    "warning_count": Json::number(result.warning_count().to_double()),
    "issues": Json::array(issues),
  }).stringify()
}

///|
pub fn render_validation_text(result : ValidationResult) -> String {
  let mut output = "HAR validation" +
    "\nvalid: " +
    result.is_valid().to_string() +
    "\nerrors: " +
    result.error_count().to_string() +
    "\nwarnings: " +
    result.warning_count().to_string()
  for issue in result.issues_value {
    output = output + "\n" + issue.format()
  }
  output
}

///|
fn metric_json(metric : HarMetricDelta) -> Json {
  Json::object({
    "name": Json::string(metric.name_value),
    "before": Json::number(metric.before_value),
    "after": Json::number(metric.after_value),
    "delta": Json::number(metric.delta_value),
  })
}

///|
fn host_delta_json(item : HarBucketDelta) -> Json {
  Json::object({
    "label": Json::string(item.label_value),
    "before_count": Json::number(item.before_count_value.to_double()),
    "after_count": Json::number(item.after_count_value.to_double()),
    "count_delta": Json::number(item.count_delta_value.to_double()),
    "before_bytes": Json::number(item.before_bytes_value),
    "after_bytes": Json::number(item.after_bytes_value),
    "bytes_delta": Json::number(item.bytes_delta_value),
    "duration_delta": Json::number(item.duration_delta_value),
  })
}

///|
pub fn render_comparison_json(comparison : HarComparison) -> String {
  let metrics : Array[Json] = []
  for metric in comparison.metrics_value {
    metrics.push(metric_json(metric))
  }
  let hosts : Array[Json] = []
  for host in comparison.hosts_value {
    hosts.push(host_delta_json(host))
  }
  Json::object({
    "type": Json::string("har_comparison"),
    "metrics": Json::array(metrics),
    "hosts": Json::array(hosts),
  }).stringify()
}

///|
pub fn render_comparison_text(comparison : HarComparison) -> String {
  let mut output = "HAR comparison\nmetrics:"
  for metric in comparison.metrics_value {
    output = output +
      "\n  " +
      metric.name_value +
      ": before=" +
      metric.before_value.to_string() +
      ", after=" +
      metric.after_value.to_string() +
      ", delta=" +
      metric.delta_value.to_string()
  }
  output = output + "\nhosts:"
  for host in comparison.hosts_value {
    output = output +
      "\n  " +
      host.label_value +
      ": count delta=" +
      host.count_delta_value.to_string() +
      ", bytes delta=" +
      host.bytes_delta_value.to_string()
  }
  output
}

///|
pub fn render_waterfall_json(items : Array[HarWaterfallItem]) -> String {
  let values : Array[Json] = []
  for item in items {
    let object : Map[String, Json] = {
      "index": Json::number(item.index_value.to_double()),
      "url": Json::string(item.url_value),
      "status": Json::number(item.status_value),
      "duration": Json::number(item.duration_value),
      "dominant_phase": Json::string(item.dominant_phase_value),
    }
    set_optional_string(object, "page_ref", item.page_ref_value)
    set_optional_number(
      object,
      "transferred_bytes",
      item.transferred_bytes_value,
    )
    values.push(Json::object(object))
  }
  Json::object({
    "type": Json::string("har_waterfall"),
    "items": Json::array(values),
  }).stringify()
}