///|
/// A downstream event consumer used by parsers, rules, renderers, and transforms.
pub struct Sink {
  priv emit_event : (Event) -> Unit
  priv finish_stream : () -> Unit
}

///|
/// Creates an event sink.
pub fn Sink::Sink(
  emit : (Event) -> Unit,
  finish? : () -> Unit = fn() { () },
) -> Sink {
  { emit_event: emit, finish_stream: finish }
}

///|
/// Emits an event downstream.
pub fn Sink::emit(self : Sink, event : Event) -> Unit {
  (self.emit_event)(event)
}

///|
/// Finishes the stream and lets downstream consumers flush buffered state.
pub fn Sink::finish(self : Sink) -> Unit {
  (self.finish_stream)()
}

///|
/// Context passed to block rules.
pub struct BlockContext {
  priv lines : Array[String]
  priv line_starts : Array[Int]
  priv index : Int
  priv registry : RuleRegistry
  priv plan : PreparedRulePlan
  priv inline_rules : Array[RuleEntry]
  priv inline_mode : InlineParseMode
  priv inline_trigger_chars : String
  priv state : ParseState
  priv collect_only : Bool
}

///|
/// Context passed to inline and delimiter rules.
pub struct InlineContext {
  priv text : String
  priv mut index : Int
  priv registry : RuleRegistry
  priv plan : PreparedRulePlan
  priv inline_rules : Array[RuleEntry]
  priv inline_mode : InlineParseMode
  priv inline_trigger_chars : String
  priv state : ParseState
}

///|
/// Parser services exposed to rule implementations.
pub struct ParserServices {
  priv registry : RuleRegistry
  priv plan : PreparedRulePlan
  priv inline_rules : Array[RuleEntry]
  priv inline_mode : InlineParseMode
  priv inline_trigger_chars : String
  priv state : ParseState
}

///|
fn ParserServices::ParserServices(
  registry : RuleRegistry,
  plan : PreparedRulePlan,
  inline_rules : Array[RuleEntry],
  inline_mode : InlineParseMode,
  inline_trigger_chars : String,
  state : ParseState,
) -> ParserServices {
  { registry, plan, inline_rules, inline_mode, inline_trigger_chars, state }
}

///|
/// Returns the line currently being considered by a block rule.
pub fn BlockContext::current_line(self : BlockContext) -> String {
  if self.index >= 0 && self.index < self.lines.length() {
    self.lines[self.index]
  } else {
    ""
  }
}

///|
/// Returns the source line at `offset` relative to the current block line.
pub fn BlockContext::line(self : BlockContext, offset : Int) -> String? {
  self.line_at(self.index + offset)
}

///|
/// Returns the source line at `index`, if it exists.
pub fn BlockContext::line_at(self : BlockContext, index : Int) -> String? {
  if index >= 0 && index < self.lines.length() {
    Some(self.lines[index])
  } else {
    None
  }
}

///|
/// Returns the total number of source lines in this block parse.
pub fn BlockContext::line_count(self : BlockContext) -> Int {
  self.lines.length()
}

///|
/// Returns all source lines visible to this block parse.
pub fn BlockContext::source_lines(self : BlockContext) -> Array[String] {
  self.lines
}

///|
/// Returns parser offsets for all source lines visible to this block parse.
pub fn BlockContext::source_line_starts(self : BlockContext) -> Array[Int] {
  self.line_starts
}

///|
/// Returns the current zero-based source line index.
pub fn BlockContext::line_index(self : BlockContext) -> Int {
  self.index
}

///|
/// Returns the parser offset where the current source line starts.
pub fn BlockContext::current_line_start(self : BlockContext) -> Int {
  if self.index >= 0 && self.index < self.line_starts.length() {
    self.line_starts[self.index]
  } else {
    0
  }
}

///|
/// Returns the parser offset where the source line at `offset` starts.
pub fn BlockContext::source_offset(self : BlockContext, offset : Int) -> Int {
  let index = self.index + offset
  if index >= 0 && index < self.line_starts.length() {
    self.line_starts[index]
  } else {
    0
  }
}

///|
/// Returns parser services for nested parsing from a block rule.
pub fn BlockContext::services(self : BlockContext) -> ParserServices {
  ParserServices(
    self.registry,
    self.plan,
    self.inline_rules,
    self.inline_mode,
    self.inline_trigger_chars,
    self.state,
  )
}

///|
/// Returns whether the parser is currently collecting definitions.
pub fn BlockContext::is_collecting(self : BlockContext) -> Bool {
  self.collect_only
}

///|
/// Returns the full inline source being scanned.
pub fn InlineContext::source(self : InlineContext) -> String {
  self.text
}

///|
/// Returns the current parser offset in the inline source.
pub fn InlineContext::offset(self : InlineContext) -> Int {
  self.index
}

///|
/// Returns a scanner positioned at the current inline offset.
pub fn InlineContext::scanner(self : InlineContext) -> InlineScanner {
  inline_scanner(self.text, self.index)
}

