// 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)
  AttributeNode(Attribute)
  NamespaceNode(Namespace)
}

///|
/// An attribute as a node of its own: XPath's attribute node. It is not a
/// child of its element — the element's attributes stay in `attrs`, which is
/// what the writer reads — but a query that asks for `@href` or for the
/// attribute axis answers these, so that `attribute::x/self::node()` has
/// something with an identity to answer.
pub(all) struct Attribute {
  owner : Element
  name : String
  value : String
}

///|
/// A namespace as a node of its own: XPath's namespace node, answered by the
/// `namespace` axis. `prefix` is `""` for the default namespace.
pub(all) struct Namespace {
  owner : Element
  prefix : String
  uri : 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.
#derive(Eq, Debug)
pub(all) enum Flavour {
  Xml
  Html
}

///|
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
  /// How the document was read: whether the reader was allowed to run what it
  /// read. A `noscript`'s content is text when it was and markup when it was
  /// not, so the writer needs the answer the reader had.
  priv mut scripting : Bool
  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,
  scripting~ : Bool = false,
) -> Document {
  let root = Element::new("#document")
  let doc : Document = {
    root,
    selectors: Map([]),
    flavour,
    scripting,
    repairs: Repairs::new(),
    nesting: [],
    unresolved: [],
  }
  root.home = Some(doc)
  doc.seed_selectors()
  doc
}

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