///|
priv struct Context {
  tree : @internal.Tree
  mut lines : Array[Int32]?
  identity : Int32
}

///|
let context_counter : Ref[Int32] = { val: 0, }

///|
fn Context::new(tree : @internal.Tree) -> Context {
  context_counter.val += 1
  { tree, lines: None, identity: context_counter.val, }
}

///|
/// An HTML document produced by `parse`.
pub struct Document {
  priv ctx : Context
}

///|
/// An element handle. Handles are cheap views into the document's compact tree.
pub struct Element {
  priv ctx : Context
  priv id : Int32
}

///|
/// A text node, including foreign-content CDATA sections.
pub struct Text {
  priv ctx : Context
  priv id : Int32
}

///|
/// A normal or bogus comment.
pub struct Comment {
  priv ctx : Context
  priv id : Int32
}

///|
/// A doctype declaration.
pub struct Directive {
  priv ctx : Context
  priv id : Int32
}

///|
/// A child node in an HTML document.
#derive(Eq, Debug)
pub(all) enum Node {
  Element(Element)
  Text(Text)
  Comment(Comment)
  Directive(Directive)
}

///|
/// The parent of a top-level or nested node.
#derive(Eq, Debug)
pub(all) enum Parent {
  Document(Document)
  Element(Element)
}

///|
/// A one-based line and column in the original UTF-16 source.
#derive(Eq, Debug)
pub(all) struct Position {
  line : Int32
  column : Int32
}

///|
/// A compiled CSS selector list.
pub struct Selector {
  priv compiled : @internal.Selector
}

///|
/// A malformed or unsupported CSS selector.
pub suberror SelectorError {
  InvalidSelector(String)
}

///|
/// Values that can be serialized back to their exact original HTML source.
pub trait Stringify {
  fn stringify(Self) -> String
}

///|
pub fn[T : Stringify] stringify(target : T) -> String {
  target.stringify()
}

///|
fn compile_selector(input : String) -> @internal.Selector raise SelectorError {
  @internal.compile(input) catch {
    @internal.SelectorSyntax::Message(message) =>
      raise SelectorError::InvalidSelector(message)
  }
}

///|
/// Compile a selector once for repeated matching.
pub fn Selector::parse(input : String) -> Selector raise SelectorError {
  { compiled: compile_selector(input), }
}

///|
pub impl Eq for Document with fn equal(self, other) {
  self.ctx.identity == other.ctx.identity
}

///|
pub impl Eq for Element with fn equal(self, other) {
  self.id == other.id && self.ctx.identity == other.ctx.identity
}

///|
pub impl Eq for Text with fn equal(self, other) {
  self.id == other.id && self.ctx.identity == other.ctx.identity
}

///|
pub impl Eq for Comment with fn equal(self, other) {
  self.id == other.id && self.ctx.identity == other.ctx.identity
}

///|
pub impl Eq for Directive with fn equal(self, other) {
  self.id == other.id && self.ctx.identity == other.ctx.identity
}

///|
pub impl Debug for Document with fn to_repr(self) {
  Repr::Repr("Document(offsets=0..\{self.end()})")
}

///|
pub impl Debug for Element with fn to_repr(self) {
  Repr::Repr("Element(\{self.name()}, offsets=\{self.start()}..\{self.end()})")
}

///|
pub impl Debug for Text with fn to_repr(self) {
  Repr::Repr("Text(\{self.value()})")
}

///|
pub impl Debug for Comment with fn to_repr(self) {
  Repr::Repr("Comment(\{self.value()})")
}

///|
pub impl Debug for Directive with fn to_repr(self) {
  Repr::Repr("Directive(\{self.value()})")
}

///|
fn wrap(ctx : Context, id : Int32) -> Node {
  match ctx.tree.kind[id] {
    Element => Node::Element({ ctx, id, })
    Text => Node::Text({ ctx, id, })
    Comment => Node::Comment({ ctx, id, })
    Directive => Node::Directive({ ctx, id, })
    Document => abort("document cannot be a child node")
  }
}

///|
fn element(ctx : Context, id : Int32) -> Element {
  { ctx, id, }
}

///|
fn children(ctx : Context, id : Int32) -> Array[Node] {
  let output : Array[Node] = []
  let mut current = id + 1
  while current < ctx.tree.stop[id] {
    output.push(wrap(ctx, current))
    current = ctx.tree.stop[current]
  }
  output
}

///|
fn find_one(
  ctx : Context,
  id : Int32,
  selector : String,
) -> Element? raise SelectorError {
  let compiled = compile_selector(selector)
  let found = @internal.all(ctx.tree, id, compiled, limit=1)
  if found.is_empty() {
    None
  } else {
    Some(element(ctx, found[0]))
  }
}

///|
fn find_all(
  ctx : Context,
  id : Int32,
  selector : String,
) -> Array[Element] raise SelectorError {
  @internal.all(ctx.tree, id, compile_selector(selector)).map(id => {
    element(ctx, id)
  })
}

