///|
/// A parsed document: the blocks in order, and the flavor it was read with,
/// which the HTML renderer needs for the tag filter.
pub struct Document {
  priv blocks : Array[Block]
  priv flavor : Flavor
  priv lines : Array[LineSpan]
}

///|
/// Assemble a document out of blocks, which is what a caller collecting the
/// output of a `Stream` needs in order to render it. Footnote references are
/// resolved against the definitions in the blocks, so a definition read after
/// the reference that uses it still applies.
pub fn Document::of_blocks(
  blocks : Array[Block],
  flavor~ : Flavor = flavor,
) -> Document {
  let settled = settle_footnotes(blocks, flavor)
  { blocks: settled, flavor, lines: [], }
}

///|
/// The same, for a document that was read from text: the lines each block came
/// from are carried along, and moved with the blocks that reading footnotes
/// rearranges.
fn Document::from_source(
  blocks : Array[Block],
  flavor : Flavor,
  lines : Array[LineSpan],
) -> Document {
  let settled = settle_footnotes(blocks, flavor)
  // Reading footnotes moves definitions to the end and drops the ones nothing
  // refers to, so the lines have to be put back in step with the blocks.
  let aligned = if flavor.footnotes {
    realign_lines(blocks, settled, lines)
  } else {
    lines
  }
  { blocks: settled, flavor, lines: aligned, }
}

///|
/// The top level blocks of the document, in order.
pub fn Document::blocks(self : Document) -> Array[Block] {
  self.blocks
}

///|
/// The lines of the source each top level block was read from, counted from
/// one, one entry per block of `blocks()` and in the same order. Blank lines
/// between blocks belong to neither. A document assembled with `of_blocks` was
/// not read from text, so its list is empty.
///
/// ```mbt check
/// test {
///   let document = @moonmd.parse("# One\n\ntwo\n")
///   let spans = document.lines()
///   assert_eq((spans[0].first, spans[0].last), (1, 1))
///   assert_eq((spans[1].first, spans[1].last), (3, 3))
/// }
/// ```
pub fn Document::lines(self : Document) -> Array[LineSpan] {
  self.lines
}

///|
/// The flavor this document was parsed with.
pub fn Document::flavor(self : Document) -> Flavor {
  self.flavor
}