///|
pub(all) struct TimelineEvent {
  at : Int
  label : String
  url : String
  entry_index : Int
} derive(Eq, Debug)

///|
pub fn build_timeline(archive : HarArchive) -> Array[TimelineEvent] {
  let rows = build_waterfall(archive)
  let events : Array[TimelineEvent] = []
  for i = 0; i < rows.length(); i = i + 1 {
    let row = rows[i]
    events.push(TimelineEvent::{
      at: row.start_offset,
      label: "request:start",
      url: row.url,
      entry_index: row.index,
    })
    events.push(TimelineEvent::{
      at: row.start_offset + row.duration,
      label: "request:end",
      url: row.url,
      entry_index: row.index,
    })
    if row.wait > 0 {
      events.push(TimelineEvent::{
        at: row.start_offset +
        row.blocked +
        row.dns +
        row.connect +
        row.ssl +
        row.send,
        label: "request:wait",
        url: row.url,
        entry_index: row.index,
      })
    }
  }
  sort_timeline(events)
  events
}

///|
fn sort_timeline(events : Array[TimelineEvent]) -> Unit {
  for i = 0; i < events.length(); i = i + 1 {
    for j = i + 1; j < events.length(); j = j + 1 {
      if events[j].at < events[i].at {
        let tmp = events[i]
        events[i] = events[j]
        events[j] = tmp
      }
    }
  }
}

///|
pub fn events_at_or_after(
  events : Array[TimelineEvent],
  at : Int,
) -> Array[TimelineEvent] {
  let out : Array[TimelineEvent] = []
  for i = 0; i < events.length(); i = i + 1 {
    if events[i].at >= at {
      out.push(events[i])
    }
  }
  out
}

///|
pub fn events_for_entry(
  events : Array[TimelineEvent],
  entry_index : Int,
) -> Array[TimelineEvent] {
  let out : Array[TimelineEvent] = []
  for i = 0; i < events.length(); i = i + 1 {
    if events[i].entry_index == entry_index {
      out.push(events[i])
    }
  }
  out
}

///|
pub fn events_with_label(
  events : Array[TimelineEvent],
  label : String,
) -> Array[TimelineEvent] {
  let out : Array[TimelineEvent] = []
  for i = 0; i < events.length(); i = i + 1 {
    if events[i].label == label {
      out.push(events[i])
    }
  }
  out
}

///|
pub fn render_timeline(events : Array[TimelineEvent]) -> String {
  let mut out = ""
  for i = 0; i < events.length(); i = i + 1 {
    let e = events[i]
    out = out +
      e.at.to_string() +
      "ms " +
      e.label +
      " #" +
      e.entry_index.to_string() +
      " " +
      e.url +
      "\n"
  }
  out
}

///|
pub fn timeline_span(events : Array[TimelineEvent]) -> Int {
  if events.is_empty() {
    return 0
  }
  let mut min = events[0].at
  let mut max = events[0].at
  for i = 1; i < events.length(); i = i + 1 {
    if events[i].at < min {
      min = events[i].at
    }
    if events[i].at > max {
      max = events[i].at
    }
  }
  max - min
}

///|
pub fn timeline_event_count_for_label(
  events : Array[TimelineEvent],
  label : String,
) -> Int {
  events.count_if(e => e.label == label)
}