// F0 — the lexical `w:rPr` engine and direct-property oracle for
// span-addressed formatting.
//
// Everything here operates on ONE run's raw byte slice, purely
// lexically: existing spellings are preserved byte-for-byte, edits are
// the smallest spans that state the requested absolute values, absent
// children are inserted at their CT_RPr schema positions without
// moving anything, and every ambiguity — duplicate `rPr`, duplicate
// targeted properties, an in-flight `rPrChange`, out-of-order ranked
// children, a namespace binding this walker cannot vouch for — is a
// typed refusal, never a normalization.

///|
/// The v1 absolute-set request: None = untouched, Some(false) = an
/// EXPLICIT off override (never "clear to inherited" — that waits for
/// effective-value provenance). Color is a canonical six-digit
/// uppercase RGB value; bold and italic couple their complex-script
/// twins (`w:bCs`/`w:iCs`) as an office.mbt guarantee. Fields are
/// private: the ONLY constructor is `docx_direct_format`, so an
/// unvalidated color can never reach a planner.
pub struct DocxDirectFormat {
  priv bold : Bool?
  priv italic : Bool?
  priv underline : Bool?
  priv color : String?
} derive(Debug, Eq)

///|
/// The requested bold state: `Some(true)`/`Some(false)` forces it,
/// `None` leaves it untouched.
pub fn DocxDirectFormat::bold(self : DocxDirectFormat) -> Bool? {
  self.bold
}

///|
/// The requested italic state: `Some(true)`/`Some(false)` forces it,
/// `None` leaves it untouched.
pub fn DocxDirectFormat::italic(self : DocxDirectFormat) -> Bool? {
  self.italic
}

///|
/// The requested underline state: `Some(true)`/`Some(false)` forces it,
/// `None` leaves it untouched.
pub fn DocxDirectFormat::underline(self : DocxDirectFormat) -> Bool? {
  self.underline
}

///|
/// The requested color as canonical uppercase `RRGGBB`, or `None` when
/// no color was requested.
pub fn DocxDirectFormat::color(self : DocxDirectFormat) -> String? {
  self.color
}

///|
/// Validate and canonicalize a direct-format request. Color accepts
/// `RRGGBB` or `#RRGGBB` (either case) and canonicalizes to uppercase.
/// A request that touches nothing is refused — a formatting command
/// must state at least one property.
pub fn docx_direct_format(
  bold? : Bool,
  italic? : Bool,
  underline? : Bool,
  color? : String,
) -> DocxDirectFormat raise DocxError {
  let canonical_color : String? = match color {
    Some(raw) => {
      let hex = if raw.has_prefix("#") { raw[1:].to_owned() } else { raw }
      guard hex.length() == 6 else {
        raise Unsupported(
          message="format color is a six-digit RGB value (RRGGBB or #RRGGBB)",
        )
      }
      let builder = StringBuilder(size_hint=6)
      for ch in hex {
        let upper = if ch is ('a'..='f') {
          (ch.to_int() - 32).unsafe_to_char()
        } else {
          ch
        }
        guard upper is ('0'..='9' | 'A'..='F') else {
          raise Unsupported(
            message="format color is a six-digit RGB value (RRGGBB or #RRGGBB)",
          )
        }
        builder.write_char(upper)
      }
      Some(builder.to_string())
    }
    None => None
  }
  guard bold is Some(_) ||
    italic is Some(_) ||
    underline is Some(_) ||
    canonical_color is Some(_) else {
    raise Unsupported(
      message="a format request states at least one property (bold, italic, underline, color)",
    )
  }
  { bold, italic, underline, color: canonical_color, }
}

///|
/// One slice-relative byte edit: replace [start, end) with the
/// replacement text (encoded as UTF-8 by the consumer).
pub struct RunFormatEdit {
  priv start : Int
  priv end : Int
  priv replacement : String
}

///|
/// The byte offset where the edit's replacement begins (slice-relative).
pub fn RunFormatEdit::start(self : RunFormatEdit) -> Int {
  self.start
}

///|
/// The byte offset just past the text the edit replaces (slice-relative).
pub fn RunFormatEdit::end(self : RunFormatEdit) -> Int {
  self.end
}

///|
/// The replacement text (encoded as UTF-8 by the consumer).
pub fn RunFormatEdit::replacement(self : RunFormatEdit) -> String {
  self.replacement
}

///|
/// The planned edits for one run: slice-relative, non-overlapping, in
/// ascending order (stable — equal-offset insertions keep their
/// schema-rank order). `changed=false` means every requested property
/// already holds explicitly — nothing is rewritten.
pub struct RunFormatPlan {
  priv edits : Array[RunFormatEdit]
  priv changed : Bool
}

///|
/// A defensive copy — the plan's sorted/non-overlapping invariant
/// cannot be mutated away by a caller.
pub fn RunFormatPlan::edits(self : RunFormatPlan) -> Array[RunFormatEdit] {
  self.edits.copy()
}

///|
/// Whether the plan rewrites anything: `false` means every requested
/// property already holds explicitly.
pub fn RunFormatPlan::changed(self : RunFormatPlan) -> Bool {
  self.changed
}

// ---------------------------------------------------------------------------
// Names under a caller-vouched binding
// ---------------------------------------------------------------------------

///|
/// The caller-vouched namespace binding this lexical engine edits
/// under. XML default namespaces apply to ELEMENTS only, so `element`
/// may be "" (a default-namespace document spells ``, ``), but
/// an unprefixed attribute has NO namespace — WML's schema qualifies
/// its attributes, so `attribute` is never "". `extensions` lists the
/// prefixes the caller warrants are bound to recognized rPr extension
/// namespaces (w14 and friends) — and NOT to WML or to
/// markup-compatibility.
priv struct WmlBinding {
  element : String
  attribute : String
  extensions : Array[String]
}

