///|
/// 元素构建中帧(栈顶 = 当前接受子节点的元素;End 弹帧落树)。
priv struct ElementFrame {
  name : XmlName
  attributes : Array[Attribute]
  children : Array[Node]
}

///|
/// 相邻 Text 事件拼接:底座按实体边界切分多段,消费方须自行拼(契约一)。
/// 段列表惰性合并——单段零拷贝直通,多段用一次 StringBuilder 合并
/// (避免逐段 `previous + content` 的 O(n²) 重拼;结构事件/EOF 时 flush)。
fn flush_pending_text(pending : Array[String], target : Array[Node]) -> Unit {
  if pending.length() == 1 {
    target.push(Text(pending[0]))
  } else if pending.length() > 1 {
    let joined = StringBuilder()
    for chunk in pending {
      joined.write_string(chunk)
    }
    target.push(Text(joined.to_string()))
  }
  pending.clear()
}

///|
/// 当前文本落点(栈顶子节点,根前序,根后序——与折叠臂同一选路)。
fn active_text_nodes(
  stack : Array[ElementFrame],
  prologue : Array[Node],
  root_slot : Array[Element],
  epilogue : Array[Node],
) -> Array[Node] {
  if stack.length() > 0 {
    stack[stack.length() - 1].children
  } else if root_slot.length() == 0 {
    prologue
  } else {
    epilogue
  }
}

///|
/// 适配层结构错(防御纵深:底座已保 WF,此面正常解析不可达)。
fn structure_error(base_span : @xml.SourceSpan, message : String) -> XmlError {
  XmlError::At(
    error=XmlErrorKind::Structure(message),
    span=translate_span(base_span),
  )
}

///|
fn translate_name(base_name : @xml.XmlName) -> XmlName {
  {
    qualified_name: base_name.qualified_name,
    prefix: base_name.prefix,
    local_name: base_name.local_name,
    namespace_uri: base_name.namespace_uri,
  }
}

///|
/// xmlns 前缀声明("xmlns:p")→ 属性名。
fn xmlns_prefixed(prefix : String) -> XmlName {
  {
    qualified_name: "xmlns:" + prefix,
    prefix: Some("xmlns"),
    local_name: prefix,
    namespace_uri: None,
  }
}

///|
/// xmlns 缺省声明("xmlns")→ 属性名(无前缀)。
fn xmlns_default() -> XmlName {
  {
    qualified_name: "xmlns",
    prefix: None,
    local_name: "xmlns",
    namespace_uri: None,
  }
}

///|
fn xmlns_name(prefix : String?) -> XmlName {
  match prefix {
    Some(prefix) => xmlns_prefixed(prefix)
    None => xmlns_default()
  }
}

///|
/// 底座元素属性面 → 树属性:普通属性按序翻译,xmlns 声明并回队尾
/// (底座把 xmlns 与普通属性分离;并回后序列化统一按属性发,再解析等价)。
fn build_attributes(base_element : @xml.NamespaceElement) -> Array[Attribute] {
  let attributes : Array[Attribute] = []
  for base_attribute in base_element.attributes {
    attributes.push({
      name: translate_name(base_attribute.name),
      value: base_attribute.value,
    })
  }
  for base_declaration in base_element.namespace_declarations {
    attributes.push({
      name: xmlns_name(base_declaration.prefix),
      value: base_declaration.namespace_uri,
    })
  }
  attributes
}

///|
/// 元素落位:栈非空挂栈顶,否则占根槽(恰一;再来的元素 = 结构错)。
/// 根槽用单格数组表达(0/1 长度),避免 mut 局部跨助手传递。
fn attach_element(
  element : Element,
  stack : Array[ElementFrame],
  root_slot : Array[Element],
  base_span : @xml.SourceSpan,
) -> Unit raise XmlError {
  if stack.length() > 0 {
    stack[stack.length() - 1].children.push(Node::Element(element))
  } else if root_slot.length() == 0 {
    root_slot.push(element)
  } else {
    raise structure_error(base_span, "unexpected element after root")
  }
}

