///|
/// Named render artifacts allow integrations to consume one deterministic bundle.
pub struct RenderArtifact {
  name : String
  format : String
  content : String
} derive(Debug, Eq)

///|
pub fn RenderArtifact::new(
  name : String,
  format : String,
  content : String,
) -> RenderArtifact {
  { name, format, content }
}

///|
pub fn RenderArtifact::bytes(self : RenderArtifact) -> Int {
  self.content.length()
}

///|
pub fn RenderArtifact::file_name(self : RenderArtifact) -> String {
  self.name + "." + self.format
}

///|
pub fn RenderArtifact::is_empty(self : RenderArtifact) -> Bool {
  self.content == ""
}

///|
pub fn RenderArtifact::header(self : RenderArtifact) -> String {
  ""
}

///|
pub struct RenderBundle {
  name : String
  artifacts : Array[RenderArtifact]
} derive(Debug, Eq)

///|
pub fn RenderBundle::empty(name : String) -> RenderBundle {
  { name, artifacts: [] }
}

///|
pub fn RenderBundle::add(
  self : RenderBundle,
  artifact : RenderArtifact,
) -> RenderBundle {
  let artifacts = self.artifacts
  artifacts.push(artifact)
  { ..self, artifacts, }
}

///|
pub fn RenderBundle::for_brand_book(book : BrandBook) -> RenderBundle {
  let bundle = RenderBundle::empty(normalize_identifier(book.name))
  bundle
  .add(RenderArtifact::new("brandbook", "md", book.to_markdown()))
  .add(RenderArtifact::new("brandbook", "html", book.to_html()))
  .add(RenderArtifact::new("theme", "json", book.to_theme_json()))
  .add(RenderArtifact::new("theme", "yaml", book.to_theme_yaml()))
  .add(RenderArtifact::new("theme", "css", book.to_css()))
  .add(
    RenderArtifact::new("tailwind", "js", book.theme_document().to_tailwind()),
  )
}

///|
pub fn RenderBundle::len(self : RenderBundle) -> Int {
  self.artifacts.length()
}

///|
pub fn RenderBundle::is_empty(self : RenderBundle) -> Bool {
  self.artifacts.length() == 0
}

///|
pub fn RenderBundle::total_bytes(self : RenderBundle) -> Int {
  self.artifacts.fold(init=0, fn(total, artifact) { total + artifact.bytes() })
}

///|
pub fn RenderBundle::formats(self : RenderBundle) -> Array[String] {
  let result : Array[String] = []
  for artifact in self.artifacts {
    if !result.contains(artifact.format) {
      result.push(artifact.format)
    }
  }
  result
}

///|
pub fn RenderBundle::artifact(
  self : RenderBundle,
  name : String,
  format : String,
) -> RenderArtifact? {
  for artifact in self.artifacts {
    if artifact.name == name && artifact.format == format {
      return Some(artifact)
    }
  }
  None
}

///|
pub fn RenderBundle::artifact_names(self : RenderBundle) -> Array[String] {
  self.artifacts.map(fn(artifact) { artifact.file_name() })
}

///|
pub fn RenderBundle::manifest_markdown(self : RenderBundle) -> String {
  let lines : Array[String] = [
    "# Render bundle",
    "",
    "Name: `" + self.name + "`",
    "",
    "| File | Format | Bytes |",
    "| --- | --- | ---: |",
  ]
  for artifact in self.artifacts {
    lines.push(
      "| `" +
      artifact.file_name() +
      "` | " +
      artifact.format +
      " | " +
      artifact.bytes().to_string() +
      " |",
    )
  }
  lines.push("")
  lines.push("Total: " + self.total_bytes().to_string() + " bytes")
  lines.join("\n")
}

///|
pub fn RenderBundle::manifest_json(self : RenderBundle) -> String {
  "{\"name\":\"" +
  json_escape(self.name) +
  "\",\"artifacts\":[" +
  self.artifacts
  .map(fn(artifact) {
    "{\"file\":\"" +
    json_escape(artifact.file_name()) +
    "\",\"format\":\"" +
    json_escape(artifact.format) +
    "\",\"bytes\":" +
    artifact.bytes().to_string() +
    "}"
  })
  .join(",") +
  "]}"
}

///|
pub fn RenderBundle::non_empty(self : RenderBundle) -> Array[RenderArtifact] {
  self.artifacts.filter(fn(artifact) { !artifact.is_empty() })
}

///|
pub fn BrandBook::render_bundle(self : BrandBook) -> RenderBundle {
  RenderBundle::for_brand_book(self)
}