//! Canonical event dump used as the unit-test oracle.
//!
//! The output format is a line-per-event canonical dump.

///|
/// Parses `source` and returns a line-per-event canonical dump.
pub fn event_dump(source : String, options : Options) -> String {
  let events = parse(source, options)
  let parts = events.map(dump_event)
  parts.join("\n")
}

///|
fn dump_escape(s : String) -> String {
  let bytes = @utf8.encode(s)
  let buf = @buffer.Buffer(size_hint=bytes.length())
  for i in 0.. buf.write_bytesview(b"\\\\".view())
      0x0a => buf.write_bytesview(b"\\n".view())
      0x0d => buf.write_bytesview(b"\\r".view())
      0x09 => buf.write_bytesview(b"\\t".view())
      0x7c => buf.write_bytesview(b"\\|".view())
      _ => buf.write_byte(b)
    }
  }
  @utf8.decode_lossy(buf.to_bytes().view())
}

///|
fn dump_event(ev : Event) -> String {
  match ev {
    Start(tag) => dump_tag(tag)
    End(tag_end) => dump_tag_end(tag_end)
    Text(s) => "Text|" + dump_escape(s)
    Code(s) => "Code|" + dump_escape(s)
    InlineMath(s) => "InlineMath|" + dump_escape(s)
    DisplayMath(s) => "DisplayMath|" + dump_escape(s)
    Html(s) => "Html|" + dump_escape(s)
    InlineHtml(s) => "InlineHtml|" + dump_escape(s)
    FootnoteReference(s) => "FootnoteReference|" + dump_escape(s)
    SoftBreak => "SoftBreak"
    HardBreak => "HardBreak"
    Rule => "Rule"
    TaskListMarker(b) => "TaskListMarker|" + b.to_string()
  }
}

///|
fn dump_tag(tag : Tag) -> String {
  match tag {
    Paragraph => "Start|Paragraph"
    Heading(level~, id~, classes~, attrs~) =>
      "Start|Heading|" +
      level_str(level) +
      "|" +
      id.map_or("None", fn(s) { "Some(" + dump_escape(s) + ")" }) +
      "|" +
      classes.map(dump_escape).join(",") +
      "|" +
      join_attrs(attrs)
    BlockQuote(kind) => "Start|BlockQuote|" + blockquote_str(kind)
    CodeBlock(kind) => "Start|CodeBlock|" + codeblock_str(kind)
    ContainerBlock(kind, d) =>
      "Start|ContainerBlock|" + container_str(kind) + "|" + dump_escape(d)
    HtmlBlock => "Start|HtmlBlock"
    List(start) => "Start|List|" + list_str(start)
    Item => "Start|Item"
    FootnoteDefinition(s) => "Start|FootnoteDefinition|" + dump_escape(s)
    DefinitionList => "Start|DefinitionList"
    DefinitionListTitle => "Start|DefinitionListTitle"
    DefinitionListDefinition => "Start|DefinitionListDefinition"
    Table(a) => "Start|Table|" + join_alignments(a)
    TableHead => "Start|TableHead"
    TableRow => "Start|TableRow"
    TableCell => "Start|TableCell"
    Emphasis => "Start|Emphasis"
    Strong => "Start|Strong"
    Strikethrough => "Start|Strikethrough"
    Highlight => "Start|Highlight"
    Superscript => "Start|Superscript"
    Subscript => "Start|Subscript"
    Link(link_type~, dest_url~, title~, id~) =>
      "Start|Link|" +
      linktype_str(link_type) +
      "|" +
      dump_escape(dest_url) +
      "|" +
      dump_escape(title) +
      "|" +
      dump_escape(id)
    Image(link_type~, dest_url~, title~, id~) =>
      "Start|Image|" +
      linktype_str(link_type) +
      "|" +
      dump_escape(dest_url) +
      "|" +
      dump_escape(title) +
      "|" +
      dump_escape(id)
    MetadataBlock(kind) => "Start|MetadataBlock|" + metadata_str(kind)
  }
}

///|
fn dump_tag_end(te : TagEnd) -> String {
  match te {
    Paragraph => "End|Paragraph"
    Heading(level) => "End|Heading|" + level_str(level)
    BlockQuote(kind) => "End|BlockQuote|" + blockquote_str(kind)
    CodeBlock => "End|CodeBlock"
    ContainerBlock(kind) => "End|ContainerBlock|" + container_str(kind)
    HtmlBlock => "End|HtmlBlock"
    List(b) => "End|List|" + b.to_string()
    Item => "End|Item"
    FootnoteDefinition => "End|FootnoteDefinition"
    DefinitionList => "End|DefinitionList"
    DefinitionListTitle => "End|DefinitionListTitle"
    DefinitionListDefinition => "End|DefinitionListDefinition"
    Table => "End|Table"
    TableHead => "End|TableHead"
    TableRow => "End|TableRow"
    TableCell => "End|TableCell"
    Emphasis => "End|Emphasis"
    Strong => "End|Strong"
    Strikethrough => "End|Strikethrough"
    Highlight => "End|Highlight"
    Superscript => "End|Superscript"
    Subscript => "End|Subscript"
    Link => "End|Link"
    Image => "End|Image"
    MetadataBlock(kind) => "End|MetadataBlock|" + metadata_str(kind)
  }
}

///|
fn level_str(level : HeadingLevel) -> String {
  match level {
    H1 => "H1"
    H2 => "H2"
    H3 => "H3"
    H4 => "H4"
    H5 => "H5"
    H6 => "H6"
  }
}

///|
fn join_attrs(items : Array[(String, String?)]) -> String {
  let parts = items.map(fn(p) {
    match p {
      (k, Some(v)) => dump_escape(k) + "=" + dump_escape(v)
      (k, None) => dump_escape(k)
    }
  })
  parts.join(",")
}

///|
fn join_alignments(items : Array[Alignment]) -> String {
  let parts = items.map(fn(a) {
    match a {
      None => "None"
      Left => "Left"
      Center => "Center"
      Right => "Right"
    }
  })
  parts.join(",")
}

///|
fn list_str(o : Int64?) -> String {
  match o {
    Some(n) => "Some(" + n.to_string() + ")"
    None => "None"
  }
}

///|
fn blockquote_str(kind : BlockQuoteKind?) -> String {
  match kind {
    None => "None"
    Some(Note) => "Note"
    Some(Tip) => "Tip"
    Some(Important) => "Important"
    Some(Warning) => "Warning"
    Some(Caution) => "Caution"
  }
}

///|
fn container_str(kind : ContainerKind) -> String {
  match kind {
    Default => "Default"
    Spoiler => "Spoiler"
  }
}

///|
fn metadata_str(kind : MetadataBlockKind) -> String {
  match kind {
    YamlStyle => "YamlStyle"
    PlusesStyle => "PlusesStyle"
  }
}

///|
fn codeblock_str(kind : CodeBlockKind) -> String {
  match kind {
    Indented => "Indented"
    Fenced(lang) => "Fenced(" + dump_escape(lang) + ")"
  }
}

///|
fn linktype_str(lt : LinkType) -> String {
  match lt {
    Inline => "Inline"
    Reference => "Reference"
    ReferenceUnknown => "ReferenceUnknown"
    Collapsed => "Collapsed"
    CollapsedUnknown => "CollapsedUnknown"
    Shortcut => "Shortcut"
    ShortcutUnknown => "ShortcutUnknown"
    Autolink => "Autolink"
    Email => "Email"
    WikiLink(b) => "WikiLink(" + b.to_string() + ")"
  }
}