///|
/// A Markdown element tag used by events, renderers, and transforms.
///
/// CommonMark tags use the `cm:` namespace. Extension authors should use a
/// custom namespace or an agreed raw value to avoid collisions.
pub struct Tag {
  priv value : String
} derive(Eq)

///|
/// An element attribute name.
///
/// CommonMark attributes such as `level`, `destination`, and `title` are
/// unqualified. Extension attributes can use a custom namespace.
pub struct AttrName {
  priv value : String
} derive(Eq)

///|
/// A rule identifier used for ordering, override, enable, and disable.
///
/// Built-in CommonMark rules use the `cm:` namespace.
pub struct RuleName {
  priv value : String
} derive(Eq)

///|
/// Creates a CommonMark tag by prefixing `name` with `cm:`.
pub fn Tag::commonmark(name : String) -> Tag {
  { value: "cm:" + name }
}

///|
/// Creates an extension tag from `ns` and `name` as `ns:name`.
pub fn Tag::custom(ns : String, name : String) -> Tag {
  { value: ns + ":" + name }
}

///|
/// Creates a tag from an already-qualified value.
pub fn Tag::raw(value : String) -> Tag {
  { value, }
}

///|
/// Returns the tag value used in events and rendered extension HTML.
pub fn Tag::to_string(self : Tag) -> String {
  self.value
}

///|
/// Creates a CommonMark attribute name.
///
/// Attribute names are not prefixed with `cm:` because they are interpreted in
/// the context of their element tag.
pub fn AttrName::commonmark(name : String) -> AttrName {
  { value: name }
}

///|
/// Creates an extension attribute name from `ns` and `name` as `ns:name`.
pub fn AttrName::custom(ns : String, name : String) -> AttrName {
  { value: ns + ":" + name }
}

///|
/// Creates an attribute name from an already-qualified value.
pub fn AttrName::raw(value : String) -> AttrName {
  { value, }
}

///|
/// Returns the attribute name value used by renderers.
pub fn AttrName::to_string(self : AttrName) -> String {
  self.value
}

///|
/// Creates a CommonMark rule name by prefixing `name` with `cm:`.
pub fn RuleName::commonmark(name : String) -> RuleName {
  { value: "cm:" + name }
}

///|
/// Creates an extension rule name from `ns` and `name` as `ns:name`.
pub fn RuleName::custom(ns : String, name : String) -> RuleName {
  { value: ns + ":" + name }
}

///|
/// Creates a rule name from an already-qualified value.
pub fn RuleName::raw(value : String) -> RuleName {
  { value, }
}

///|
/// Returns the rule name value used by `Processor` rule controls.
pub fn RuleName::to_string(self : RuleName) -> String {
  self.value
}

///|
fn tag_name(value : String) -> Tag {
  { value, }
}

///|
/// A parser source range measured in MoonBit string offsets.
///
/// `start` is inclusive and `end` is exclusive.
/// These offsets are compatible with the parser's `String` indexing and line
/// normalization; they are not UTF-8 byte offsets.
pub struct SourceRange {
  priv start : Int
  priv end : Int
}

///|
/// Creates a source range from inclusive `start` and exclusive `end` parser
/// offsets.
pub fn SourceRange::new(
  start : Int,
  end : Int,
) -> SourceRange? where {
  proof_ensure: result => {
    match result {
      Some(range) =>
        range.start == start &&
        range.end == end &&
        range.start >= 0 &&
        range.end >= range.start
      None => start < 0 || end < start
    }
  },
} {
  if start < 0 || end < start {
    None
  } else {
    Some(source_range(start, end))
  }
}

///|
/// Returns the inclusive start parser offset.
pub fn SourceRange::start(self : SourceRange) -> Int {
  self.start
}

///|
/// Returns the exclusive end parser offset.
pub fn SourceRange::end(self : SourceRange) -> Int {
  self.end
}

///|
fn source_range(
  start : Int,
  end : Int,
) -> SourceRange where {
  proof_require: start >= 0 && end >= start,
  proof_ensure: result => result.start == start && result.end == end,
} {
  { start, end }
}

///|
/// An attribute attached to an `Element`.
pub struct Attr {
  priv name : AttrName
  priv value : String
}

///|
/// A Markdown event element.
///
/// Elements carry a tag, attributes, and an optional source range. Extension
/// authors can construct elements with custom tags and attributes.
pub struct Element {
  priv tag : Tag
  priv attrs : Array[Attr]
  priv range : SourceRange?
}

///|
/// A parser event stream.
///
/// `Enter` and `Exit` delimit elements by tag. `Text`, breaks, and raw HTML are
/// leaf events. Extension tags are preserved by the parser, renderer, AST
/// builder, and transforms.
pub(all) enum Event {
  Enter(Element)
  Exit(Tag)
  Text(String)
  SoftBreak
  HardBreak
  RawHtml(String)
}

///|
/// Creates an attribute.
pub fn Attr::Attr(name : AttrName, value : String) -> Attr {
  { name, value }
}

///|
/// Returns the attribute name.
pub fn Attr::name(self : Attr) -> AttrName {
  self.name
}

///|
/// Returns the attribute value.
pub fn Attr::value(self : Attr) -> String {
  self.value
}

///|
/// Creates an element without attributes or source range.
pub fn Element::Element(tag : Tag) -> Element {
  { tag, attrs: [], range: None }
}

