///|
/// A dependency-free, serialization-friendly trace row.
pub(all) struct TraceRow {
  sequence : Int
  replica : String
  physical : Int
  logical : Int
  kind : String
  detail : String
} derive(Eq, Debug)

///|
/// A serialization-friendly direct edge.
pub(all) struct EdgeRow {
  from_sequence : Int
  to_sequence : Int
} derive(Eq, Debug)

///|
/// Convert trace events into flat rows suitable for CSV/JSON adapters.
pub fn trace_rows(events : Array[TraceEvent]) -> Array[TraceRow] {
  let output : Array[TraceRow] = []
  for event in events {
    output.push({
      sequence: event.sequence,
      replica: event.replica,
      physical: event.stamp.physical,
      logical: event.stamp.logical,
      kind: trace_kind_name(event.kind),
      detail: trace_kind_detail(event.kind),
    })
  }
  output
}

///|
/// Flatten direct causal edges for host serializers.
pub fn edge_rows(analysis : TraceAnalysis) -> Array[EdgeRow] {
  let output : Array[EdgeRow] = []
  for edge in analysis.direct_edges() {
    output.push({
      from_sequence: edge.earlier.sequence,
      to_sequence: edge.later.sequence,
    })
  }
  output
}

///|
/// Stable name for a trace event kind.
pub fn trace_kind_name(kind : TraceKind) -> String {
  match kind {
    Local(_) => "local"
    Sent(..) => "send"
    Received(..) => "receive"
    AvailabilityChanged(online~) => if online { "online" } else { "offline" }
  }
}

///|
/// Human-readable payload carried by a trace kind.
pub fn trace_kind_detail(kind : TraceKind) -> String {
  match kind {
    Local(label~) => label
    Sent(target~, payload~) => target + ":" + payload
    Received(source~, payload~) => source + ":" + payload
    AvailabilityChanged(online~) => if online { "online" } else { "offline" }
  }
}

///| Graphviz DOT lines. Returning lines avoids forcing a particular newline or

///|
/// filesystem API on browser, native, and Wasm callers.
pub fn trace_dot_lines(analysis : TraceAnalysis) -> Array[String] {
  let output : Array[String] = ["digraph causality {"]
  output.push("  rankdir=LR;")
  for event in analysis.events() {
    output.push(
      "  e" +
      event.sequence.to_string() +
      " [label=\"" +
      event.sequence.to_string() +
      " " +
      event.replica +
      " " +
      trace_kind_name(event.kind) +
      "\"];",
    )
  }
  for edge in analysis.direct_edges() {
    output.push(
      "  e" +
      edge.earlier.sequence.to_string() +
      " -> e" +
      edge.later.sequence.to_string() +
      ";",
    )
  }
  output.push("}")
  output
}

///|
/// Mermaid flowchart lines for Markdown documentation and issue reports.
pub fn trace_mermaid_lines(analysis : TraceAnalysis) -> Array[String] {
  let output : Array[String] = ["flowchart LR"]
  for event in analysis.events() {
    output.push(
      "  e" +
      event.sequence.to_string() +
      "[\"" +
      event.sequence.to_string() +
      " " +
      event.replica +
      " " +
      trace_kind_name(event.kind) +
      "\"]",
    )
  }
  for edge in analysis.direct_edges() {
    output.push(
      "  e" +
      edge.earlier.sequence.to_string() +
      " --> e" +
      edge.later.sequence.to_string(),
    )
  }
  output
}

///| One line per concurrency conflict, useful in diagnostics without a graph

///|
/// rendering dependency.
pub fn concurrency_report_lines(analysis : TraceAnalysis) -> Array[String] {
  let output : Array[String] = []
  for pair in analysis.concurrent_pairs() {
    output.push(
      pair.first.sequence.to_string() +
      " (" +
      pair.first.replica +
      ") concurrent with " +
      pair.second.sequence.to_string() +
      " (" +
      pair.second.replica +
      ")",
    )
  }
  output
}

///|
/// One line per topological layer.
pub fn layer_report_lines(analysis : TraceAnalysis) -> Array[String] {
  let output : Array[String] = []
  for layer in analysis.layers() {
    let mut line = "layer " + layer.index.to_string() + ":"
    for event in layer.events {
      line = line + " " + event.sequence.to_string()
    }
    output.push(line)
  }
  output
}