///|
/// The result of reading a Mermaid source: the lossless syntax tree, the
/// typed AST when the source has the `sequenceDiagram` header, and all
/// diagnostics.
pub struct ReadResult {
  tree : @cst.Node
  ast : @ast.Diagram?
  diagnostics : ReadOnlyArray[@diagrams.Diagnostic]
}

///|
/// Read a Mermaid sequence diagram. The text of `tree` is always equal to
/// `source`.
pub fn read(source : String) -> ReadResult {
  let parsed = @syntax.parse(source)
  {
    tree: parsed.tree,
    ast: @ast.lower(parsed.tree),
    diagnostics: parsed.diagnostics,
  }
}

///|
/// Canonical Mermaid text of `diagram`.
pub fn print(diagram : @ast.Diagram) -> String {
  @printer.print(diagram)
}

///|
/// The sequence model of `diagram`, with the conversion diagnostics.
pub fn to_sequence(diagram : @ast.Diagram) -> @sequence.Conversion {
  @sequence.to_sequence(diagram)
}

///|
/// Read `source` and render it with archify's classic theme as adaptive SVG.
/// The result has the diagnostics of reading, converting and rendering. A
/// source without a diagram gives no output.
pub fn render_svg(source : String) -> @render.Rendered {
  let r = read_diagram(source)
  if r.ast == None &&
    r.diagnostics.iter().any(d => d.code == "mermaid/unsupported-diagram") {
    let legacy = read(source)
    if !legacy.diagnostics.is_empty() &&
      legacy.diagnostics.iter().all(d => d.code != "mermaid/unknown-statement") {
      return @render.Rendered::failed("image/svg+xml", legacy.diagnostics[:])
    }
  }
  let diagnostics = r.diagnostics.iter().to_array()
  if diagnostics.iter().any(d => d.severity == @diagrams.Severity::Error) {
    return @render.Rendered::failed("image/svg+xml", diagnostics[:])
  }
  guard r.ast is Some(ast) else {
    return @render.Rendered::failed("image/svg+xml", diagnostics[:])
  }
  let model = match ast {
    Sequence(ast) => {
      let converted = to_sequence(ast)
      for d in converted.diagnostics {
        diagnostics.push(d)
      }
      Some(@render.Diagram::Sequence(converted.sequence))
    }
    Swimlane(ast) => {
      let converted = to_swimlane(ast)
      for d in converted.diagnostics {
        diagnostics.push(d)
      }
      converted.swimlane.map(model => @render.Diagram::Swimlane(model))
    }
  }
  guard model is Some(model) else {
    return @render.Rendered::failed("image/svg+xml", diagnostics[:])
  }
  if diagnostics.iter().any(d => d.severity == @diagrams.Severity::Error) {
    return @render.Rendered::failed("image/svg+xml", diagnostics[:])
  }
  let rendered = @render.Renderer::render(@svg.SvgRenderer::new(), model)
  for d in rendered.diagnostics {
    diagnostics.push(d)
  }
  match rendered.output {
    Some(output) =>
      @render.Rendered::ok(output, "image/svg+xml", diagnostics=diagnostics[:])
    None => @render.Rendered::failed("image/svg+xml", diagnostics[:])
  }
}

///|
/// Every diagnostic code that reading can give.
pub let diagnostic_codes : ReadOnlyArray[String] = @syntax.diagnostic_codes

///|
/// Every diagnostic code that conversion can give. Reading uses
/// `diagnostic_codes`; link resolution uses `sequence/unresolved-link`.
pub let conversion_diagnostic_codes : ReadOnlyArray[String] = @sequence.diagnostic_codes