///|
fn make_binding(
  prefix : String,
  attr_prefix : String?,
  extension_prefixes : Array[String]?,
) -> WmlBinding raise DocxError {
  if prefix != "" {
    validate_ns_prefix(prefix)
  }
  let attribute = attr_prefix.unwrap_or(prefix)
  guard attribute != "" else {
    raise Unsupported(
      message="run formatting requires an attribute prefix: XML default namespaces do not apply to attributes, so WML attributes are always qualified",
    )
  }
  validate_ns_prefix(attribute)
  // `xml` and `xmlns` are bound by the XML specs themselves — they can
  // never be the WML binding.
  guard prefix != "xmlns" &&
    prefix != "xml" &&
    attribute != "xmlns" &&
    attribute != "xml" else {
    raise Unsupported(
      message="run formatting refuses a reserved namespace prefix",
    )
  }
  let extensions = extension_prefixes.unwrap_or([])
  for entry in extensions {
    guard entry != "" &&
      entry != prefix &&
      entry != attribute &&
      entry != "mc" &&
      entry != "xml" &&
      entry != "xmlns" else {
      raise Unsupported(
        message="run formatting refuses the extension prefix \{entry}: extension prefixes must be distinct from the WML and reserved bindings",
      )
    }
    validate_ns_prefix(entry)
  }
  { element: prefix, attribute, extensions, }
}

///|
/// A namespace prefix must be an NCName — in this engine's ASCII
/// subset: a letter or underscore, then letters, digits, '_', '-',
/// '.'. NO colon: `x:y` as a "prefix" would spell malformed names
/// like `x:y:val`.
fn validate_ns_prefix(prefix : String) -> Unit raise DocxError {
  validate_ncname_part(prefix) catch {
    _ =>
      raise Unsupported(
        message="run formatting requires namespace prefixes to be NCNames",
      )
  }
}

///|
/// The qualified spelling of a WML ELEMENT under the binding:
/// `element=""` is the default-namespace document (``, ``),
/// any other prefix spells `prefix:local`.
fn wml_name(binding : WmlBinding, local_name : String) -> String {
  if binding.element == "" {
    local_name
  } else {
    "\{binding.element}:\{local_name}"
  }
}

///|
/// Split a child's qname into (prefix, local) — prefix "" when the
/// name carries no colon.
fn split_qname(name : String) -> (String, String) {
  let units = name.code_units()
  for index in 0.. Bool {
  byte is (b' ' | b'\t' | b'\n' | b'\r')
}

///|
priv struct FormatAttribute {
  // as spelled, whitespace-trimmed
  name : String
  value : String
  // the FULL attribute span: first byte of the name through the
  // closing quote (inclusive end is exclusive here)
  name_start : Int
  attr_end : Int
  value_start : Int
  value_end : Int
}

///|
priv struct RunChildTag {
  name : String
  byte_start : Int
  byte_end : Int
  self_closing : Bool
  open_end : Int
  // Where the close tag STARTS (equals byte_end for a self-closing
  // tag) — recorded, never reconstructed, because `` is
  // legal XML and longer than the canonical spelling.
  close_start : Int
  attributes : Array[FormatAttribute]
  // Direct element children found inside (non-leaf detection).
  has_element_children : Bool
}

///|
/// Scan one element's DIRECT children lexically. Children with
/// subtrees are consumed whole with close-NAME matching; only XML
/// whitespace may appear between children; comments, PIs, CDATA, and
/// any xmlns declaration in the region are refusals — a binding this
/// walker cannot vouch for must not be edited around.
fn scan_direct_children(
  bytes : BytesView,
  content_start : Int,
  content_end : Int,
) -> Array[RunChildTag] raise DocxError {
  let children : Array[RunChildTag] = []
  let mut at = content_start
  while at < content_end {
    let byte = bytes[at]
    if is_xml_ws(byte) {
      at += 1
      continue
    }
    guard byte == b'<' else {
      raise Unsupported(
        message="run formatting refuses non-whitespace character data between properties",
      )
    }
    guard at + 1 < content_end else {
      raise Unsupported(message="run formatting found a truncated tag")
    }
    if bytes[at + 1] == b'!' || bytes[at + 1] == b'?' {
      raise Unsupported(
        message="run formatting does not edit regions containing comments, processing instructions, or CDATA",
      )
    }
    guard bytes[at + 1] != b'/' else {
      raise Unsupported(message="run formatting found an unmatched close tag")
    }
    let (name, attributes, self_closing, open_end) = scan_format_tag(
      bytes, at, content_end,
    )
    let mut byte_end = open_end
    let mut close_start = open_end
    let mut has_element_children = false
    if !self_closing {
      let close_at = consume_subtree(bytes, name, open_end, content_end)
      // Anything other than pure whitespace between the open tag and
      // the close tag means element or text content.
      let mut probe = open_end
      while probe < close_at.0 {
        if !is_xml_ws(bytes[probe]) {
          has_element_children = true
          break
        }
        probe += 1
      }
      close_start = close_at.0
      byte_end = close_at.1
    }
    children.push({
      name,
      byte_start: at,
      byte_end,
      self_closing,
      open_end,
      close_start,
      attributes,
      has_element_children,
    })
    at = byte_end
  }
  children
}

///|
/// Consume a subtree opened by `name` starting just past its open tag;
/// returns (close_tag_start, one-past-close-'>'). Close tags must
/// MATCH their open names — a mismatched pair is a refusal, never a
/// guess.
fn consume_subtree(
  bytes : BytesView,
  name : String,
  open_end : Int,
  limit : Int,
) -> (Int, Int) raise DocxError {
  let stack : Array[String] = [name]
  let mut at = open_end
  while at < limit && stack.length() > 0 {
    if bytes[at] != b'<' {
      at += 1
      continue
    }
    guard at + 1 < limit else {
      raise Unsupported(message="run formatting found a truncated tag")
    }
    if bytes[at + 1] == b'!' || bytes[at + 1] == b'?' {
      raise Unsupported(
        message="run formatting does not edit regions containing comments, processing instructions, or CDATA",
      )
    }
    if bytes[at + 1] == b'/' {
      // XML permits whitespace AFTER the end-tag name, never before it:
      // the name starts immediately at "' && !is_xml_ws(bytes[cursor]) {
        cursor += 1
      }
      let close_name = ascii_slice(bytes, name_start, cursor)
      validate_xml_name(close_name)
      let mut probe = cursor
      while probe < limit && is_xml_ws(bytes[probe]) {
        probe += 1
      }
      guard probe < limit && bytes[probe] == b'>' else {
        raise Unsupported(message="run formatting found a malformed close tag")
      }
      guard stack.length() > 0 && stack[stack.length() - 1] == close_name else {
        raise Unsupported(
          message="run formatting found a close tag that does not match its element",
        )
      }
      ignore(stack.pop())
      at = probe + 1
      if stack.length() == 0 {
        return (close_start, at)
      }
    } else {
      let (inner_name, _, inner_self_closing, inner_open_end) = scan_format_tag(
        bytes, at, limit,
      )
      if !inner_self_closing {
        stack.push(inner_name)
      }
      at = inner_open_end
    }
  }
  raise Unsupported(message="run formatting found an unclosed element")
}

