///|
/// An artifact entry in a reproducible processing manifest.
pub struct AnalysisArtifact {
  name : String
  kind : String
  sample_count : Int
  checksum : String
  description : String
}

///|
/// A reproducible analysis manifest.
pub struct AnalysisManifest {
  project : String
  version : String
  created_at : Double
  toolchain : String
  artifacts : Array[AnalysisArtifact]
  parameters : Array[(String, String)]
}

///|
/// Create an empty analysis manifest.
pub fn AnalysisManifest::new(
  project : String,
  version : String,
  created_at : Double,
  toolchain : String,
) -> AnalysisManifest {
  { project, version, created_at, toolchain, artifacts: [], parameters: [] }
}

///|
/// Add a waveform artifact to a manifest.
pub fn AnalysisManifest::add_waveform(
  self : AnalysisManifest,
  name : String,
  trace : Trace,
  description : String,
) -> AnalysisManifest {
  let artifacts = self.artifacts.copy()
  artifacts.push({
    name,
    kind: "waveform",
    sample_count: trace.npts(),
    checksum: waveform_checksum(trace.data),
    description,
  })
  {
    project: self.project,
    version: self.version,
    created_at: self.created_at,
    toolchain: self.toolchain,
    artifacts,
    parameters: self.parameters.copy(),
  }
}

///|
/// Add a textual parameter to a manifest.
pub fn AnalysisManifest::add_parameter(
  self : AnalysisManifest,
  name : String,
  value : String,
) -> AnalysisManifest {
  let parameters = self.parameters.copy()
  parameters.push((name, value))
  {
    project: self.project,
    version: self.version,
    created_at: self.created_at,
    toolchain: self.toolchain,
    artifacts: self.artifacts.copy(),
    parameters,
  }
}

///|
/// Validate that all manifest artifact names are unique and non-empty.
pub fn validate_analysis_manifest(
  manifest : AnalysisManifest,
) -> Result[Unit, String] {
  for index = 0; index < manifest.artifacts.length(); index = index + 1 {
    if manifest.artifacts[index].name == "" {
      return Err("manifest artifact name is empty")
    }
    for other = 0; other < index; other = other + 1 {
      if manifest.artifacts[index].name == manifest.artifacts[other].name {
        return Err("manifest artifact names must be unique")
      }
    }
  }
  Ok(())
}

///|
/// Serialize a manifest in a stable human-readable form.
pub fn analysis_manifest_to_text(manifest : AnalysisManifest) -> String {
  let mut output = "project=" +
    manifest.project +
    "\nversion=" +
    manifest.version +
    "\ncreated_at=" +
    manifest.created_at.to_string() +
    "\ntoolchain=" +
    manifest.toolchain +
    "\n"
  output += "[parameters]\n"
  for parameter in manifest.parameters {
    output += parameter.0 + "=" + parameter.1 + "\n"
  }
  output += "[artifacts]\n"
  for artifact in manifest.artifacts {
    output += artifact.name +
      "|" +
      artifact.kind +
      "|" +
      artifact.sample_count.to_string() +
      "|" +
      artifact.checksum +
      "|" +
      artifact.description +
      "\n"
  }
  output
}

///|
/// Format a manifest headline.
pub fn analysis_manifest_summary(manifest : AnalysisManifest) -> String {
  "project=" +
  manifest.project +
  " version=" +
  manifest.version +
  " artifacts=" +
  manifest.artifacts.length().to_string() +
  " parameters=" +
  manifest.parameters.length().to_string()
}