///|
/// Parse HTML without raising on malformed markup. Recovery follows htmlparser2,
/// not the WHATWG browser tree-construction algorithm.
pub fn parse(source : String) -> Document {
  { ctx: Context::new(@internal.build(source)), }
}

///|
/// The exact source from which this document was parsed.
pub fn Document::source(self : Document) -> String {
  self.ctx.tree.source
}

///|
pub fn Document::start(_self : Document) -> Int32 {
  0
}

///|
pub fn Document::end(self : Document) -> Int32 {
  self.ctx.tree.source.length()
}

///|
/// The decoded text of all text nodes in document order.
pub fn Document::text(self : Document) -> String {
  @internal.text(self.ctx.tree, 0)
}

///|
/// Top-level nodes in source order.
pub fn Document::children(self : Document) -> Array[Node] {
  children(self.ctx, 0)
}

///|
/// Find the first matching element, or return `None`.
pub fn Document::find(
  self : Document,
  selector : String,
) -> Element? raise SelectorError {
  find_one(self.ctx, 0, selector)
}

///|
/// Find all matching elements in source order.
pub fn Document::all(
  self : Document,
  selector : String,
) -> Array[Element] raise SelectorError {
  find_all(self.ctx, 0, selector)
}

///|
/// Find the first element using a selector compiled by `Selector::parse`.
pub fn Document::find_with(self : Document, selector : Selector) -> Element? {
  let found = @internal.all(self.ctx.tree, 0, selector.compiled, limit=1)
  if found.is_empty() {
    None
  } else {
    Some(element(self.ctx, found[0]))
  }
}

///|
/// Find all elements using a selector compiled by `Selector::parse`.
pub fn Document::all_with(
  self : Document,
  selector : Selector,
) -> Array[Element] {
  @internal.all(self.ctx.tree, 0, selector.compiled).map(id => {
    element(self.ctx, id)
  })
}

///|
/// Convert a UTF-16 source offset to a one-based line and column.
pub fn Document::position(self : Document, offset : Int32) -> Position {
  if self.ctx.lines is None {
    let lines : Array[Int32] = []
    let mut at = @internal.find_from(self.ctx.tree.source, "\n", 0)
    while at != -1 {
      lines.push(at)
      at = @internal.find_from(self.ctx.tree.source, "\n", at + 1)
    }
    self.ctx.lines = Some(lines)
  }
  let lines = self.ctx.lines.unwrap()
  let mut low = 0
  let mut high = lines.length()
  while low < high {
    let middle = (low + high) >> 1
    if lines[middle] < offset {
      low = middle + 1
    } else {
      high = middle
    }
  }
  {
    line: low + 1,
    column: offset - (if low > 0 { lines[low - 1] + 1 } else { 0 }) + 1,
  }
}

///|
pub impl Stringify for Document with fn stringify(self) {
  self.source()
}

///|
pub fn Element::name(self : Element) -> String {
  self.ctx.tree.name[self.id]
}

///|
pub fn Element::document(self : Element) -> Document {
  { ctx: self.ctx, }
}

///|
pub fn Element::start(self : Element) -> Int32 {
  self.ctx.tree.start[self.id]
}

///|
pub fn Element::end(self : Element) -> Int32 {
  self.ctx.tree.end[self.id]
}

///|
pub fn Element::parent(self : Element) -> Parent {
  let parent = self.ctx.tree.parent[self.id]
  if parent == 0 {
    Parent::Document({ ctx: self.ctx, })
  } else {
    Parent::Element(element(self.ctx, parent))
  }
}

///|
pub fn Element::children(self : Element) -> Array[Node] {
  children(self.ctx, self.id)
}

///|
/// Return an independent map of lowercase attribute names and decoded values.
pub fn Element::attributes(self : Element) -> Map[String, String] {
  @internal.attributes(self.ctx.tree, self.id).copy()
}

///|
/// Read an attribute without distinguishing its ASCII letter case.
pub fn Element::get_attr(self : Element, name : String) -> String? {
  @internal.attribute(self.ctx.tree, self.id, name.to_lower())
}

///|
pub fn Element::has_attr(self : Element, name : String) -> Bool {
  self.get_attr(name) is Some(_)
}

///|
/// The decoded text of all descendant text nodes.
pub fn Element::text(self : Element) -> String {
  @internal.text(self.ctx.tree, self.id)
}

///|
/// The element's inner HTML exactly as written.
pub fn Element::html(self : Element) -> String {
  @internal.slice(
    self.ctx.tree.source,
    self.ctx.tree.from[self.id],
    self.ctx.tree.to[self.id],
  )
}

///|
pub fn Element::find(
  self : Element,
  selector : String,
) -> Element? raise SelectorError {
  find_one(self.ctx, self.id, selector)
}

///|
pub fn Element::all(
  self : Element,
  selector : String,
) -> Array[Element] raise SelectorError {
  find_all(self.ctx, self.id, selector)
}

///|
/// Find the first descendant using a precompiled selector.
pub fn Element::find_with(self : Element, selector : Selector) -> Element? {
  let found = @internal.all(self.ctx.tree, self.id, selector.compiled, limit=1)
  if found.is_empty() {
    None
  } else {
    Some(element(self.ctx, found[0]))
  }
}

