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

///|
/// One node of the tree. Mixed content survives: text, CDATA, elements,
/// comments, processing instructions and the doctype are all nodes, in
/// document order — none of which fits in a JSON-shaped value, which is why
/// this is not moonjson.
pub(all) enum Node {
  ElementNode(Element)
  TextNode(String)
  CDataNode(String)
  CommentNode(String)
  ProcessingInstructionNode(ProcessingInstruction)
  DoctypeNode(String)
}

///|
/// A processing instruction: ``, with `data` the raw text
/// between the target and the `?>`, so an instruction round-trips exactly.
pub(all) struct ProcessingInstruction {
  target : String
  data : String
}

///|
/// How a document was read, and therefore how it writes itself back.
pub(all) enum Flavour {
  Xml
  Html
} derive(Eq, Debug)

///|
pub extend Flavour with Eq::{equal, not_equal}

///|
pub extend Flavour with Debug::{to_repr}

///|
pub impl Show for Flavour with fn output(self, logger) {
  logger.write_string(
    match self {
      Xml => "xml"
      Html => "html"
    },
  )
}

///|
pub extend Flavour with Show::{output, to_string}

///|
/// An element: the `{uri}local` name — the James Clark spelling, chosen over
/// registered prefixes so a node reads the same whichever prefixes the
/// document declared — its attributes in document order, its children, its
/// parent, and how it was closed (`` versus ``) for the writer.
pub struct Element {
  name : String
  attrs : Array[(String, String)]
  children : Array[Node]
  mut parent : Element?
  mut closed : Bool
  priv mut home : Document?
  priv mut content : Element?
}

///|
/// A document: the virtual root (named `#document`) holding the top-level
/// nodes, the flavour it was read as, the selector registry, and — for HTML —
/// the tally of what the parser repaired.
pub struct Document {
  root : Element
  priv selectors : Map[String, &Selector]
  priv mut flavour : Flavour
  priv mut repairs : Repairs
  priv mut nesting : Array[Element]
  priv mut unresolved : Array[String]
}

///|
/// A fresh document. A tree built by hand has no syntax it was read from, so
/// it writes back with XML's exact rules; ask for `flavour=Html` when the tree
/// came from HTML.
pub fn Document::new(flavour~ : Flavour = Xml) -> Document {
  let root = Element::new("#document")
  let doc : Document = {
    root,
    selectors: Map([]),
    flavour,
    repairs: Repairs::new(),
    nesting: [],
    unresolved: [],
  }
  root.home = Some(doc)
  doc.seed_selectors()
  doc
}

///|
/// The contents of a `