///|
/// Only standard line endings are emitted by the text writers.
pub(all) enum LineEnding {
  LF
  CRLF
} derive(Eq, Debug)

///|
/// Bounded text sink shared by format writers; never stores an array of lines.
pub struct TextOutput {
  priv builder : StringBuilder
  priv mut length : Int
  priv limit : Int
  priv ending : String
}

///|
/// Validate output budget before allocation. The hard limit is 256 Mi characters.
pub fn TextOutput::new(
  ending : LineEnding,
  limit : Int,
) -> TextOutput raise @model.FirmwareError {
  if limit < 0 || limit > 256 * 1024 * 1024 {
    raise @model.FirmwareError(
      @model.diagnostic(
        InvalidOption,
        "text output limit must be between 0 and 256 Mi characters",
      ),
    )
  }
  {
    builder: StringBuilder(),
    length: 0,
    limit,
    ending: if ending == LF {
      "\n"
    } else {
      "\r\n"
    },
  }
}

///|
/// Check incremental encoded size before adding each bounded protocol record.
pub fn TextOutput::write_line(
  self : TextOutput,
  line : String,
) -> Unit raise @model.FirmwareError {
  if line.length() > self.limit - self.length - self.ending.length() {
    raise @model.FirmwareError(
      @model.diagnostic(ResourceLimit, "encoded document exceeds output limit"),
    )
  }
  self.builder.write_string(line)
  self.builder.write_string(self.ending)
  self.length += line.length() + self.ending.length()
}

///|
/// Finish deterministic ASCII text after all record checks succeed.
pub fn TextOutput::finish(self : TextOutput) -> String {
  self.builder.to_string()
}