///|
/// An artifact produced by a trace analysis run.
pub enum TraceArtifactKind {
  Waveform
  Report
  CoverageData
  AssertionData
  ProtocolData
  Manifest
} derive(Debug, Eq)

///|
/// A named artifact with a stable content digest.
pub struct TraceArtifact {
  name : String
  kind : TraceArtifactKind
  format : String
  bytes : Int
  digest : Int64
  ready : Bool
} derive(Debug)

///|
/// List artifact kinds.
pub fn trace_artifact_catalog() -> Array[TraceArtifactKind] {
  [Waveform, Report, CoverageData, AssertionData, ProtocolData, Manifest]
}

///|
/// Create an artifact descriptor from textual content.
pub fn trace_artifact(
  name : String,
  kind : TraceArtifactKind,
  format : String,
  content : String,
) -> TraceArtifact {
  {
    name,
    kind,
    format,
    bytes: content.length(),
    digest: content.hash().to_int64(),
    ready: content.length() > 0,
  }
}

///|
/// Return whether an artifact is usable.
pub fn TraceArtifact::is_ready(self : TraceArtifact) -> Bool {
  self.ready && self.bytes > 0
}

///|
/// Return a stable artifact label.
pub fn TraceArtifact::label(self : TraceArtifact) -> String {
  self.name + "." + self.format
}

///|
/// Render one artifact descriptor as JSON.
pub fn TraceArtifact::to_json(self : TraceArtifact) -> String {
  "{\"name\":\"" +
  escape_text(self.name) +
  "\",\"kind\":\"" +
  trace_artifact_kind_name(self.kind) +
  "\",\"format\":\"" +
  self.format +
  "\",\"bytes\":" +
  self.bytes.to_string() +
  ",\"digest\":" +
  self.digest.to_string() +
  ",\"ready\":" +
  self.ready.to_string() +
  "}"
}

///|
/// Render a compact artifact status line.
pub fn TraceArtifact::to_text(self : TraceArtifact) -> String {
  self.label() +
  " bytes=" +
  self.bytes.to_string() +
  " digest=" +
  self.digest.to_string() +
  " ready=" +
  self.ready.to_string()
}

///|
/// Return artifacts that are ready for publication.
pub fn ready_trace_artifacts(
  items : Array[TraceArtifact],
) -> Array[TraceArtifact] {
  items.filter(fn(item) { item.is_ready() })
}

///|
/// Compare an artifact digest with independently generated content.
pub fn TraceArtifact::digest_matches(
  self : TraceArtifact,
  content : String,
) -> Bool {
  self.digest == content.hash().to_int64() && self.bytes == content.length()
}

///|
/// Return the artifact kinds represented by a collection.
pub fn artifact_kinds(items : Array[TraceArtifact]) -> Array[TraceArtifactKind] {
  let result : Array[TraceArtifactKind] = []
  for item in items {
    if !result.contains(item.kind) {
      result.push(item.kind)
    }
  }
  result
}

///|
/// Render a publication manifest from ready artifacts.
pub fn artifact_publication_manifest(items : Array[TraceArtifact]) -> String {
  let ready = ready_trace_artifacts(items)
  "{\"artifacts\":[" +
  ready.map(fn(item) { item.to_json() }).join(",") +
  "],\"count\":" +
  ready.length().to_string() +
  "}"
}

///|
/// Return whether every required artifact is ready.
pub fn artifacts_complete(
  items : Array[TraceArtifact],
  required : Array[TraceArtifactKind],
) -> Bool {
  required.all(fn(kind) {
    items.any(fn(item) { item.kind == kind && item.is_ready() })
  })
}

///|
/// Check whether an artifact uses a supported interchange format.
pub fn artifact_format_supported(format : String) -> Bool {
  format == "json" ||
  format == "csv" ||
  format == "vcd" ||
  format == "markdown" ||
  format == "html"
}

///|
/// Return labels for ready artifacts.
pub fn ready_artifact_labels(items : Array[TraceArtifact]) -> Array[String] {
  ready_trace_artifacts(items).map(fn(item) { item.label() })
}

///|
/// Return a canonical format label for an artifact kind.
pub fn artifact_default_format(kind : TraceArtifactKind) -> String {
  match kind {
    Waveform => "vcd"
    Report => "markdown"
    CoverageData | AssertionData | ProtocolData => "csv"
    Manifest => "json"
  }
}

///|
/// Validate the canonical format selected for an artifact kind.
pub fn artifact_uses_canonical_format(item : TraceArtifact) -> Bool {
  artifact_format_supported(item.format) &&
  item.format == artifact_default_format(item.kind)
}

///|
/// Return the first artifact with a requested kind.
pub fn first_artifact_of_kind(
  items : Array[TraceArtifact],
  kind : TraceArtifactKind,
) -> TraceArtifact? {
  for item in items {
    if item.kind == kind {
      return Some(item)
    }
  }
  None
}

///|
/// Export artifact metadata as CSV.
pub fn trace_artifacts_csv(items : Array[TraceArtifact]) -> String {
  let out = StringBuilder()
  out.write_string("name,kind,format,bytes,digest,ready\n")
  for item in items {
    out.write_string(
      escape_text(item.name) +
      "," +
      trace_artifact_kind_name(item.kind) +
      "," +
      item.format +
      "," +
      item.bytes.to_string() +
      "," +
      item.digest.to_string() +
      "," +
      item.ready.to_string() +
      "\n",
    )
  }
  out.to_string()
}

///|
/// Name an artifact kind.
pub fn trace_artifact_kind_name(value : TraceArtifactKind) -> String {
  match value {
    Waveform => "waveform"
    Report => "report"
    CoverageData => "coverage"
    AssertionData => "assertions"
    ProtocolData => "protocol"
    Manifest => "manifest"
  }
}