///|
/// One attribute on an open-start element.
pub(all) struct BinAttr {
  name : String
  value : BinNode
} derive(Debug)

///|
/// Procedural BINXML node. Close tokens are consumed and not stored.
pub(all) enum BinNode {
  EndOfStream
  StreamStart
  OpenStart(String, Array[BinAttr], Array[BinNode])
  ValueText(String)
  CData(String)
  CharRef(Int)
  EntityRef(String)
  PITarget(String)
  PIData(String)
  NormalSub(Int, Int)
  OptionalSub(Int, Int)
} derive(Debug)

///|
/// Substitution cell. Nested BINXML lives in `nested` when type is BXml.
pub(all) struct SubCell {
  type_code : Int
  payload : Bytes
  nested : Array[ParsedRoot]
} derive(Debug)

///|
/// Root BINXML: template body plus substitution table.
pub(all) struct ParsedRoot {
  template : Array[BinNode]
  substitutions : Array[SubCell]
} derive(Debug)

///|
fn peek_byte(data : Bytes, pos : Int) -> Result[Int, EvtxError] {
  let _ = match need(data, pos, 1, "token") {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok(data[pos].to_int())
}

///|
fn load_name(
  data : Bytes,
  chunk_offset : Int,
  string_offset : Int,
  names : Array[(Int, String)],
) -> Result[String, EvtxError] {
  for i = 0; i < names.length(); i = i + 1 {
    let (off, name) = names[i]
    if off == string_offset {
      return Ok(name)
    }
  }
  let abs = chunk_offset + string_offset
  let (next_off, p1) = match read_u32(data, abs) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (hash, p2) = match read_u16(data, p1) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (nchars, p3) = match read_u16(data, p2) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let name = match read_utf16_units(data, p3, nchars) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if next_off == 0xFFFFFFFF && hash == -2 {
    return Err(InvalidName("impossible name header"))
  }
  names.push((string_offset, name))
  Ok(name)
}

///|
fn parse_attribute(
  data : Bytes,
  pos : Int,
  chunk_offset : Int,
  names : Array[(Int, String)],
  style : TimeStyle,
) -> Result[(BinAttr, Int), EvtxError] {
  let token = match peek_byte(data, pos) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if (token & 0x0F) != 0x06 {
    return Err(UnexpectedToken(token, pos))
  }
  let (string_offset, cur) = match read_u32(data, pos + 1) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let rel = pos - chunk_offset
  let name = match
    load_name(data, chunk_offset, string_offset.reinterpret_as_int(), names) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let mut body = cur
  if string_offset.reinterpret_as_int() > rel {
    let abs = chunk_offset + string_offset.reinterpret_as_int()
    let (nchars_hdr, _) = match read_u16(data, abs + 6) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    body = abs + nchars_hdr * 2 + 10
  }
  let (value, next) = match parse_node(data, body, chunk_offset, names, style) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok((BinAttr::{ name, value, }, next))
}

///|
fn parse_value_node(
  data : Bytes,
  pos : Int,
  style : TimeStyle,
) -> Result[(BinNode, Int), EvtxError] {
  let (type_code, cur) = match read_u8(data, pos + 1) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (text, used) = match format_variant(data, cur, type_code, None, style) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok((ValueText(text), cur + used))
}

///|
fn parse_open_start(
  data : Bytes,
  pos : Int,
  chunk_offset : Int,
  names : Array[(Int, String)],
  style : TimeStyle,
) -> Result[(BinNode, Int), EvtxError] {
  let token = match peek_byte(data, pos) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (_unknown, p1) = match read_u16(data, pos + 1) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (size, p2) = match read_u32(data, p1) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (string_offset, p3) = match read_u32(data, p2) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let mut cur = p3
  if (token & 0x40) != 0 {
    let (_extra, next) = match read_u32(data, cur) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    cur = next
  }
  let rel = pos - chunk_offset
  let name = match
    load_name(data, chunk_offset, string_offset.reinterpret_as_int(), names) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if string_offset.reinterpret_as_int() > rel {
    let abs = chunk_offset + string_offset.reinterpret_as_int()
    let (nchars, _) = match read_u16(data, abs + 6) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    cur = abs + nchars * 2 + 10
  }
  if size == 0U && name.length() == 0 {
    return Err(InvalidName("empty open-start"))
  }
  let attrs : Array[BinAttr] = []
  let children : Array[BinNode] = []
  let mut done = false
  while !done {
    let tok = match peek_byte(data, cur) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let code = tok & 0x0F
    if code == 0x02 {
      cur = cur + 1
    } else if code == 0x03 || code == 0x04 {
      cur = cur + 1
      done = true
    } else if code == 0x06 {
      let (attr, next) = match
        parse_attribute(data, cur, chunk_offset, names, style) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      attrs.push(attr)
      cur = next
    } else {
      let (child, next) = match
        parse_node(data, cur, chunk_offset, names, style) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      children.push(child)
      cur = next
    }
  }
  Ok((OpenStart(name, attrs, children), cur))
}

///|
fn parse_node(
  data : Bytes,
  pos : Int,
  chunk_offset : Int,
  names : Array[(Int, String)],
  style : TimeStyle,
) -> Result[(BinNode, Int), EvtxError] {
  let token = match peek_byte(data, pos) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let code = token & 0x0F
  match code {
    0x00 => Ok((EndOfStream, pos + 1))
    0x01 => parse_open_start(data, pos, chunk_offset, names, style)
    0x02 => Ok((EndOfStream, pos + 1))
    0x03 => Ok((EndOfStream, pos + 1))
    0x04 => Ok((EndOfStream, pos + 1))
    0x05 => parse_value_node(data, pos, style)
    0x07 => {
      let (slen, cur) = match read_u16(data, pos + 1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let nchars = slen - 2
      let text = match read_utf16_units(data, cur, nchars) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok((CData(text), cur + nchars * 2))
    }
    0x08 => {
      let (entity, next) = match read_u16(data, pos + 1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok((CharRef(entity), next))
    }
    0x09 => {
      let (string_offset, cur) = match read_u32(data, pos + 1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let name = match
        load_name(data, chunk_offset, string_offset.reinterpret_as_int(), names) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let mut next = cur
      if string_offset.reinterpret_as_int() > pos - chunk_offset {
        let abs = chunk_offset + string_offset.reinterpret_as_int()
        let (nchars, _) = match read_u16(data, abs + 6) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        next = abs + nchars * 2 + 10
      }
      Ok((EntityRef("&" + name + ";"), next))
    }
    0x0A => {
      let (string_offset, cur) = match read_u32(data, pos + 1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let name = match
        load_name(data, chunk_offset, string_offset.reinterpret_as_int(), names) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let mut next = cur
      if string_offset.reinterpret_as_int() > pos - chunk_offset {
        let abs = chunk_offset + string_offset.reinterpret_as_int()
        let (nchars, _) = match read_u16(data, abs + 6) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        next = abs + nchars * 2 + 10
      }
      Ok((PITarget(name), next))
    }
    0x0B => {
      let (nchars, cur) = match read_u16(data, pos + 1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let text = if nchars == 0 {
        ""
      } else {
        match read_utf16_units(data, cur, nchars) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
      }
      Ok((PIData(text), cur + nchars * 2))
    }
    0x0D => {
      let (index, p1) = match read_u16(data, pos + 1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let (typ, next) = match read_u8(data, p1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok((NormalSub(index, typ), next))
    }
    0x0E => {
      let (index, p1) = match read_u16(data, pos + 1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let (typ, next) = match read_u8(data, p1) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok((OptionalSub(index, typ), next))
    }
    0x0F => {
      let _ = match need(data, pos, 4, "stream-start") {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok((StreamStart, pos + 4))
    }
    _ => Err(UnexpectedToken(token, pos))
  }
}

///|
fn parse_node_list(
  data : Bytes,
  pos : Int,
  chunk_offset : Int,
  names : Array[(Int, String)],
  style : TimeStyle,
  end_abs : Int,
) -> Result[(Array[BinNode], Int), EvtxError] {
  let nodes : Array[BinNode] = []
  let mut cur = pos
  let mut stop = false
  while !stop {
    if cur >= end_abs {
      stop = true
    } else {
      let (node, next) = match
        parse_node(data, cur, chunk_offset, names, style) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      nodes.push(node)
      cur = next
      match node {
        EndOfStream => stop = true
        _ => ()
      }
    }
  }
  Ok((nodes, cur))
}

///|
fn parse_substitutions(
  data : Bytes,
  pos : Int,
  chunk_offset : Int,
  names : Array[(Int, String)],
  style : TimeStyle,
) -> Result[(Array[SubCell], Int), EvtxError] {
  let (count_u, cur0) = match read_u32(data, pos) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let count = count_u.reinterpret_as_int()
  if count < 0 || count > 4096 {
    return Err(InvalidSubstitution("substitution count \{count}"))
  }
  let sizes : Array[Int] = []
  let types : Array[Int] = []
  let mut cur = cur0
  for i = 0; i < count; i = i + 1 {
    let (size, p1) = match read_u16(data, cur) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let (typ, p2) = match read_u8(data, p1) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let (_pad, p3) = match read_u8(data, p2) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    sizes.push(size)
    types.push(typ)
    cur = p3
  }
  let cells : Array[SubCell] = []
  for i = 0; i < count; i = i + 1 {
    let size = sizes[i]
    let typ = types[i]
    let _ = match need(data, cur, size, "substitution") {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let payload = bytes_slice(data, cur, cur + size)
    let nested : Array[ParsedRoot] = []
    if (typ & 0x7F) == 0x21 {
      let (root, _) = match parse_root(data, cur, chunk_offset, names, style) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      nested.push(root)
    }
    cells.push(SubCell::{ type_code: typ, payload, nested, })
    cur = cur + size
  }
  Ok((cells, cur))
}

///|
fn parse_template_body(
  data : Bytes,
  pos : Int,
  chunk_offset : Int,
  names : Array[(Int, String)],
  style : TimeStyle,
) -> Result[(Array[BinNode], UInt, Int), EvtxError] {
  let (_next_off, _) = match read_u32(data, pos) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (template_id, _) = match read_u32(data, pos + 4) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let _ = match need(data, pos, 0x18, "template") {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (data_length, _) = match read_u32(data, pos + 0x14) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let body = pos + 0x18
  let end_abs = body + data_length.reinterpret_as_int()
  let (nodes, cur) = match
    parse_node_list(data, body, chunk_offset, names, style, end_abs) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if cur > end_abs {
    return Err(InvalidRecord("template body overruns data_length"))
  }
  Ok((nodes, template_id, pos + 0x18 + data_length.reinterpret_as_int()))
}

///|
fn parse_root(
  data : Bytes,
  pos : Int,
  chunk_offset : Int,
  names : Array[(Int, String)],
  style : TimeStyle,
) -> Result[(ParsedRoot, Int), EvtxError] {
  let mut cur = pos
  let first = match peek_byte(data, pos) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if (first & 0x0F) == 0x0F {
    cur = pos + 4
  }
  let tok = match peek_byte(data, cur) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if (tok & 0x0F) == 0x0C {
    let (_unknown0, p1) = match read_u8(data, cur + 1) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let (_tid, p2) = match read_u32(data, p1) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let (template_offset, p3) = match read_u32(data, p2) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let rel = cur - chunk_offset
    let mut after_inst = p3
    let tpl_abs = chunk_offset + template_offset.reinterpret_as_int()
    let (nodes, _tid2, tpl_end) = match
      parse_template_body(data, tpl_abs, chunk_offset, names, style) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    if template_offset.reinterpret_as_int() > rel {
      after_inst = tpl_end
    }
    let (subs, end) = match
      parse_substitutions(data, after_inst, chunk_offset, names, style) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    Ok((ParsedRoot::{ template: nodes, substitutions: subs, }, end))
  } else {
    let (nodes, mid) = match
      parse_node_list(data, pos, chunk_offset, names, style, data.length()) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    if mid + 4 <= data.length() {
      let (subs, end) = match
        parse_substitutions(data, mid, chunk_offset, names, style) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok((ParsedRoot::{ template: nodes, substitutions: subs, }, end))
    } else {
      Ok((ParsedRoot::{ template: nodes, substitutions: [], }, mid))
    }
  }
}

///|
fn sub_text(cell : SubCell, style : TimeStyle) -> Result[String, EvtxError] {
  if cell.nested.length() > 0 {
    render_root(cell.nested[0], style)
  } else {
    let (text, _) = match
      format_variant(
        cell.payload,
        0,
        cell.type_code,
        Some(cell.payload.length()),
        style,
      ) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    Ok(text)
  }
}

///|
pub extend BinAttr with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend BinNode with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend SubCell with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend ParsedRoot with @moonbitlang/core/debug.Debug::{to_repr}