///|
/// Find all descendants using a precompiled selector.
pub fn Element::all_with(self : Element, selector : Selector) -> Array[Element] {
  @internal.all(self.ctx.tree, self.id, selector.compiled).map(id => {
    element(self.ctx, id)
  })
}

///|
pub fn Element::matches(
  self : Element,
  selector : String,
) -> Bool raise SelectorError {
  @internal.matches(self.ctx.tree, self.id, compile_selector(selector))
}

///|
/// Return this element or its nearest matching ancestor.
pub fn Element::closest(
  self : Element,
  selector : String,
) -> Element? raise SelectorError {
  let compiled = compile_selector(selector)
  let mut id = self.id
  while id > 0 {
    if @internal.matches(self.ctx.tree, id, compiled) {
      return Some(element(self.ctx, id))
    }
    id = self.ctx.tree.parent[id]
  }
  None
}

///|
/// Match this element against a precompiled selector.
pub fn Element::matches_with(self : Element, selector : Selector) -> Bool {
  @internal.matches(self.ctx.tree, self.id, selector.compiled)
}

///|
/// Return this element or its nearest ancestor matching a precompiled selector.
pub fn Element::closest_with(self : Element, selector : Selector) -> Element? {
  let mut id = self.id
  while id > 0 {
    if @internal.matches(self.ctx.tree, id, selector.compiled) {
      return Some(element(self.ctx, id))
    }
    id = self.ctx.tree.parent[id]
  }
  None
}

///|
pub impl Stringify for Element with fn stringify(self) {
  @internal.slice(self.ctx.tree.source, self.start(), self.end())
}

///|
pub fn Node::document(self : Node) -> Document {
  match self {
    Element(node) => node.document()
    Text(node) => node.document()
    Comment(node) => node.document()
    Directive(node) => node.document()
  }
}

///|
pub fn Node::start(self : Node) -> Int32 {
  match self {
    Element(node) => node.start()
    Text(node) => node.start()
    Comment(node) => node.start()
    Directive(node) => node.start()
  }
}

///|
pub fn Node::end(self : Node) -> Int32 {
  match self {
    Element(node) => node.end()
    Text(node) => node.end()
    Comment(node) => node.end()
    Directive(node) => node.end()
  }
}

///|
pub fn Node::parent(self : Node) -> Parent {
  match self {
    Element(node) => node.parent()
    Text(node) => node.parent()
    Comment(node) => node.parent()
    Directive(node) => node.parent()
  }
}

///|
pub impl Stringify for Node with fn stringify(self) {
  match self {
    Element(node) => node.stringify()
    Text(node) => node.stringify()
    Comment(node) => node.stringify()
    Directive(node) => node.stringify()
  }
}

///|
pub fn Text::value(self : Text) -> String {
  @internal.value(self.ctx.tree, self.id)
}

///|
pub fn Text::document(self : Text) -> Document {
  { ctx: self.ctx, }
}

///|
pub fn Text::start(self : Text) -> Int32 {
  self.ctx.tree.start[self.id]
}

///|
pub fn Text::end(self : Text) -> Int32 {
  self.ctx.tree.end[self.id]
}

///|
pub fn Text::parent(self : Text) -> Parent {
  leaf_parent(self.ctx, self.id)
}

///|
pub impl Stringify for Text with fn stringify(self) {
  @internal.slice(self.ctx.tree.source, self.start(), self.end())
}

///|
pub fn Comment::value(self : Comment) -> String {
  @internal.value(self.ctx.tree, self.id)
}

///|
pub fn Comment::document(self : Comment) -> Document {
  { ctx: self.ctx, }
}

///|
pub fn Comment::start(self : Comment) -> Int32 {
  self.ctx.tree.start[self.id]
}

///|
pub fn Comment::end(self : Comment) -> Int32 {
  self.ctx.tree.end[self.id]
}

///|
pub fn Comment::parent(self : Comment) -> Parent {
  leaf_parent(self.ctx, self.id)
}

///|
pub impl Stringify for Comment with fn stringify(self) {
  @internal.slice(self.ctx.tree.source, self.start(), self.end())
}

///|
pub fn Directive::value(self : Directive) -> String {
  @internal.value(self.ctx.tree, self.id)
}

///|
pub fn Directive::document(self : Directive) -> Document {
  { ctx: self.ctx, }
}

///|
pub fn Directive::start(self : Directive) -> Int32 {
  self.ctx.tree.start[self.id]
}

///|
pub fn Directive::end(self : Directive) -> Int32 {
  self.ctx.tree.end[self.id]
}

///|
pub fn Directive::parent(self : Directive) -> Parent {
  leaf_parent(self.ctx, self.id)
}

///|
pub impl Stringify for Directive with fn stringify(self) {
  @internal.slice(self.ctx.tree.source, self.start(), self.end())
}

///|
fn leaf_parent(ctx : Context, id : Int32) -> Parent {
  let parent = ctx.tree.parent[id]
  if parent == 0 {
    Parent::Document({ ctx, })
  } else {
    Parent::Element(element(ctx, parent))
  }
}