///|
/// 事件折叠 → 文档树。阶段由根槽/栈深推导:栈深>0 = 根内;
/// 根槽空 = 根前;根槽有根且栈空 = 根后。
fn parse_into(source : String) -> Document raise XmlError {
  let reader = @xml.NamespaceReader::from_string(source)
  let mut declaration : XmlDecl? = None
  let mut doctype : String? = None
  let prologue : Array[Node] = []
  let epilogue : Array[Node] = []
  let root_slot : Array[Element] = []
  let stack : Array[ElementFrame] = []
  let pending_text : Array[String] = []
  for ;; {
    let event = reader.read_event() catch {
      base_error => raise translate_error(base_error)
    }
    if !(event.kind is @xml.Text(_)) {
      flush_pending_text(
        pending_text,
        active_text_nodes(stack, prologue, root_slot, epilogue),
      )
    }
    match event.kind {
      @xml.Decl(version~, encoding~, standalone~) => {
        if declaration is Some(_) {
          raise structure_error(event.span, "duplicate XML declaration")
        }
        declaration = Some({ version, encoding, standalone, })
      }
      @xml.DocType(name) => {
        if doctype is Some(_) {
          raise structure_error(event.span, "duplicate DOCTYPE")
        }
        doctype = Some(name)
      }
      @xml.Comment(content) =>
        if stack.length() > 0 {
          stack[stack.length() - 1].children.push(Node::Comment(content))
        } else if root_slot.length() == 0 {
          prologue.push(Node::Comment(content))
        } else {
          epilogue.push(Node::Comment(content))
        }
      @xml.PI(target~, data~) =>
        if stack.length() > 0 {
          stack[stack.length() - 1].children.push(Node::PI(target~, data~))
        } else if root_slot.length() == 0 {
          prologue.push(Node::PI(target~, data~))
        } else {
          epilogue.push(Node::PI(target~, data~))
        }
      @xml.Text(content) => pending_text.push(content)
      @xml.CData(content) =>
        if stack.length() > 0 {
          stack[stack.length() - 1].children.push(Node::CData(content))
        } else if root_slot.length() == 0 {
          prologue.push(Node::CData(content))
        } else {
          epilogue.push(Node::CData(content))
        }
      @xml.Start(base_element) => {
        if root_slot.length() > 0 && stack.length() == 0 {
          raise structure_error(event.span, "unexpected element after root")
        }
        stack.push({
          name: translate_name(base_element.name),
          attributes: build_attributes(base_element),
          children: [],
        })
      }
      @xml.Empty(base_element) => {
        let element : Element = {
          name: translate_name(base_element.name),
          attributes: build_attributes(base_element),
          children: [],
        }
        attach_element(element, stack, root_slot, event.span)
      }
      @xml.End(_) =>
        match stack.pop() {
          Some(frame) => {
            let element : Element = {
              name: frame.name,
              attributes: frame.attributes,
              children: frame.children,
            }
            attach_element(element, stack, root_slot, event.span)
          }
          None =>
            raise structure_error(event.span, "end tag without matching start")
        }
      @xml.Eof => break
    }
  }
  if root_slot.length() == 0 {
    raise XmlError::At(
      error=XmlErrorKind::Structure("document has no root element"),
      span={
        start: { offset: 0, line: 1, column: 1, },
        end: { offset: 0, line: 1, column: 1, },
      },
    )
  }
  { decl: declaration, doctype, prologue, root: root_slot[0], epilogue, }
}

///|
/// 非 NS 面折叠:raw Reader 事件 → 文档树;名字按原文直通
/// (qualified_name = 原文,prefix/local_name/namespace_uri = None——非 NS 面
/// 没有命名空间语义可记);xmlns 即普通属性,保持原位。
/// 事件序/根槽/拼接与 NS 面共用同一套助手(同一良构层语义)。
fn parse_into_ns_off(source : String) -> Document raise XmlError {
  let reader = @xml.Reader::from_string(source)
  let mut declaration : XmlDecl? = None
  let mut doctype : String? = None
  let prologue : Array[Node] = []
  let epilogue : Array[Node] = []
  let root_slot : Array[Element] = []
  let stack : Array[ElementFrame] = []
  let pending_text : Array[String] = []
  for ;; {
    let event = reader.read_event() catch {
      base_error => raise translate_error(base_error)
    }
    if !(event.kind is @xml.Text(_)) {
      flush_pending_text(
        pending_text,
        active_text_nodes(stack, prologue, root_slot, epilogue),
      )
    }
    match event.kind {
      @xml.Decl(version~, encoding~, standalone~) => {
        if declaration is Some(_) {
          raise structure_error(event.span, "duplicate XML declaration")
        }
        declaration = Some({ version, encoding, standalone, })
      }
      @xml.DocType(name) => {
        if doctype is Some(_) {
          raise structure_error(event.span, "duplicate DOCTYPE")
        }
        doctype = Some(name)
      }
      @xml.Comment(content) =>
        if stack.length() > 0 {
          stack[stack.length() - 1].children.push(Node::Comment(content))
        } else if root_slot.length() == 0 {
          prologue.push(Node::Comment(content))
        } else {
          epilogue.push(Node::Comment(content))
        }
      @xml.PI(target~, data~) =>
        if stack.length() > 0 {
          stack[stack.length() - 1].children.push(Node::PI(target~, data~))
        } else if root_slot.length() == 0 {
          prologue.push(Node::PI(target~, data~))
        } else {
          epilogue.push(Node::PI(target~, data~))
        }
      @xml.Text(content) => pending_text.push(content)
      @xml.CData(content) =>
        if stack.length() > 0 {
          stack[stack.length() - 1].children.push(Node::CData(content))
        } else if root_slot.length() == 0 {
          prologue.push(Node::CData(content))
        } else {
          epilogue.push(Node::CData(content))
        }
      @xml.Start(base_element) => {
        if root_slot.length() > 0 && stack.length() == 0 {
          raise structure_error(event.span, "unexpected element after root")
        }
        stack.push({
          name: raw_name(base_element.name),
          attributes: raw_attributes(base_element.attributes),
          children: [],
        })
      }
      @xml.Empty(base_element) => {
        let element : Element = {
          name: raw_name(base_element.name),
          attributes: raw_attributes(base_element.attributes),
          children: [],
        }
        attach_element(element, stack, root_slot, event.span)
      }
      @xml.End(_) =>
        match stack.pop() {
          Some(frame) => {
            let element : Element = {
              name: frame.name,
              attributes: frame.attributes,
              children: frame.children,
            }
            attach_element(element, stack, root_slot, event.span)
          }
          None =>
            raise structure_error(event.span, "end tag without matching start")
        }
      @xml.Eof => break
    }
  }
  if root_slot.length() == 0 {
    raise XmlError::At(
      error=XmlErrorKind::Structure("document has no root element"),
      span={
        start: { offset: 0, line: 1, column: 1, },
        end: { offset: 0, line: 1, column: 1, },
      },
    )
  }
  { decl: declaration, doctype, prologue, root: root_slot[0], epilogue, }
}

