// The scratch tree the block phase builds. Nodes live in one array and refer to
// each other by index, so a parent can be linked and unlinked without the
// ownership questions a tree of pointers would raise in MoonBit.

///|
#derive(Eq)
priv enum Kind {
  Document
  Quote
  List
  Item
  Paragraph
  Heading
  Code
  Html
  ThematicBreak
  Table
  TableRow
  TableCell
  FootnoteDefinition
}

///|
/// The marks a list carries: the bullet character, or the number and delimiter
/// of an ordered list.
priv enum ListShape {
  NoList
  Bullet(Char)
  Ordered(Int32, Char)
}

///|
priv struct TreeNode {
  mut kind : Kind
  mut parent : Int32
  mut first_child : Int32
  mut last_child : Int32
  mut next : Int32
  mut prev : Int32
  mut open : Bool
  /// Whether the last line that belonged to this block was blank.
  mut blank : Bool
  /// Whether `blank` already stands for the whole subtree.
  mut settled : Bool
  content : StringBuilder
  start_line : Int32
  mut level : Int32
  mut setext : Bool
  mut fenced : Bool
  mut fence_char : Int32
  mut fence_length : Int32
  mut fence_offset : Int32
  mut info : String
  mut html_type : Int32
  mut shape : ListShape
  mut marker_offset : Int32
  mut padding : Int32
  mut tight : Bool
  mut task : Task
  mut align : Array[Align]
  mut header : Bool
  /// Whether a paragraph already failed to become a table, so the delimiter row
  /// is not tried again with every following line.
  mut visited : Bool
}

///|
/// The tree: node zero is the document.
priv struct Tree {
  nodes : Array[TreeNode]
}

///|
fn Tree::new() -> Tree {
  let tree : Tree = { nodes: [], }
  tree.nodes.push(TreeNode::{
    kind: Document,
    parent: -1,
    first_child: -1,
    last_child: -1,
    next: -1,
    prev: -1,
    open: true,
    blank: false,
    settled: false,
    content: StringBuilder(),
    start_line: 0,
    level: 0,
    setext: false,
    fenced: false,
    fence_char: 0,
    fence_length: 0,
    fence_offset: 0,
    info: "",
    html_type: 0,
    shape: NoList,
    marker_offset: 0,
    padding: 0,
    tight: true,
    task: Plain,
    align: [],
    header: false,
    visited: false,
  })
  tree
}

///|
fn Tree::make(self : Tree, kind : Kind, line : Int32) -> Int32 {
  self.nodes.push(TreeNode::{
    kind,
    parent: -1,
    first_child: -1,
    last_child: -1,
    next: -1,
    prev: -1,
    open: true,
    blank: false,
    settled: false,
    content: StringBuilder(),
    start_line: line,
    level: 0,
    setext: false,
    fenced: false,
    fence_char: 0,
    fence_length: 0,
    fence_offset: 0,
    info: "",
    html_type: 0,
    shape: NoList,
    marker_offset: 0,
    padding: 0,
    tight: true,
    task: Plain,
    align: [],
    header: false,
    visited: false,
  })
  self.nodes.length() - 1
}

///|

///|
/// Whether a block of `parent` kind may hold one of `child` kind. A child that
/// does not fit closes its parent first, which is how a heading inside a table
/// ends the table.
fn Tree::can_contain(self : Tree, parent : Int32, child : Kind) -> Bool {
  match self.nodes[parent].kind {
    Document | Quote | Item | FootnoteDefinition => true
    List => child == Item
    Table => child == TableRow
    TableRow => child == TableCell
    _ => false
  }
}

///|
fn Tree::append_child(self : Tree, parent : Int32, child : Int32) -> Unit {
  let last = self.nodes[parent].last_child
  self.nodes[child].parent = parent
  self.nodes[child].prev = last
  self.nodes[child].next = -1
  if last >= 0 {
    self.nodes[last].next = child
  } else {
    self.nodes[parent].first_child = child
  }
  self.nodes[parent].last_child = child
}

///|
/// Put `node` in front of `index` in the same parent.
fn Tree::insert_before(self : Tree, index : Int32, node : Int32) -> Unit {
  let parent = self.nodes[index].parent
  let prev = self.nodes[index].prev
  self.nodes[node].parent = parent
  self.nodes[node].prev = prev
  self.nodes[node].next = index
  self.nodes[index].prev = node
  if prev >= 0 {
    self.nodes[prev].next = node
  } else {
    self.nodes[parent].first_child = node
  }
}

///|
fn Tree::unlink(self : Tree, index : Int32) -> Unit {
  let parent = self.nodes[index].parent
  if parent < 0 {
    return
  }
  let prev = self.nodes[index].prev
  let next = self.nodes[index].next
  if prev >= 0 {
    self.nodes[prev].next = next
  } else {
    self.nodes[parent].first_child = next
  }
  if next >= 0 {
    self.nodes[next].prev = prev
  } else {
    self.nodes[parent].last_child = prev
  }
  self.nodes[index].parent = -1
  self.nodes[index].prev = -1
  self.nodes[index].next = -1
}

///|
/// Whether the block, or the last block inside it, ended on a blank line. This
/// is what makes a list loose.
fn Tree::ends_with_blank_line(self : Tree, index : Int32) -> Bool {
  if self.nodes[index].settled {
    return self.nodes[index].blank
  }
  let node = self.nodes[index]
  if (node.kind == List || node.kind == Item) && node.last_child >= 0 {
    self.nodes[index].settled = true
    self.ends_with_blank_line(node.last_child)
  } else {
    self.nodes[index].settled = true
    node.blank
  }
}

///|
fn Tree::last_child_is_open(self : Tree, index : Int32) -> Bool {
  let last = self.nodes[index].last_child
  last >= 0 && self.nodes[last].open
}