///|
/// Best-effort whole-document formatter used by the `.mbtv` language server.
pub fn format_document(
  source : String,
  filename? : String = "Component.mbtv",
) -> String? {
  let doc = @compiler.parse_sfc(source, filename~) catch { _ => return None }
  let template_nodes = @compiler.parse_template(doc.template) catch {
    _ =>
      return Some(
        format_blocks(doc.blocks, normalize_raw_template_content(doc.template)),
      )
  }
  Some(
    format_blocks(
      doc.blocks,
      render_template_block(doc.template, template_nodes),
    ),
  )
}

///|
/// Convert a successful format into one full-document text edit.
pub fn formatting_edit(
  source : String,
  filename? : String = "Component.mbtv",
) -> ToolingTextEdit? {
  match format_document(source, filename~) {
    Some(formatted) =>
      if formatted == source {
        None
      } else {
        Some({ range: full_document_range(source), new_text: formatted })
      }
    None => None
  }
}

///|
/// Re-render all top-level blocks with stable spacing between them.
fn format_blocks(
  blocks : Array[@compiler.SfcBlock],
  rendered_template : String,
) -> String {
  let rendered : Array[String] = []
  for block in blocks {
    rendered.push(
      match block.kind {
        @compiler_common.Template =>
          render_named_block("template", block.attrs, rendered_template)
        @compiler_common.Style =>
          render_named_block(
            "style",
            block.attrs,
            normalize_raw_block_content(block.content),
          )
        @compiler_common.ScriptSetup | @compiler_common.ScriptExtern =>
          render_named_block(
            "script",
            block.attrs,
            normalize_raw_block_content(block.content),
          )
      },
    )
  }
  rendered.join("\n\n") + "\n"
}

///|
/// Render one top-level SFC block using its original attribute set.
fn render_named_block(
  tag : String,
  attrs : Array[@compiler.BlockAttr],
  content : String,
) -> String {
  let sb = StringBuilder::new()
  sb.write_char('<')
  sb.write_string(tag)
  for attr in attrs {
    sb.write_char(' ')
    sb.write_string(attr.name)
    match attr.value {
      Some(value) => {
        sb.write_string("=\"")
        sb.write_string(escape_block_attr_value(value))
        sb.write_char('"')
      }
      None => ()
    }
  }
  sb.write_string(">\n")
  if content.length() > 0 {
    sb.write_string(content)
    sb.write_char('\n')
  }
  sb.write_string("')
  sb.to_string()
}

///|
/// Normalize script/style block bodies without trying to rewrite MoonBit or CSS syntax.
fn normalize_raw_block_content(content : String) -> String {
  let lines = content.split("\n").map(fn(line) { line.to_string() }).collect()
  let normalized : Array[String] = []
  for line in lines {
    normalized.push(line.trim_end(chars=" \t\r").to_string())
  }
  let mut start = 0
  let mut end_ = normalized.length()
  while start < end_ && normalized[start].trim().length() == 0 {
    start += 1
  }
  while end_ > start && normalized[end_ - 1].trim().length() == 0 {
    end_ -= 1
  }
  let trimmed : Array[String] = []
  for index in start..` block when the AST can be rendered safely.
fn render_template_block(
  raw_template : String,
  nodes : Array[@compiler.TemplateNode],
) -> String {
  if raw_template.contains("