///|
/// 非 NS 面名字原文直通(无命名空间语义可记,三字段皆 None 形)。
fn raw_name(qualified_name : String) -> XmlName {
  {
    qualified_name,
    prefix: None,
    local_name: qualified_name,
    namespace_uri: None,
  }
}

///|
/// 非 NS 面属性直通(保持原序,xmlns 不另眼)。
fn raw_attributes(
  base_attributes : Array[@xml.XmlAttribute],
) -> Array[Attribute] {
  let attributes : Array[Attribute] = []
  for base_attribute in base_attributes {
    attributes.push({
      name: raw_name(base_attribute.name),
      value: base_attribute.value,
    })
  }
  attributes
}

///|
/// 解析入口:源文本 → 文档树(命名空间已解析,实体已展开;
/// 错误面 = [XmlError],位置透传)。缺省 = XML 1.0 五版面(Edition5,
/// 现行底座行为);版次敏感消费方用 [parse_edition]。
pub fn parse(source : String) -> Result[Document, XmlError] {
  parse_edition(source, edition=Edition5, ns=On)
}

///|
/// 命名空间模式:XML 1.0 基础规范与 Namespaces 是两个规范面——非 NS 消费
/// (RDF/XML 旧系、嵌入式)合法地接受首/尾冒号名、组合字符衔接段等 NCName
/// 规则外的名字。
/// · On(缺省):命名空间感知——前缀解析 / xmlns 收集 / NCName 规则,由底座
///   NamespaceReader 承担;
/// · Off:非 NS 面——名字按 Name 原文直通,xmlns 即普通属性,不解析前缀,
///   由底座 raw Reader 承担。
/// 施工账:ctx.md §8(役N1,R-N1a)。
pub(all) enum NsMode {
  On
  Off
} derive(Eq)

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

///|
/// 按版本面解析:树建后走名字段门。version="1.1" 文档恒走 1.1 紧凑表,
/// 不受版本面选择影响;版本面只区分 1.0 文档。
/// 施工账:ctx.md §6(役E1,R-E1c)。
pub fn parse_edition(
  source : String,
  edition~ : XmlEdition,
  ns~ : NsMode = On,
  resolver~ : &EntityResolver? = None,
) -> Result[Document, XmlError] {
  Ok(
    // 外挂面预组合:None = 缺省拒绝取件(原文直通,零漂移);
    // Some(face) = 取件/装载/预展开,错误重定位账本随行。
    {
      let (effective, patches) = match resolver {
        None => (source, [])
        Some(face) => prepare_external(source, face, edition)
      }
      let document = try {
        let document = match ns {
          On => parse_into(effective)
          Off => parse_into_ns_off(effective)
        }
        validate_document_names(document, edition)
        document
      } catch {
        base_error => raise relocate_error(base_error, patches, source)
      }
      document
    },
  ) catch {
    error => Err(error)
  }
}