///|
/// Creates an element with attributes and an optional source range.
pub fn Element::with_attrs(
  tag : Tag,
  attrs : Array[Attr],
  range? : SourceRange? = None,
) -> Element {
  { tag, attrs: copy_attrs(attrs), range }
}

///|
/// Returns the element tag.
pub fn Element::tag(self : Element) -> Tag {
  self.tag
}

///|
/// Returns the element attributes.
pub fn Element::attrs(self : Element) -> Array[Attr] {
  copy_attrs(self.attrs)
}

///|
/// Returns the first attribute value for `name`, if present.
pub fn Element::attr(self : Element, name : AttrName) -> String? {
  for attr in self.attrs {
    if attr.name == name {
      return Some(attr.value)
    }
  }
  None
}

///|
/// Returns the source range attached to the element, if any.
pub fn Element::range(self : Element) -> SourceRange? {
  self.range
}

///|
fn copy_attrs(attrs : Array[Attr]) -> Array[Attr] {
  let copied : Array[Attr] = []
  for attr in attrs {
    copied.push(attr)
  }
  copied
}

///|
fn copy_element(el : Element) -> Element {
  { tag: el.tag, attrs: copy_attrs(el.attrs), range: el.range }
}

///|
fn copy_event(event : Event) -> Event {
  match event {
    Enter(el) => Enter(copy_element(el))
    Exit(tag) => Exit(tag)
    Text(text) => Text(text)
    SoftBreak => SoftBreak
    HardBreak => HardBreak
    RawHtml(raw) => RawHtml(raw)
  }
}

///|
fn copy_events(events : Array[Event]) -> Array[Event] {
  let copied : Array[Event] = []
  for event in events {
    copied.push(copy_event(event))
  }
  copied
}

///|
/// A balanced event sequence produced by a parser rule.
pub struct EventFragment {
  priv events : Array[Event]
}

///|
/// Creates an empty event fragment.
pub fn EventFragment::empty() -> EventFragment {
  { events: [] }
}

///|
/// Creates a text event fragment.
pub fn EventFragment::text(text : String) -> EventFragment {
  { events: [Text(text)] }
}

///|
/// Creates a soft-break event fragment.
pub fn EventFragment::soft_break() -> EventFragment {
  { events: [SoftBreak] }
}

///|
/// Creates a hard-break event fragment.
pub fn EventFragment::hard_break() -> EventFragment {
  { events: [HardBreak] }
}

///|
/// Creates a raw HTML event fragment.
pub fn EventFragment::html(raw : String) -> EventFragment {
  { events: [RawHtml(raw)] }
}

///|
/// Creates a balanced element event fragment.
pub fn EventFragment::element(
  element : Element,
  children? : Array[EventFragment] = [],
) -> EventFragment {
  let events : Array[Event] = [Enter(copy_element(element))]
  for child in children {
    for event in child.events {
      events.push(copy_event(event))
    }
  }
  events.push(Exit(element.tag))
  { events, }
}

///|
/// Concatenates event fragments.
pub fn EventFragment::concat(fragments : Array[EventFragment]) -> EventFragment {
  let events : Array[Event] = []
  for fragment in fragments {
    for event in fragment.events {
      events.push(copy_event(event))
    }
  }
  { events, }
}

///|
/// Creates a fragment from events if all element enter/exit pairs are balanced.
pub fn EventFragment::from_events(events : Array[Event]) -> EventFragment? {
  match validate_balanced_events(events) {
    true => Some({ events: copy_events(events) })
    false => None
  }
}

///|
fn event_fragment_from_owned_events(events : Array[Event]) -> EventFragment? {
  match validate_balanced_events(events) {
    true => Some({ events, })
    false => None
  }
}

///|
fn validate_balanced_events(events : Array[Event]) -> Bool {
  let stack : Array[Tag] = []
  for event in events {
    match event {
      Enter(element) => stack.push(element.tag)
      Exit(tag) => {
        if stack.length() == 0 {
          return false
        }
        let top = stack[stack.length() - 1]
        if top != tag {
          return false
        }
        ignore(stack.pop())
      }
      _ => ()
    }
  }
  stack.length() == 0
}

///|
/// Returns a copy of the events in this fragment.
pub fn EventFragment::events(self : EventFragment) -> Array[Event] {
  copy_events(self.events)
}

///|
/// Emits this fragment into `sink`.
pub fn EventFragment::emit_to(self : EventFragment, sink : Sink) -> Unit {
  for event in self.events {
    sink.emit(event)
  }
}

///|
/// A downstream sink that collects events into an array.
pub struct Events {
  priv events : Array[Event]
}

///|
/// Creates an empty event collector.
pub fn Events::Events() -> Events {
  { events: [] }
}

///|
/// Returns a sink that appends emitted events to this collector.
pub fn Events::sink(self : Events) -> Sink {
  Sink(fn(event) { self.events.push(copy_event(event)) })
}

///|
pub fn Events::owned_sink(self : Events) -> Sink {
  Sink(fn(event) { self.events.push(event) })
}

///|
/// Returns a copy of collected events.
pub fn Events::collect(self : Events) -> Array[Event] {
  copy_events(self.events)
}

///|
pub fn Events::owned_events(self : Events) -> Array[Event] {
  self.events
}

///|
pub fn Events::into_fragment(self : Events) -> EventFragment {
  match event_fragment_from_owned_events(self.events) {
    Some(fragment) => fragment
    None => EventFragment::empty()
  }
}