///|
/// Returns whether any inline rule declares the character at `index` as a
/// trigger. Text rules can use this to leave extension starts visible to the
/// extension rule instead of consuming them as ordinary text.
pub fn InlineContext::has_inline_trigger_at(
  self : InlineContext,
  index : Int,
) -> Bool {
  if index < 0 || index >= self.text.length() {
    return false
  }
  let c = char_at(self.text, index)
  contains_char(self.inline_trigger_chars, c)
}

///|
/// Returns whether any inline rule declares a trigger outside `ignored`.
///
/// Text rules can use this to keep their own syntax characters on the fast
/// path while still yielding to extension rules with new trigger characters.
pub fn InlineContext::has_inline_triggers_other_than(
  self : InlineContext,
  ignored : String,
) -> Bool {
  if ignored == commonmark_inline_trigger_chars() {
    self.plan.inline_has_triggers_outside_commonmark
  } else {
    inline_has_triggers_other_than(self.inline_trigger_chars, ignored)
  }
}

///|
/// Returns parser services for nested parsing from an inline rule.
pub fn InlineContext::services(self : InlineContext) -> ParserServices {
  ParserServices(
    self.registry,
    self.plan,
    self.inline_rules,
    self.inline_mode,
    self.inline_trigger_chars,
    self.state,
  )
}

///|
/// Parses inline text with the current inline rule set into `sink`.
pub fn ParserServices::parse_inline(
  self : ParserServices,
  text : String,
  sink : Sink,
) -> Unit {
  parse_inlines_with_rules(
    text,
    self.registry,
    self.plan,
    self.inline_rules,
    self.inline_mode,
    self.inline_trigger_chars,
    sink,
    self.state,
  )
}

///|
/// Parses inline text with the current inline rule set and returns a fragment.
pub fn ParserServices::parse_inline_fragment(
  self : ParserServices,
  text : String,
) -> EventFragment {
  let events = Events()
  self.parse_inline(text, events.owned_sink())
  events.into_fragment()
}

///|
/// Parses block lines with the current rule set into `sink`.
pub fn ParserServices::parse_block_lines(
  self : ParserServices,
  lines : Array[String],
  line_starts : Array[Int],
  sink : Sink,
) -> Unit {
  parse_block_lines_into(lines, line_starts, self.plan, sink, self.state)
}

///|
/// Runs block rules in definition-collection mode over `lines`.
pub fn ParserServices::collect_block_lines(
  self : ParserServices,
  lines : Array[String],
  line_starts : Array[Int],
) -> Unit {
  parse_block_lines_into(
    lines,
    line_starts,
    self.plan,
    Sink(fn(_) { () }),
    self.state,
    collect_only=true,
  )
}

///|
/// Returns a string value from parser state.
pub fn ParserServices::state_string(
  self : ParserServices,
  key : StateKey,
  item : String,
) -> String? {
  self.state.string_get(key, item)
}

///|
/// Stores a string value in parser state.
pub fn ParserServices::set_state_string(
  self : ParserServices,
  key : StateKey,
  item : String,
  value : String,
) -> Unit {
  self.state.string_set(key, item, value)
}

///|
/// A small scanner for inline rule implementations.
pub struct InlineScanner {
  priv text : String
  priv index : Int
}

///|
/// Creates an inline scanner positioned at `index`.
pub fn InlineScanner::new(text : String, index : Int) -> InlineScanner? {
  if index < 0 || index > text.length() {
    None
  } else {
    Some(inline_scanner(text, index))
  }
}

///|
fn inline_scanner(text : String, index : Int) -> InlineScanner {
  { text, index }
}

///|
/// Returns whether the scanner position starts with `prefix`.
pub fn InlineScanner::has_prefix(self : InlineScanner, prefix : String) -> Bool {
  has_prefix_at(self.text, prefix, self.index)
}

///|
/// Returns the character code at the scanner position.
pub fn InlineScanner::char(self : InlineScanner) -> Int? {
  if self.index >= 0 && self.index < self.text.length() {
    Some(self.text[self.index].to_int())
  } else {
    None
  }
}

///|
/// Returns the character code at an offset from the scanner position.
pub fn InlineScanner::char_at_offset(
  self : InlineScanner,
  offset : Int,
) -> Int? {
  let target = self.index + offset
  if target >= 0 && target < self.text.length() {
    Some(self.text[target].to_int())
  } else {
    None
  }
}

///|
/// Returns a slice relative to the scanner position.
pub fn InlineScanner::slice(
  self : InlineScanner,
  start_offset : Int,
  end_offset : Int,
) -> String? {
  let start = self.index + start_offset
  let end = self.index + end_offset
  if start < 0 || end < start || end > self.text.length() {
    None
  } else {
    Some(self.text[start:end].to_owned())
  }
}

///|
/// Finds `needle` from the scanner position and returns a relative offset.
pub fn InlineScanner::find_char(self : InlineScanner, needle : Int) -> Int? {
  for i in self.index..