///|
/// Scan one open tag: (qname, attributes with FULL spans, self_closing,
/// one-past-'>'). Whitespace around `=` is valid XML and accepted; an
/// xmlns declaration anywhere in the tag is a refusal (a local
/// rebinding would silently change what every name in this slice
/// means).
fn scan_format_tag(
  bytes : BytesView,
  start : Int,
  limit : Int,
) -> (String, Array[FormatAttribute], Bool, Int) raise DocxError {
  let mut at = start + 1
  let name_start = at
  while at < limit &&
        bytes[at] != b'>' &&
        bytes[at] != b'/' &&
        !is_xml_ws(bytes[at]) {
    guard bytes[at] < b'\x80' else {
      raise Unsupported(
        message="run formatting requires ASCII element names at the edit site",
      )
    }
    at += 1
  }
  let name = ascii_slice(bytes, name_start, at)
  validate_xml_name(name)
  let attributes : Array[FormatAttribute] = []
  let mut self_closing = false
  while at < limit {
    while at < limit && is_xml_ws(bytes[at]) {
      at += 1
    }
    guard at < limit else {
      raise Unsupported(message="run formatting found a truncated tag")
    }
    if bytes[at] == b'>' {
      at += 1
      break
    }
    if bytes[at] == b'/' {
      guard at + 1 < limit && bytes[at + 1] == b'>' else {
        raise Unsupported(message="run formatting found a malformed tag")
      }
      self_closing = true
      at += 2
      break
    }
    // Attribute: name [ws] = [ws] quoted-value. The FULL span from the
    // name's first byte through the closing quote is recorded so a
    // whole-attribute removal never leaves a dangling name.
    let attr_name_start = at
    while at < limit && bytes[at] != b'=' && !is_xml_ws(bytes[at]) {
      guard bytes[at] < b'\x80' else {
        raise Unsupported(
          message="run formatting requires ASCII attribute names at the edit site",
        )
      }
      guard bytes[at] != b'>' && bytes[at] != b'/' else {
        raise Unsupported(
          message="run formatting found an attribute without a value",
        )
      }
      at += 1
    }
    let attr_name = ascii_slice(bytes, attr_name_start, at)
    validate_xml_name(attr_name)
    while at < limit && is_xml_ws(bytes[at]) {
      at += 1
    }
    guard at < limit && bytes[at] == b'=' else {
      raise Unsupported(
        message="run formatting found an attribute without a value",
      )
    }
    at += 1
    while at < limit && is_xml_ws(bytes[at]) {
      at += 1
    }
    guard at < limit && (bytes[at] == b'"' || bytes[at] == b'\'') else {
      raise Unsupported(
        message="run formatting found an unquoted attribute value",
      )
    }
    let quote = bytes[at]
    at += 1
    let value_start = at
    while at < limit && bytes[at] != quote {
      at += 1
    }
    guard at < limit else {
      raise Unsupported(
        message="run formatting found an unterminated attribute",
      )
    }
    if attr_name == "xmlns" || attr_name.has_prefix("xmlns:") {
      raise Unsupported(
        message="run formatting refuses a local namespace declaration at the edit site",
      )
    }
    attributes.push({
      name: attr_name,
      value: ascii_lossy_slice(bytes, value_start, at),
      name_start: attr_name_start,
      attr_end: at + 1,
      value_start,
      value_end: at,
    })
    at += 1
  }
  (name, attributes, self_closing, at)
}

