///|
fn quoted(value : String) -> String {
  "\"" +
  value
  .replace_all(old="\\", new="\\\\")
  .replace_all(old="\"", new="\\\"")
  .replace_all(old="\n", new="\\n") +
  "\""
}

///|
fn Kind::text(self : Kind, explicit_label : Bool) -> String {
  match self {
    Declaration(code, label, decoration, composite) =>
      "state " +
      (if explicit_label { quoted(label) + " as " + code } else { code }) +
      (if decoration.is_empty() { "" } else { " " + decoration }) +
      (if composite { " {" } else { "" })
    Close => "}"
    Separator(vertical) => if vertical { "||" } else { "--" }
    Description(code, text) => code + " : " + text
    Transition(from, arrow, target, label) =>
      from +
      " " +
      arrow +
      " " +
      target +
      (if label.is_empty() { "" } else { " : " + label })
    Note(placement, target, text, code) =>
      match code {
        Some(code) => "note " + quoted(text) + " as " + code
        None =>
          (if target == Some("@link") {
            "note on link"
          } else {
            "note " + placement + target.map(t => " of " + t).unwrap_or("")
          }) +
          "\n" +
          text +
          "\nend note"
      }
    Direction(direction) =>
      match direction {
        LR => "left to right direction"
        TB => "top to bottom direction"
        _ => "invalid direction"
      }
    Presentation(text) => text
  }
}

///|
/// Canonical source is derived from edited values and checked for representability.
pub fn print_body(
  document : Document,
  limits~ : @envelope.Limits = @envelope.Limits::new(),
) -> Result[String, ReadOnlyArray[@diagrams.Diagnostic]] {
  fn refused(
    text : String,
  ) -> Result[String, ReadOnlyArray[@diagrams.Diagnostic]] {
    Err([@diagrams.Diagnostic::error("state/invalid-ast", text)])
  }
  if !document.complete {
    return refused("Recovered state document is incomplete")
  }
  if !limits.valid() || document.items.length() > limits.nodes {
    return Err([
      @diagrams.Diagnostic::error(
        "state/limit", "Invalid limits or too many statements",
      ),
    ])
  }
  let out = StringBuilder()
  let mut size = 0
  let mut work = 0
  for item in document.items {
    let values : Array[String] = match item.kind {
      Declaration(a, b, c, _) => [a, b, c]
      Description(a, b) => [a, b]
      Transition(a, b, c, d) => [a, b, c, d]
      Note(a, b, c, d) => [a, b.unwrap_or(""), c, d.unwrap_or("")]
      Presentation(a) => [a]
      _ => []
    }
    for value in values {
      if value.length() > limits.output || value.length() > limits.work - work {
        return Err([
          @diagrams.Diagnostic::error(
            "state/limit", "Typed value exceeds printing limits",
          ),
        ])
      }
      work += value.length()
    }
    let text = item.kind.text(item.explicit_label)
    size += text.length() + 1
    work += text.length() + 1
    if size > limits.output || work > limits.work {
      return Err([
        @diagrams.Diagnostic::error(
          "state/limit", "Printing output or work limit exceeded",
        ),
      ])
    }
    out.write_string(text)
    out.write_char('\n')
  }
  let text = out.to_string()
  let check_limits = @envelope.Limits::new(
    source=limits.output,
    output=limits.output,
    depth=limits.depth,
    nodes=limits.nodes,
    regions=limits.regions,
    edges=limits.edges,
    work=limits.work,
    diagnostics=limits.diagnostics,
  )
  let check = read(
    @envelope.scan_body(State, text, limits=check_limits),
    limits=check_limits,
  )
  if check.diagnostics.iter().any(d => d.severity == Error) {
    return refused("Typed state values are not representable as native source")
  }
  guard check.document is Some(parsed) else {
    return refused("Invalid typed state values")
  }
  if parsed.items.iter().map(s => (s.kind, s.explicit_label)).to_array() !=
    document.items.iter().map(s => (s.kind, s.explicit_label)).to_array() {
    return refused("Typed state values do not survive canonical reading")
  }
  Ok(text)
}