///|
/// Why `format` gives no text.
pub(all) suberror FormatError {
  /// The text has a diagnostic with severity `Error` (all of them, in the
  /// parser's order). Render them with `@report.render_plain`.
  ParseFailed(Array[@diagnostic.Diagnostic])
  /// The fuel budget of the configuration ran out while the formatter
  /// walked a definition; the error names the operations on the stack.
  OutOfFuel(@fuel.FuelExhausted)
} derive(Eq, Debug)

///|
/// Format Gleam source as `gleam format` (1.19.2) does: 80 columns, two
/// spaces of indentation, no options. Comments and empty lines are kept
/// where the compiler keeps them; the output is idempotent and parses
/// again where the compiler's is (see "Format Parity" in AGENTS.md for
/// the known differences). Raises `ParseFailed` when the text has a
/// syntax error,
/// `OutOfFuel` when `config.fuel` runs out.
///
/// ```mbt check
/// test {
///   inspect(
///     @format.format("pub fn main(){\n  io.println(  \"hi\" )\n}\n"),
///     content="pub fn main() {\n  io.println(\"hi\")\n}\n",
///   )
/// }
/// ```
pub fn format(
  text : String,
  config? : @config.Config,
) -> String raise FormatError {
  format_parsed(@parser.parse_text(text, config?), config?)
}

///|
/// Format a parse result, as `format` formats text. The source is rebuilt
/// from the CST (byte for byte), so the comments keep their text. Raises
/// `ParseFailed` when the result has an error diagnostic.
///
/// ```mbt check
/// test {
///   let r = @parser.parse_text("//// m\n// c\n")
///   inspect(@format.format_parsed(r), content="//// m\n\n// c\n")
/// }
/// ```
pub fn format_parsed(
  result : @parser.ParseResult,
  config? : @config.Config,
) -> String raise FormatError {
  let errors = result.diagnostics
    .iter()
    .filter(d => d.severity is Error)
    .to_array()
  guard errors.is_empty() else { raise ParseFailed(errors) }
  let config = match config {
    Some(c) => c
    None => @config.Config::default()
  }
  let source = @scanner.SourceText::new(result.cst.to_source())
  let formatter = Formatter::new(result.extra, source, config)
  pretty_print(formatter.format_module(result.module_))
}

///|
/// Lay out a document as the compiler's `pretty_print(80)` does.
fn pretty_print(doc : @pretty.Doc) -> String {
  @pretty.render(doc, width=80, start=Unbroken)
}