///|
/// The ASCII subset of XML QNAMES this engine will edit around: at
/// most ONE colon, each side a nonempty NCName. Anything else — an
/// empty name (so `` can never pass as `w:b`), `w:`, `x:y:z` —
/// is a refusal: a multi-colon spelling would let split_qname's
/// first-colon split evade the alias guards.
fn validate_xml_name(name : String) -> Unit raise DocxError {
  let units = name.code_units()
  let mut colon = -1
  for index in 0.. Unit raise DocxError {
  guard part.length() > 0 else {
    raise Unsupported(message="run formatting found a malformed XML name")
  }
  let mut first = true
  for ch in part {
    let valid = if first {
      ch is ('A'..='Z' | 'a'..='z' | '_')
    } else {
      ch is ('A'..='Z' | 'a'..='z' | '0'..='9' | '_' | '-' | '.')
    }
    guard valid else {
      raise Unsupported(message="run formatting found a malformed XML name")
    }
    first = false
  }
}

///|
fn ascii_slice(
  bytes : BytesView,
  start : Int,
  end : Int,
) -> String raise DocxError {
  let builder = StringBuilder()
  for index in start.. String {
  let builder = StringBuilder()
  for index in start.. Int? {
  let order = [
    "rStyle", "rFonts", "b", "bCs", "i", "iCs", "caps", "smallCaps", "strike", "dstrike",
    "outline", "shadow", "emboss", "imprint", "noProof", "snapToGrid", "vanish",
    "webHidden", "color", "spacing", "w", "kern", "position", "sz", "szCs", "highlight",
    "u", "effect", "bdr", "shd", "fitText", "vertAlign", "rtl", "cs", "em", "lang",
    "eastAsianLayout", "specVanish", "oMath",
  ]
  for index, name in order {
    if name == local_name {
      return Some(index)
    }
  }
  None
}

///|
/// The boolean on/off vocabulary of ST_OnOff.
fn on_off_value(children_value : String?) -> Bool? {
  match children_value {
    None => Some(true)
    Some("1") | Some("true") | Some("on") => Some(true)
    Some("0") | Some("false") | Some("off") => Some(false)
    Some(_) => None
  }
}

// ---------------------------------------------------------------------------
// Planning
// ---------------------------------------------------------------------------

///|
priv struct PendingEdit {
  start : Int
  end : Int
  replacement : String
  // schema rank for stable equal-offset ordering
  order : Int
}

///|
/// The scanned structure of one run slice: where its open tag ends,
/// whether it is self-closing, and its (unique, first-child) `rPr` if
/// present. Shared by the planner and the verifier — this is lexer
/// truth, not edit generation.
priv struct RunShape {
  open_end : Int
  self_closing : Bool
  rpr : RunChildTag?
}

///|
/// Markup-compatibility directive attributes are refused wherever
/// they could redirect what this engine is editing around.
fn refuse_mce_attributes(
  owner : String,
  attributes : Array[FormatAttribute],
) -> Unit raise DocxError {
  for attribute in attributes {
    let (_, attr_local) = split_qname(attribute.name)
    if attr_local == "Ignorable" ||
      attr_local == "ProcessContent" ||
      attr_local == "MustUnderstand" {
      raise Unsupported(
        message="run formatting refuses a markup-compatibility attribute on \{owner}",
      )
    }
  }
}

///|
fn scan_run_shape(
  run_slice : BytesView,
  binding : WmlBinding,
) -> RunShape raise DocxError {
  guard run_slice.length() > 0 && run_slice[0] == b'<' else {
    raise Unsupported(message="run formatting requires the run's element slice")
  }
  let run_qname = wml_name(binding, "r")
  let (run_name, run_attributes, run_self_closing, run_open_end) = scan_format_tag(
    run_slice,
    0,
    run_slice.length(),
  )
  guard run_name == run_qname else {
    raise Unsupported(
      message="run formatting expected a <\{run_qname}> element at the slice head",
    )
  }
  // Markup-compatibility DIRECTIVES on the run itself (and they are
  // inherited from ancestors this slice cannot see — the wrapper
  // refusals below are the real guard) are refused on sight.
  refuse_mce_attributes(run_qname, run_attributes)
  if run_self_closing {
    guard run_open_end == run_slice.length() else {
      raise Unsupported(
        message="run formatting requires the slice to end at the run",
      )
    }
    return { open_end: run_open_end, self_closing: true, rpr: None, }
  }
  // The slice must end with this run's own close tag — checked, not
  // assumed, because this is a public planner. Parsed, not compared
  // to a canonical spelling: `` is legal XML. A '<' inside
  // content would be a well-formedness error anyway, so the LAST '<'
  // opens the run's close tag.
  let mut close_start = run_slice.length() - 1
  while close_start >= run_open_end && run_slice[close_start] != b'<' {
    close_start -= 1
  }
  guard close_start >= run_open_end &&
    close_start + 1 < run_slice.length() &&
    run_slice[close_start + 1] == b'/' else {
    raise Unsupported(
      message="run formatting requires the slice to end at the run's close tag",
    )
  }
  let mut cursor = close_start + 2
  while cursor < run_slice.length() &&
        run_slice[cursor] != b'>' &&
        !is_xml_ws(run_slice[cursor]) {
    cursor += 1
  }
  let close_name = ascii_slice(run_slice, close_start + 2, cursor)
  while cursor < run_slice.length() && is_xml_ws(run_slice[cursor]) {
    cursor += 1
  }
  guard close_name == run_qname &&
    cursor == run_slice.length() - 1 &&
    run_slice[cursor] == b'>' else {
    raise Unsupported(
      message="run formatting requires the slice to end at the run's close tag",
    )
  }
  let content_end = close_start
  let children = scan_direct_children(run_slice, run_open_end, content_end)
  let rpr_qname = wml_name(binding, "rPr")
  let mut rpr : RunChildTag? = None
  for index, child in children {
    // The alias family stops HERE too: an rPr under any other prefix
    // may be a WML alias (editing around it would plan a duplicate or
    // false-verify), and a markup-compatibility container could
    // supply one after content selection.
    let (child_prefix, child_local) = split_qname(child.name)
    if child_local == "rPr" && child.name != rpr_qname {
      raise Unsupported(
        message="run formatting refuses the run child \{child.name}: an rPr under another prefix may be a WML alias",
      )
    }
    if child_local == "AlternateContent" ||
      child_local == "Choice" ||
      child_local == "Fallback" ||
      child_local == "ProcessContent" {
      raise Unsupported(
        message="run formatting refuses markup-compatibility content inside a run",
      )
    }
    // ANY foreign-prefixed run child could be a wrapper that an
    // inherited mc:ProcessContent directive splices away — its
    // effective contents (a hidden rPr, say) cannot be established
    // from this slice, so it refuses.
    if child_prefix != binding.element {
      raise Unsupported(
        message="run formatting refuses the run child \{child.name}: a foreign-prefixed run child may be a markup-compatibility wrapper whose effective contents this engine cannot establish",
      )
    }
    if child.name == rpr_qname {
      refuse_mce_attributes(child.name, child.attributes)
      guard rpr is None else {
        raise Unsupported(
          message="run formatting refuses a run with duplicate rPr elements",
        )
      }
      // CT_R: rPr, when present, is the FIRST child.
      guard index == 0 else {
        raise Unsupported(
          message="run formatting refuses a run whose rPr is not its first element",
        )
      }
      rpr = Some(child)
    }
  }
  { open_end: run_open_end, self_closing: false, rpr, }
}

///|
/// Plan the minimal edits that make `format` hold explicitly on one
/// run, given the run's complete byte slice and the document's WML
/// binding: `prefix` is the ELEMENT prefix ("" for a default-namespace
/// document), `attr_prefix` (default: `prefix`) is the prefix WML
/// attributes carry — never empty, because default namespaces do not
/// apply to attributes — and `extension_prefixes` lists the prefixes
/// the caller warrants are bound to recognized rPr extension
/// namespaces.
pub fn plan_run_format(
  run_slice : BytesView,
  prefix~ : String,
  attr_prefix? : String,
  extension_prefixes? : Array[String],
  format~ : DocxDirectFormat,
) -> RunFormatPlan raise DocxError {
  let binding = make_binding(prefix, attr_prefix, extension_prefixes)
  let shape = scan_run_shape(run_slice, binding)
  let run_qname = wml_name(binding, "r")
  let rpr_qname = wml_name(binding, "rPr")
  if shape.self_closing {
    // Expand only the closing "/>" — the open tag's own attributes and
    // spacing are preserved untouched.
    let rendered = render_rpr_children(binding, format)
    let insert_at = run_slice.length() - 2
    guard run_slice[insert_at] == b'/' && run_slice[insert_at + 1] == b'>' else {
      raise Unsupported(
        message="run formatting found a malformed self-closing run",
      )
    }
    return {
      edits: [
        {
          start: insert_at,
          end: run_slice.length(),
          replacement: "><\{rpr_qname}>" +
          rendered +
          "",
        },
      ],
      changed: true,
    }
  }
  match shape.rpr {
    None => {
      let rendered = render_rpr_children(binding, format)
      {
        edits: [
          {
            start: shape.open_end,
            end: shape.open_end,
            replacement: "<\{rpr_qname}>" + rendered + "",
          },
        ],
        changed: true,
      }
    }
    Some(container) => plan_within_rpr(run_slice, binding, format, container)
  }
}

///|
/// The classified inventory of one non-self-closing rPr: targeted
/// properties recorded (duplicates refused), ranked children proven
/// monotonic, extension children admitted ONLY under a caller-vouched
/// prefix — and never when their local name collides with a ranked
/// WML property (a possible alias) or spells markup-compatibility
/// content this lexical engine cannot resolve.
priv struct RprInventory {
  bold : RunChildTag?
  bold_cs : RunChildTag?
  italic : RunChildTag?
  italic_cs : RunChildTag?
  underline : RunChildTag?
  color : RunChildTag?
  children : Array[RunChildTag]
  insertion_limit : Int
}

///|
fn empty_inventory() -> RprInventory {
  {
    bold: None,
    bold_cs: None,
    italic: None,
    italic_cs: None,
    underline: None,
    color: None,
    children: [],
    insertion_limit: 0,
  }
}

///|
fn classify_rpr_children(
  run_slice : BytesView,
  binding : WmlBinding,
  container : RunChildTag,
) -> RprInventory raise DocxError {
  // The interior ends where the scanner RECORDED the close tag —
  // never reconstructed from the canonical spelling, which ``
  // would exceed.
  let interior_end = container.close_start
  let children = scan_direct_children(
    run_slice,
    container.open_end,
    interior_end,
  )
  let mut seen_b : RunChildTag? = None
  let mut seen_bcs : RunChildTag? = None
  let mut seen_i : RunChildTag? = None
  let mut seen_ics : RunChildTag? = None
  let mut seen_u : RunChildTag? = None
  let mut seen_color : RunChildTag? = None
  let mut last_rank = -1
  let mut extension_block_start : Int? = None
  for child in children {
    let (child_prefix, local_name) = split_qname(child.name)
    if child_prefix != binding.element {
      // A foreign-prefixed child is legal ONLY as part of the trailing
      // extension block, under a prefix the caller vouched for — and
      // even then never under a ranked property's name (a possible
      // WML alias) or a markup-compatibility name (content selection
      // this lexical engine cannot resolve).
      guard binding.extensions.contains(child_prefix) else {
        raise Unsupported(
          message="run formatting refuses the rPr child \{child.name}: its prefix is outside the caller-vouched extension prefixes",
        )
      }
      guard rpr_rank(local_name) is None else {
        raise Unsupported(
          message="run formatting refuses \{child.name}: a ranked property name under a foreign prefix may be a WML alias",
        )
      }
      // The unranked WML structural names are alias-suspect too: an
      // aliased tracked change (or nested rPr) must never ride in as
      // an "extension".
      guard local_name != "rPrChange" && local_name != "rPr" else {
        raise Unsupported(
          message="run formatting refuses \{child.name}: \{local_name} under a foreign prefix may be a WML alias",
        )
      }
      guard local_name != "AlternateContent" &&
        local_name != "Choice" &&
        local_name != "Fallback" &&
        local_name != "ProcessContent" else {
        raise Unsupported(
          message="run formatting refuses markup-compatibility content inside rPr",
        )
      }
      // The wrapper's SUBTREE must not conceal WML either: an
      // inherited mc:ProcessContent directive could splice its
      // children into rPr after the fact.
      if !child.self_closing {
        scan_extension_interior(
          run_slice,
          binding,
          child.open_end,
          child.close_start,
        )
      }
      if extension_block_start is None {
        extension_block_start = Some(child.byte_start)
      }
      continue
    }
    if local_name == "rPrChange" {
      raise Unsupported(
        message="run formatting refuses a run with an in-flight tracked property change (rPrChange)",
      )
    }
    match rpr_rank(local_name) {
      Some(rank) => {
        guard extension_block_start is None else {
          raise Unsupported(
            message="run formatting refuses a ranked property after the trailing extension block",
          )
        }
        guard rank >= last_rank else {
          raise Unsupported(
            message="run formatting refuses an rPr whose children are out of schema order",
          )
        }
        // EVERY ranked CT_RPr property is an empty element in the
        // schema — the attribute-only ones (rFonts, sz, lang, bdr, …)
        // just as much as the on/off ones. A child inside one is
        // content this engine cannot account for, and the reader would
        // not walk into it either.
        guard !child.has_element_children else {
          raise Unsupported(
            message="run formatting refuses a non-leaf \{child.name} property",
          )
        }
        last_rank = rank
      }
      None =>
        raise Unsupported(
          message="run formatting does not recognize the rPr child \{child.name}",
        )
    }
    fn record(
      slot : RunChildTag?,
      tag : RunChildTag,
    ) -> RunChildTag? raise DocxError {
      guard slot is None else {
        raise Unsupported(
          message="run formatting refuses duplicate \{tag.name} properties",
        )
      }
      // A targeted property with element content is not the leaf this
      // engine understands.
      guard !tag.has_element_children else {
        raise Unsupported(
          message="run formatting refuses a non-leaf \{tag.name} property",
        )
      }
      Some(tag)
    }
    if local_name == "b" {
      seen_b = record(seen_b, child)
    } else if local_name == "bCs" {
      seen_bcs = record(seen_bcs, child)
    } else if local_name == "i" {
      seen_i = record(seen_i, child)
    } else if local_name == "iCs" {
      seen_ics = record(seen_ics, child)
    } else if local_name == "u" {
      seen_u = record(seen_u, child)
    } else if local_name == "color" {
      seen_color = record(seen_color, child)
    }
  }
  {
    bold: seen_b,
    bold_cs: seen_bcs,
    italic: seen_i,
    italic_cs: seen_ics,
    underline: seen_u,
    color: seen_color,
    children,
    // The unique legal insertion boundary for NEW children is before
    // the trailing extension block (or the interior end).
    insertion_limit: extension_block_start.unwrap_or(interior_end),
  }
}

///|
/// An admitted extension wrapper's DESCENDANTS must not conceal WML:
/// an inherited mc:ProcessContent directive can splice a wrapper's
/// children into rPr after the fact, so anything that could then
/// contradict this engine's absolute set — an element under the WML
/// binding, a ranked or structural local under ANY prefix, or nested
/// markup-compatibility content — refuses now, while it is still
/// hidden. Genuine extension content (w14:glow's theme colours, say)
/// passes untouched.
fn scan_extension_interior(
  bytes : BytesView,
  binding : WmlBinding,
  start : Int,
  end : Int,
) -> Unit raise DocxError {
  let children = scan_direct_children(bytes, start, end)
  for child in children {
    let (child_prefix, local_name) = split_qname(child.name)
    guard child_prefix != binding.element else {
      raise Unsupported(
        message="run formatting refuses WML content (\{child.name}) inside an extension wrapper",
      )
    }
    guard rpr_rank(local_name) is None &&
      local_name != "rPr" &&
      local_name != "rPrChange" else {
      raise Unsupported(
        message="run formatting refuses \{child.name} inside an extension wrapper: it may be a WML alias",
      )
    }
    guard local_name != "AlternateContent" &&
      local_name != "Choice" &&
      local_name != "Fallback" &&
      local_name != "ProcessContent" else {
      raise Unsupported(
        message="run formatting refuses markup-compatibility content inside an extension wrapper",
      )
    }
    if !child.self_closing {
      scan_extension_interior(bytes, binding, child.open_end, child.close_start)
    }
  }
}

///|
fn plan_within_rpr(
  run_slice : BytesView,
  binding : WmlBinding,
  format : DocxDirectFormat,
  container : RunChildTag,
) -> RunFormatPlan raise DocxError {
  let rpr_qname = wml_name(binding, "rPr")
  if container.self_closing {
    // Expand only the closing "/>": the open tag with every attribute
    // and spacing survives byte-for-byte.
    let rendered = render_rpr_children(binding, format)
    let slash_at = container.open_end - 2
    guard run_slice[slash_at] == b'/' && run_slice[slash_at + 1] == b'>' else {
      raise Unsupported(
        message="run formatting found a malformed self-closing rPr",
      )
    }
    return {
      edits: [
        {
          start: slash_at,
          end: container.open_end,
          replacement: ">" + rendered + "",
        },
      ],
      changed: true,
    }
  }
  let inventory = classify_rpr_children(run_slice, binding, container)
  let pending : Array[PendingEdit] = []
  match format.bold {
    Some(value) => {
      plan_on_off(
        binding,
        "b",
        value,
        inventory.bold,
        inventory,
        container,
        pending,
      )
      plan_on_off(
        binding,
        "bCs",
        value,
        inventory.bold_cs,
        inventory,
        container,
        pending,
      )
    }
    None => ()
  }
  match format.italic {
    Some(value) => {
      plan_on_off(
        binding,
        "i",
        value,
        inventory.italic,
        inventory,
        container,
        pending,
      )
      plan_on_off(
        binding,
        "iCs",
        value,
        inventory.italic_cs,
        inventory,
        container,
        pending,
      )
    }
    None => ()
  }
  match format.underline {
    Some(value) =>
      plan_underline(
        binding,
        value,
        inventory.underline,
        inventory,
        container,
        pending,
      )
    None => ()
  }
  match format.color {
    Some(value) =>
      plan_color(
        run_slice,
        binding,
        value,
        inventory.color,
        inventory,
        container,
        pending,
      )
    None => ()
  }
  // Stable order: ascending start; equal offsets keep schema-rank
  // order (the tie-break that an unstable sort would lose).
  pending.sort_by((left, right) => {
    if left.start != right.start {
      left.start - right.start
    } else {
      left.order - right.order
    }
  })
  let edits : Array[RunFormatEdit] = []
  for entry in pending {
    edits.push({
      start: entry.start,
      end: entry.end,
      replacement: entry.replacement,
    })
  }
  { edits, changed: edits.length() > 0, }
}

///|
/// The insertion offset for a new `local` child: after the last ranked
/// child of lower rank, never past the trailing extension block.
/// Ranked children are already proven monotonic, so this point is
/// unique and legal by construction.
fn rpr_insertion_offset(
  binding : WmlBinding,
  local_name : String,
  inventory : RprInventory,
  container : RunChildTag,
) -> (Int, Int) raise DocxError {
  guard rpr_rank(local_name) is Some(rank) else {
    raise Unsupported(message="run formatting has no rank for \{local_name}")
  }
  let mut offset = container.open_end
  for child in inventory.children {
    if child.byte_start >= inventory.insertion_limit {
      break
    }
    let (child_prefix, child_local) = split_qname(child.name)
    if child_prefix != binding.element {
      break
    }
    match rpr_rank(child_local) {
      Some(child_rank) => if child_rank < rank { offset = child.byte_end }
      None => break
    }
  }
  (offset, rank)
}

///|
/// The attribute name a WML-namespaced attribute carries — ALWAYS
/// qualified, because XML default namespaces do not apply to
/// attributes.
fn wml_attr(binding : WmlBinding, local_name : String) -> String {
  "\{binding.attribute}:\{local_name}"
}

///|
fn find_val(
  binding : WmlBinding,
  tag : RunChildTag,
) -> FormatAttribute? raise DocxError {
  let val_name = wml_attr(binding, "val")
  let mut found : FormatAttribute? = None
  for attribute in tag.attributes {
    if attribute.name == val_name {
      guard found is None else {
        raise Unsupported(
          message="run formatting refuses duplicate val attributes on \{tag.name}",
        )
      }
      found = Some(attribute)
    }
  }
  found
}

///|
/// On properties that keep their other attributes (u, color), any
/// attribute whose LOCAL name is one this engine asserts about but
/// whose prefix is not the bound one — a bare `val` (no namespace), a
/// foreign `x:val`, an aliased `x:themeColor` — is refused rather
/// than read past: under an aliased WML binding it may be the very
/// value this engine is about to contradict or leave behind.
fn refuse_wml_attr_alias(
  binding : WmlBinding,
  tag : RunChildTag,
  locals : Array[String],
) -> Unit raise DocxError {
  for attribute in tag.attributes {
    let (attr_prefix, local_name) = split_qname(attribute.name)
    if locals.contains(local_name) && attr_prefix != binding.attribute {
      raise Unsupported(
        message="run formatting refuses the attribute \{attribute.name} on \{tag.name}",
      )
    }
  }
}

///|
fn plan_on_off(
  binding : WmlBinding,
  local_name : String,
  requested : Bool,
  existing : RunChildTag?,
  inventory : RprInventory,
  container : RunChildTag,
  pending : Array[PendingEdit],
) -> Unit raise DocxError {
  match existing {
    Some(tag) => {
      // ST_OnOff properties carry ONLY the val attribute; anything
      // else — a bare `val` with no namespace, a foreign-prefixed
      // val — would change what this planner is reading, so it
      // refuses rather than misreads the property as val-less.
      let val_name = wml_attr(binding, "val")
      for attribute in tag.attributes {
        guard attribute.name == val_name else {
          raise Unsupported(
            message="run formatting refuses the attribute \{attribute.name} on \{tag.name}",
          )
        }
      }
      let val = find_val(binding, tag)
      match on_off_value(val.map(entry => entry.value)) {
        Some(current) =>
          if current != requested {
            match val {
              Some(attribute) =>
                pending.push({
                  start: attribute.value_start,
                  end: attribute.value_end,
                  replacement: if requested {
                    "1"
                  } else {
                    "0"
                  },
                  order: 0,
                })
              None => {
                let insert_at = if tag.self_closing {
                  tag.open_end - 2
                } else {
                  tag.open_end - 1
                }
                pending.push({
                  start: insert_at,
                  end: insert_at,
                  replacement: " \{wml_attr(binding, "val")}=\"0\"",
                  order: 0,
                })
              }
            }
          }
        None =>
          raise Unsupported(
            message="run formatting found an unrecognized on/off value on \{tag.name}",
          )
      }
    }
    None => {
      let (offset, rank) = rpr_insertion_offset(
        binding, local_name, inventory, container,
      )
      let qname = wml_name(binding, local_name)
      let rendered = if requested {
        "<\{qname}/>"
      } else {
        "<\{qname} \{wml_attr(binding, "val")}=\"0\"/>"
      }
      pending.push({
        start: offset,
        end: offset,
        replacement: rendered,
        order: rank,
      })
    }
  }
}

///|
fn plan_underline(
  binding : WmlBinding,
  requested : Bool,
  existing : RunChildTag?,
  inventory : RprInventory,
  container : RunChildTag,
  pending : Array[PendingEdit],
) -> Unit raise DocxError {
  let wanted = if requested { "single" } else { "none" }
  match existing {
    Some(tag) => {
      refuse_wml_attr_alias(binding, tag, ["val"])
      match find_val(binding, tag) {
        Some(attribute) =>
          if attribute.value != wanted {
            pending.push({
              start: attribute.value_start,
              end: attribute.value_end,
              replacement: wanted,
              order: 0,
            })
          }
        None =>
          raise Unsupported(
            message="run formatting found an underline without a val attribute",
          )
      }
    }
    None => {
      let (offset, rank) = rpr_insertion_offset(
        binding, "u", inventory, container,
      )
      pending.push({
        start: offset,
        end: offset,
        replacement: "<\{wml_name(binding, "u")} \{wml_attr(binding, "val")}=\"\{wanted}\"/>",
        order: rank,
      })
    }
  }
}

///|
/// Case-insensitive RGB equality: `ff0000` IS the requested `FF0000`,
/// and a semantic no-op must not rewrite the existing spelling.
fn rgb_equal(existing : String, requested : String) -> Bool {
  if existing.length() != 6 {
    return false
  }
  let left = existing.code_units()
  let right = requested.code_units()
  for index in 0..<6 {
    let a = left[index].to_int().unsafe_to_char()
    let folded = if a is ('a'..='f') {
      (a.to_int() - 32).unsafe_to_char()
    } else {
      a
    }
    if folded.to_int() != right[index].to_int() {
      return false
    }
  }
  true
}

///|
fn plan_color(
  run_slice : BytesView,
  binding : WmlBinding,
  requested : String,
  existing : RunChildTag?,
  inventory : RprInventory,
  container : RunChildTag,
  pending : Array[PendingEdit],
) -> Unit raise DocxError {
  match existing {
    Some(tag) => {
      refuse_wml_attr_alias(binding, tag, [
        "val", "themeColor", "themeTint", "themeShade",
      ])
      let val = find_val(binding, tag)
      let theme_names = [
        wml_attr(binding, "themeColor"),
        wml_attr(binding, "themeTint"),
        wml_attr(binding, "themeShade"),
      ]
      for attribute in tag.attributes {
        if theme_names.contains(attribute.name) {
          // The removal span covers the whitespace BEFORE the name
          // through the closing quote — spans come from the scanner,
          // never reconstructed.
          let mut span_start = attribute.name_start
          while span_start > 0 && is_xml_ws(run_slice[span_start - 1]) {
            span_start -= 1
          }
          pending.push({
            start: span_start,
            end: attribute.attr_end,
            replacement: "",
            order: 0,
          })
        }
      }
      match val {
        Some(attribute) =>
          if !rgb_equal(attribute.value, requested) {
            pending.push({
              start: attribute.value_start,
              end: attribute.value_end,
              replacement: requested,
              order: 0,
            })
          }
        None =>
          raise Unsupported(
            message="run formatting found a color without a val attribute",
          )
      }
    }
    None => {
      let (offset, rank) = rpr_insertion_offset(
        binding, "color", inventory, container,
      )
      pending.push({
        start: offset,
        end: offset,
        replacement: "<\{wml_name(binding, "color")} \{wml_attr(binding, "val")}=\"\{requested}\"/>",
        order: rank,
      })
    }
  }
}

///|
/// Render the CHILDREN of a fresh `rPr` in schema order.
fn render_rpr_children(
  binding : WmlBinding,
  format : DocxDirectFormat,
) -> String {
  let builder = StringBuilder()
  let val = wml_attr(binding, "val")
  match format.bold {
    Some(true) =>
      builder.write_string(
        "<\{wml_name(binding, "b")}/><\{wml_name(binding, "bCs")}/>",
      )
    Some(false) =>
      builder.write_string(
        "<\{wml_name(binding, "b")} \{val}=\"0\"/><\{wml_name(binding, "bCs")} \{val}=\"0\"/>",
      )
    None => ()
  }
  match format.italic {
    Some(true) =>
      builder.write_string(
        "<\{wml_name(binding, "i")}/><\{wml_name(binding, "iCs")}/>",
      )
    Some(false) =>
      builder.write_string(
        "<\{wml_name(binding, "i")} \{val}=\"0\"/><\{wml_name(binding, "iCs")} \{val}=\"0\"/>",
      )
    None => ()
  }
  match format.color {
    Some(value) =>
      builder.write_string(
        "<\{wml_name(binding, "color")} \{val}=\"\{value}\"/>",
      )
    None => ()
  }
  match format.underline {
    Some(value) => {
      let wanted = if value { "single" } else { "none" }
      builder.write_string("<\{wml_name(binding, "u")} \{val}=\"\{wanted}\"/>")
    }
    None => ()
  }
  builder.to_string()
}

// ---------------------------------------------------------------------------
// The direct-property oracle
// ---------------------------------------------------------------------------

///|
/// Read one ST_OnOff property strictly for VERIFICATION and compare
/// it to the requested value — written against the vocabulary, not
/// against the planner's edit generation.
fn verify_on_off(
  binding : WmlBinding,
  existing : RunChildTag?,
  requested : Bool,
  label : String,
) -> String? raise DocxError {
  match existing {
    None => Some("\{label} is not stated explicitly")
    Some(tag) => {
      let val_name = wml_attr(binding, "val")
      let mut spelled : String? = None
      let mut has_val = false
      for attribute in tag.attributes {
        guard attribute.name == val_name else {
          raise Unsupported(
            message="run formatting refuses the attribute \{attribute.name} on \{tag.name}",
          )
        }
        guard !has_val else {
          raise Unsupported(
            message="run formatting refuses duplicate val attributes on \{tag.name}",
          )
        }
        has_val = true
        spelled = Some(attribute.value)
      }
      match on_off_value(spelled) {
        Some(observed) =>
          if observed == requested {
            None
          } else {
            let observed_text = if observed { "on" } else { "off" }
            let requested_text = if requested { "on" } else { "off" }
            Some(
              "\{tag.name} states \{observed_text} where \{label} \{requested_text} was requested",
            )
          }
        None =>
          raise Unsupported(
            message="run formatting found an unrecognized on/off value on \{tag.name}",
          )
      }
    }
  }
}

///|
/// Verify that every requested property holds EXPLICITLY on the run
/// slice by EXTRACTING each property's observed value and comparing
/// it semantically — independent of the planner's edit generation
/// (the two share only the lexical scanner and the rPr
/// classification). Returns None when satisfied, or the FIRST
/// discrepancy in property order (b, bCs, i, iCs, u, color). As a
/// final cross-check the planner must also consider a satisfied slice
/// a fixed point; a disagreement between the two readings is itself
/// reported.
pub fn verify_run_format(
  run_slice : BytesView,
  prefix~ : String,
  attr_prefix? : String,
  extension_prefixes? : Array[String],
  format~ : DocxDirectFormat,
) -> String? raise DocxError {
  let binding = make_binding(prefix, attr_prefix, extension_prefixes)
  let shape = scan_run_shape(run_slice, binding)
  let inventory : RprInventory = match shape.rpr {
    Some(container) =>
      if container.self_closing {
        empty_inventory()
      } else {
        classify_rpr_children(run_slice, binding, container)
      }
    None => empty_inventory()
  }
  match format.bold {
    Some(requested) => {
      match verify_on_off(binding, inventory.bold, requested, "bold") {
        Some(discrepancy) => return Some(discrepancy)
        None => ()
      }
      match
        verify_on_off(
          binding,
          inventory.bold_cs,
          requested,
          "complex-script bold",
        ) {
        Some(discrepancy) => return Some(discrepancy)
        None => ()
      }
    }
    None => ()
  }
  match format.italic {
    Some(requested) => {
      match verify_on_off(binding, inventory.italic, requested, "italic") {
        Some(discrepancy) => return Some(discrepancy)
        None => ()
      }
      match
        verify_on_off(
          binding,
          inventory.italic_cs,
          requested,
          "complex-script italic",
        ) {
        Some(discrepancy) => return Some(discrepancy)
        None => ()
      }
    }
    None => ()
  }
  match format.underline {
    Some(requested) => {
      let wanted = if requested { "single" } else { "none" }
      match inventory.underline {
        None => return Some("underline is not stated explicitly")
        Some(tag) => {
          refuse_wml_attr_alias(binding, tag, ["val"])
          match find_val(binding, tag) {
            Some(attribute) =>
              if attribute.value != wanted {
                return Some(
                  "\{tag.name} states \{attribute.value} where \{wanted} was requested",
                )
              }
            None =>
              raise Unsupported(
                message="run formatting found an underline without a val attribute",
              )
          }
        }
      }
    }
    None => ()
  }
  match format.color {
    Some(requested) =>
      match inventory.color {
        None => return Some("color is not stated explicitly")
        Some(tag) => {
          refuse_wml_attr_alias(binding, tag, [
            "val", "themeColor", "themeTint", "themeShade",
          ])
          let theme_names = [
            wml_attr(binding, "themeColor"),
            wml_attr(binding, "themeTint"),
            wml_attr(binding, "themeShade"),
          ]
          for attribute in tag.attributes {
            if theme_names.contains(attribute.name) {
              return Some(
                "\{tag.name} keeps theme linkage (\{attribute.name}) where an absolute color was requested",
              )
            }
          }
          match find_val(binding, tag) {
            Some(attribute) =>
              if !rgb_equal(attribute.value, requested) {
                return Some(
                  "\{tag.name} states \{attribute.value} where \{requested} was requested",
                )
              }
            None =>
              raise Unsupported(
                message="run formatting found a color without a val attribute",
              )
          }
        }
      }
    None => ()
  }
  let plan = plan_run_format(
    run_slice,
    prefix~,
    attr_prefix?,
    extension_prefixes?,
    format~,
  )
  if plan.changed {
    Some(
      "the planner would still edit a slice whose extracted properties satisfy the request",
    )
  } else {
    None
  }
}