// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// The tree builder: one consumer of the event stream, shared by whoever
/// emits events — the XML scanner, or a caller reading a log of them. The
/// open-element stack is the builder's state; after a document it is empty.
pub impl @event.Sink for Document with fn push(self, event) {
  let parent = match self.nesting.last() {
    Some(top) => top
    None => self.root
  }
  match event {
    StartTag(name~, attrs~, self_closing~) => {
      let element = Element::new(name, attrs=attrs.copy(), closed=self_closing)
      parent.append(ElementNode(element))
      if !self_closing {
        self.nesting.push(element)
      }
    }
    EndTag(name~) =>
      // The scanner checks pairing; a stray close is still not a reason to
      // lose the rest of the document.
      match self.nesting.last() {
        Some(top) => if top.name == name { ignore(self.nesting.pop()) }
        None => ()
      }
    Text(text~) => if !text.is_empty() { parent.append(TextNode(text)) }
    CData(text~) => parent.append(CDataNode(text))
    Comment(text~) => parent.append(CommentNode(text))
    ProcessingInstruction(target~, data~) =>
      parent.append(ProcessingInstructionNode({ target, data, }))
    Doctype(text~) => self.root.append(DoctypeNode(text))
  }
}

///|
pub extend Document with @event.Sink::{push}

///|
/// Read a finished tree back as the event stream it would have produced.
pub fn Document::events(self : Document) -> Array[@event.Event] {
  let out : Array[@event.Event] = []
  fn walk(node : Node) -> Unit {
    match node {
      ElementNode(data) => {
        out.push(
          @event.StartTag(
            name=data.name,
            attrs=data.attrs.copy(),
            self_closing=data.children.is_empty() && data.closed,
          ),
        )
        for child in data.children {
          walk(child)
        }
        out.push(@event.EndTag(name=data.name))
      }
      TextNode(text) => out.push(@event.Text(text~))
      CDataNode(text) => out.push(@event.CData(text~))
      CommentNode(text) => out.push(@event.Comment(text~))
      ProcessingInstructionNode(pi) =>
        out.push(@event.ProcessingInstruction(target=pi.target, data=pi.data))
      DoctypeNode(text) => out.push(@event.Doctype(text~))
    }
  }
  for child in self.root.children {
    walk(child)
  }
  out
}