// L0 — the byte-span preservation layer. The XML layer is canonicalizing
// (write(parse(x)) != x), so annotation surgery on EXISTING documents
// never reserializes: it splices well-formed fragments into the original
// part bytes at scanner-derived offsets and repacks the zip, preserving
// untouched source records under the ZIP format's offset rules. Guarantees
// (the L0 contract):
//
// - Mutated parts are byte-identical OUTSIDE the declared edit spans.
// - Untouched source entries retain their raw local records, order, names,
//   compression payloads, descriptors, and producer metadata. Central records
//   retain all metadata except the required local-offset field when preceding
//   sizes move. Rewritten entries retain their source header policy while
//   replacing payload-dependent fields. Fresh archives have no source records.
// - Fail-closed, all-or-nothing: duplicate zip entries, missing or
//   already-existing parts, overlapping or out-of-range spans, non-UTF-8
//   story encodings, and edits that leave a part XML-malformed all raise
//   BEFORE any output exists. This function is pure — publication
//   (atomic temp + no-replace rename) is the caller's contract.

///|
/// One byte-range replacement inside a part: the bytes at
/// `[start, end)` are replaced by `replacement`. A pure insertion has
/// `start == end`; a pure deletion has empty `replacement`.
pub struct SpanEdit {
  priv start : Int
  priv end : Int
  priv replacement : Bytes
}

///|
/// Validates and builds a `SpanEdit` (0 <= start <= end; range bounds
/// against the part are checked at splice time).
pub fn span_edit(
  start~ : Int,
  end~ : Int,
  replacement : Bytes,
) -> SpanEdit raise @core.DocxError {
  if start < 0 {
    raise Unsupported(message="a span edit cannot start at \{start}")
  }
  if end < start {
    raise Unsupported(
      message="a span edit cannot end at \{end}, before its start at \{start}",
    )
  }
  { start, end, replacement }
}

///|
/// The full mutation plan for one splice: per-part span edits plus
/// whole new parts. Build with `edit_part`/`add_part`, apply with
/// `splice_docx`.
pub struct SplicePlan {
  priv part_edits : StableStringMap[Array[SpanEdit]]
  priv new_parts : Array[(String, Bytes)]
  // Exact immutable source payloads for offset-bearing edits. Keeping the
  // bytes makes stale detection collision-free while sharing the archive's
  // existing immutable buffers.
  priv source_parts : StableStringMap[BytesView]
}

///|
/// Transaction-grade limits applied before a splice allocates edited payloads
/// or serialized package bytes. The type is opaque so callers must construct a
/// complete, internally consistent limit set.
pub struct SpliceLimits {
  priv max_archive_entries : Int
  priv max_changed_parts : Int
  priv max_part_name_chars : Int
  priv max_entry_uncompressed_bytes : Int
  priv max_total_uncompressed_bytes : Int
  priv max_materialized_bytes : Int
  priv max_xml_tokens : Int
}

///|
/// Default byte-level callers do not provide transaction limits, but strict XML
/// parsing still expands qualified OOXML names. This fixed-linear multiplier
/// admits ordinary namespace-dense Office parts while keeping hostile namespace
/// amplification bounded by source size.
let lower_level_xml_materialization_scale : Int64 = 64L

///|
/// Builds the fail-closed resource contract for a preservation splice.
pub fn splice_limits(
  max_archive_entries~ : Int,
  max_changed_parts~ : Int,
  max_part_name_chars~ : Int,
  max_entry_uncompressed_bytes~ : Int,
  max_total_uncompressed_bytes~ : Int,
  max_materialized_bytes~ : Int,
  max_xml_tokens~ : Int,
) -> SpliceLimits raise @core.DocxError {
  if max_archive_entries <= 0 ||
    max_changed_parts <= 0 ||
    max_part_name_chars <= 0 ||
    max_entry_uncompressed_bytes <= 0 ||
    max_total_uncompressed_bytes <= 0 ||
    max_materialized_bytes < 0 ||
    max_xml_tokens <= 0 {
    raise Unsupported(
      message="splice limits must be positive (materialized bytes may be zero)",
    )
  }
  if max_entry_uncompressed_bytes > max_total_uncompressed_bytes {
    raise Unsupported(
      message="the splice entry limit cannot exceed its aggregate uncompressed limit",
    )
  }
  {
    max_archive_entries,
    max_changed_parts,
    max_part_name_chars,
    max_entry_uncompressed_bytes,
    max_total_uncompressed_bytes,
    max_materialized_bytes,
    max_xml_tokens,
  }
}

///|
/// An empty plan.
pub fn SplicePlan::new() -> SplicePlan {
  { part_edits: SortedMap([]), new_parts: [], source_parts: SortedMap([]) }
}

///|
/// Returns an independently mutable plan snapshot. Every map and edit array is
/// copied; replacement/addition `Bytes` and source `BytesView` values may be
/// shared because MoonBit exposes both as immutable byte sequences.
pub fn SplicePlan::copy(self : SplicePlan) -> SplicePlan {
  let part_edits : StableStringMap[Array[SpanEdit]] = SortedMap([])
  for name, edits in self.part_edits {
    part_edits[name] = edits.copy()
  }
  let source_parts : StableStringMap[BytesView] = SortedMap([])
  for name, source in self.source_parts {
    source_parts[name] = source
  }
  { part_edits, new_parts: self.new_parts.copy(), source_parts }
}

///|
/// True when the plan changes no existing part and adds no new part.
pub fn SplicePlan::is_empty(self : SplicePlan) -> Bool {
  self.part_edits.is_empty() && self.new_parts.is_empty()
}

///|
/// Pins an exact source payload for later stale-plan detection. Repeated pins
/// are allowed only when their bytes are identical.
pub fn SplicePlan::pin_part(
  self : SplicePlan,
  name : String,
  source : BytesView,
) -> Unit raise @core.DocxError {
  check_plan_part_name(name)
  let key = package_part_identity(name)
  for existing_name, _ in self.source_parts {
    if package_part_identity(existing_name) == key && existing_name != name {
      raise Unsupported(
        message="the splice plan pins case-equivalent part names '\{existing_name}' and '\{name}'",
      )
    }
  }
  match self.source_parts.get(name) {
    Some(existing) if existing != source =>
      raise Unsupported(
        message="the splice plan has conflicting source bytes for '\{name}'",
      )
    Some(_) => ()
    None => self.source_parts[name] = source
  }
}

///|
/// Returns the canonical, sorted union of edited and added package parts.
pub fn SplicePlan::declared_parts(self : SplicePlan) -> Array[String] {
  let names : StableStringSet = SortedSet([])
  for name, _ in self.part_edits {
    names.add(name)
  }
  for entry in self.new_parts {
    let (name, _) = entry
    names.add(name)
  }
  let result = names.to_array()
  result.sort()
  result
}

///|
/// Returns edited parts that do not carry exact source bytes, sorted by name.
/// Preservation-safe edit sessions reject such plans before adopting them.
pub fn SplicePlan::unpinned_edited_parts(self : SplicePlan) -> Array[String] {
  let result : Array[String] = []
  for name, _ in self.part_edits {
    if !self.source_parts.contains(name) {
      result.push(name)
    }
  }
  result.sort()
  result
}

///|
/// Builds the plan's case-insensitive OPC identity index without changing it.
/// A plan may mention one physical spelling in several roles (for example a
/// source pin plus edits), but every mention of the same identity must use that
/// exact spelling.
fn splice_plan_part_spellings(
  plan : SplicePlan,
) -> StableStringMap[String] raise @core.DocxError {
  let spellings : StableStringMap[String] = SortedMap([])
  fn record(
    spellings : StableStringMap[String],
    name : String,
  ) -> Unit raise @core.DocxError {
    let key = package_part_identity(name)
    match spellings.get(key) {
      Some(existing) if existing != name =>
        raise Unsupported(
          message="the splice plan refers to case-equivalent part names '\{existing}' and '\{name}'",
        )
      _ => spellings[key] = name
    }
  }
  for name, _ in plan.source_parts {
    record(spellings, name)
  }
  for name, _ in plan.part_edits {
    record(spellings, name)
  }
  for entry in plan.new_parts {
    let (name, _) = entry
    record(spellings, name)
  }
  spellings
}

///|
/// Merges another plan without consuming it. Source-pin disagreement,
/// duplicate additions, and edit/add conflicts fail before `self` changes.
pub fn SplicePlan::merge(
  self : SplicePlan,
  other : SplicePlan,
) -> Unit raise @core.DocxError {
  let self_spellings = splice_plan_part_spellings(self)
  let other_spellings = splice_plan_part_spellings(other)
  for key, other_name in other_spellings {
    match self_spellings.get(key) {
      Some(self_name) if self_name != other_name =>
        raise Unsupported(
          message="cannot merge splice plans with case-equivalent part names '\{self_name}' and '\{other_name}'",
        )
      _ => ()
    }
  }
  for name, expected in other.source_parts {
    match self.source_parts.get(name) {
      Some(existing) if existing != expected =>
        raise Unsupported(
          message="cannot merge splice plans with different source bytes for '\{name}'",
        )
      _ => ()
    }
  }
  for entry in other.new_parts {
    let (name, _) = entry
    if self.part_edits.contains(name) {
      raise Unsupported(
        message="cannot merge a splice plan that both edits and adds '\{name}'",
      )
    }
    for existing in self.new_parts {
      let (existing_name, _) = existing
      if existing_name == name {
        raise Unsupported(
          message="cannot merge splice plans that both add '\{name}'",
        )
      }
    }
  }
  for name, _ in other.part_edits {
    for existing in self.new_parts {
      let (existing_name, _) = existing
      if existing_name == name {
        raise Unsupported(
          message="cannot merge a splice plan that both adds and edits '\{name}'",
        )
      }
    }
  }
  for name, expected in other.source_parts {
    if !self.source_parts.contains(name) {
      self.source_parts[name] = expected
    }
  }
  for name, edits in other.part_edits {
    match self.part_edits.get(name) {
      Some(existing) => existing.append(edits)
      None => self.part_edits[name] = edits.copy()
    }
  }
  for entry in other.new_parts {
    self.new_parts.push(entry)
  }
}

///|
/// Adds one span edit to an EXISTING part (zip entry name, e.g.
/// "word/document.xml"). Edits on one part may arrive in any order;
/// they are sorted and checked for overlap at splice time.
pub fn SplicePlan::edit_part(
  self : SplicePlan,
  name : String,
  edit : SpanEdit,
) -> Unit raise @core.DocxError {
  let key = package_part_identity(name)
  for entry in self.new_parts {
    let (added, _) = entry
    if package_part_identity(added) == key {
      raise Unsupported(
        message="the splice plan cannot both edit '\{name}' and add case-equivalent part '\{added}'",
      )
    }
  }
  for existing, _ in self.part_edits {
    if package_part_identity(existing) == key && existing != name {
      raise Unsupported(
        message="the splice plan edits case-equivalent part names '\{existing}' and '\{name}'",
      )
    }
  }
  match self.part_edits.get(name) {
    Some(edits) => edits.push(edit)
    None => self.part_edits[name] = [edit]
  }
}

///|
/// Adds a whole NEW part. The name must not exist in the original
/// package (checked at splice time) nor be added twice.
pub fn SplicePlan::add_part(
  self : SplicePlan,
  name : String,
  bytes : Bytes,
) -> Unit raise @core.DocxError {
  check_plan_part_name(name)
  self.add_checked_part(name, bytes)
}

///|
/// Adds a whole new part after applying the transaction-grade name and entry
/// ceilings. The Unicode-scalar/UTF-16 check deliberately runs before path
/// splitting or diagnostics can copy an attacker-controlled name.
pub fn SplicePlan::add_part_limited(
  self : SplicePlan,
  name : String,
  bytes : Bytes,
  limits : SpliceLimits,
) -> Unit raise @core.DocxError {
  check_plan_part_name_limit(name, limits.max_part_name_chars)
  if bytes.length() > limits.max_entry_uncompressed_bytes {
    raise Unsupported(
      message="the splice entry uncompressed limit is \{limits.max_entry_uncompressed_bytes} bytes",
    )
  }
  check_plan_part_name(name)
  self.add_checked_part(name, bytes)
}

///|
fn SplicePlan::add_checked_part(
  self : SplicePlan,
  name : String,
  bytes : Bytes,
) -> Unit raise @core.DocxError {
  let key = package_part_identity(name)
  for edited, _ in self.part_edits {
    if package_part_identity(edited) == key {
      if edited == name {
        raise Unsupported(
          message="the splice plan cannot both edit and add a part named '\{name}'",
        )
      } else {
        raise Unsupported(
          message="the splice plan cannot both add '\{name}' and edit case-equivalent part '\{edited}'",
        )
      }
    }
  }
  for entry in self.new_parts {
    let (existing, _) = entry
    if package_part_identity(existing) == key {
      if existing == name {
        raise Unsupported(
          message="the splice plan already adds a part named '\{name}'",
        )
      } else {
        raise Unsupported(
          message="the splice plan already adds case-equivalent part '\{existing}' for '\{name}'",
        )
      }
    }
  }
  self.new_parts.push((name, bytes))
}

///|
/// Validates the complete plan against an already materialized archive without
/// mutating the archive or serializing a candidate. With `require_pins=true`,
/// every edited part must carry exact source bytes.
pub fn SplicePlan::check_against(
  self : SplicePlan,
  archive : @mbtzip.Archive,
  require_pins? : Bool = false,
  limits? : SpliceLimits,
) -> Unit raise @core.DocxError {
  let _ = build_spliced_archive(archive, self, require_pins, limits)
}

///|
/// Applies the plan to the original package bytes and returns the new
/// package. All-or-nothing: every failure raises before any output is
/// produced (see the module header for the full fail-closed list).
pub fn splice_docx(
  original : BytesView,
  plan : SplicePlan,
) -> Bytes raise @core.DocxError {
  let archive = @mbtzip.read(original) catch {
    err =>
      raise InvalidZip(
        message="could not read the original package: \{repr(err)}",
      )
  }
  let output = build_spliced_archive(archive, plan, false, None)
  // The byte API can preserve a true no-op exactly, including archive bytes
  // the writer need not understand. Validation above still rejects ambiguous
  // duplicate names and stale pins before this fast path.
  if plan.is_empty() {
    return original.to_owned()
  }
  @mbtzip.write(output) catch {
    err =>
      raise InvalidZip(message="could not repack the package: \{repr(err)}")
  }
}

///|
/// Applies a plan to an already materialized archive and serializes through a
/// hard output ceiling. The caller's archive is never mutated; untouched
/// source records remain available to the ZIP writer for byte preservation.
pub fn splice_docx_archive(
  original : @mbtzip.Archive,
  plan : SplicePlan,
  max_output_bytes~ : Int,
  require_pins? : Bool = false,
  limits? : SpliceLimits,
) -> Bytes raise @core.DocxError {
  if max_output_bytes < 0 {
    raise Unsupported(message="the splice output limit cannot be negative")
  }
  let output = build_spliced_archive(original, plan, require_pins, limits)
  @mbtzip.write_limited(output, max_output_bytes~) catch {
    OutputLimitExceeded(limit~) =>
      raise Unsupported(
        message="the spliced package exceeds the output limit of \{limit} bytes",
      )
    err =>
      raise InvalidZip(message="could not repack the package: \{repr(err)}")
  }
}

///|
/// A copy of these limits with a reduced materialization ceiling — a
/// multi-step fold shrinks it per step so cumulative staged growth stays
/// under the transaction's single splice allowance. `bytes` may be zero
/// (an all-consumed budget); a negative value is refused.
pub fn SpliceLimits::with_max_materialized_bytes(
  self : SpliceLimits,
  bytes : Int,
) -> SpliceLimits raise @core.DocxError {
  if bytes < 0 {
    raise Unsupported(
      message="the reduced materialized ceiling cannot be negative",
    )
  }
  { ..self, max_materialized_bytes: bytes }
}

///|
/// Applies a plan to an isolated fork and returns the spliced ARCHIVE
/// without serializing it — the seam a multi-step fold uses to stage one
/// op, reindex on the result, and stage the next, serializing only once
/// at the end. Limits are enforced before any edited payload is
/// allocated, exactly as `splice_docx_archive` does.
pub fn stage_spliced_archive(
  original : @mbtzip.Archive,
  plan : SplicePlan,
  require_pins? : Bool = false,
  limits? : SpliceLimits,
) -> @mbtzip.Archive raise @core.DocxError {
  build_spliced_archive(original, plan, require_pins, limits)
}

///|
/// One checked existing-part splice. Preparation sorts and validates the
/// ranges and computes the exact result size/token count without allocating
/// the edited payload.
priv struct PreparedPartSplice {
  name : String
  source : BytesView
  edits : Array[SpanEdit]
  result_length : Int
  replacement_bytes : Int64
  markup_tokens : Int64
}

///|
fn checked_splice_size_add(
  current : Int64,
  additional : Int64,
  what : String,
) -> Int64 raise @core.DocxError {
  if current < 0L ||
    additional < 0L ||
    current > 9223372036854775807L - additional {
    raise Unsupported(message="the splice \{what} size overflows Int64")
  }
  current + additional
}

///|
/// Checks and applies a plan to an isolated shallow fork. When limits are
/// supplied, every count and exact output size is accepted before the first
/// edited payload is allocated. No package serialization happens here.
fn build_spliced_archive(
  archive : @mbtzip.Archive,
  plan : SplicePlan,
  require_pins : Bool,
  limits : SpliceLimits?,
) -> @mbtzip.Archive raise @core.DocxError {
  // OPC Part identity is case-insensitive. ZIP directory records are not
  // addressable Parts, so their physical identity remains case-sensitive while
  // exact duplicates are still forbidden, including on the true no-op path.
  let seen : StableStringMap[String] = SortedMap([])
  let directory_names : StableStringSet = SortedSet([])
  let mut source_entry_count = 0
  let mut source_uncompressed_bytes = 0L
  for entry in archive.entries() {
    let name = entry.name()
    if name.has_suffix("/") {
      if directory_names.contains(name) {
        raise InvalidZip(
          message="the package holds duplicate zip entries named '\{name}'; refusing to splice",
        )
      }
      directory_names.add(name)
    } else {
      let key = package_part_identity(name)
      match seen.get(key) {
        Some(existing) if existing == name =>
          raise InvalidZip(
            message="the package holds duplicate zip entries named '\{name}'; refusing to splice",
          )
        Some(existing) =>
          raise InvalidZip(
            message="the package holds case-equivalent zip entries '\{existing}' and '\{name}'; refusing to splice",
          )
        None => seen[key] = name
      }
    }
    source_entry_count = source_entry_count + 1
    let entry_length = entry.data().length()
    source_uncompressed_bytes = checked_splice_size_add(
      source_uncompressed_bytes,
      entry_length.to_int64(),
      "source aggregate",
    )
    match limits {
      Some(value) => {
        if source_entry_count > value.max_archive_entries {
          raise Unsupported(
            message="the splice archive entry limit is \{value.max_archive_entries}, but the source has at least \{source_entry_count} entries",
          )
        }
        if entry_length > value.max_entry_uncompressed_bytes {
          raise Unsupported(
            message="the splice entry uncompressed limit is \{value.max_entry_uncompressed_bytes} bytes, but '\{entry.name()}' has \{entry_length} bytes",
          )
        }
        if source_uncompressed_bytes >
          value.max_total_uncompressed_bytes.to_int64() {
          raise Unsupported(
            message="the splice aggregate uncompressed limit is \{value.max_total_uncompressed_bytes} bytes, but the source exceeds it",
          )
        }
      }
      None => ()
    }
  }
  let mut changed_part_count = plan.new_parts.length().to_int64()
  let mut span_edit_count = 0L
  for _, edits in plan.part_edits {
    changed_part_count = checked_splice_size_add(
      changed_part_count, 1L, "preservation manifest",
    )
    span_edit_count = checked_splice_size_add(
      span_edit_count,
      edits.length().to_int64(),
      "span edit count",
    )
  }
  match limits {
    Some(value) => {
      if changed_part_count > value.max_changed_parts.to_int64() {
        raise Unsupported(
          message="the splice preservation manifest limit is \{value.max_changed_parts} parts, but the plan declares \{changed_part_count}",
        )
      }
      if plan.new_parts.length() >
        value.max_archive_entries - source_entry_count {
        raise Unsupported(
          message="the splice archive entry limit is \{value.max_archive_entries}, but the result would contain too many entries",
        )
      }
      if span_edit_count > value.max_xml_tokens.to_int64() {
        raise Unsupported(
          message="the splice span edit limit is \{value.max_xml_tokens}, but the plan declares \{span_edit_count}",
        )
      }
    }
    None => ()
  }
  for name, _ in plan.part_edits {
    match limits {
      Some(value) => check_plan_part_name_limit(name, value.max_part_name_chars)
      None => ()
    }
    check_plan_part_name(name)
    match seen.get(package_part_identity(name)) {
      Some(actual) if actual == name => ()
      Some(actual) =>
        raise Unsupported(
          message="the splice plan must use exact source spelling '\{actual}', not case-equivalent '\{name}'",
        )
      None =>
        raise Unsupported(
          message="the splice plan edits '\{name}', which the package does not contain",
        )
    }
    if require_pins && !plan.source_parts.contains(name) {
      raise Unsupported(
        message="the splice plan edits '\{name}' without pinned source bytes",
      )
    }
  }
  for entry in plan.new_parts {
    let (name, bytes) = entry
    match limits {
      Some(value) => {
        check_plan_part_name_limit(name, value.max_part_name_chars)
        if bytes.length() > value.max_entry_uncompressed_bytes {
          raise Unsupported(
            message="the splice entry uncompressed limit is \{value.max_entry_uncompressed_bytes} bytes, but added part '\{name}' has \{bytes.length()} bytes",
          )
        }
      }
      None => ()
    }
    check_plan_part_name(name)
    let key = package_part_identity(name)
    match seen.get(key) {
      Some(existing) if existing == name =>
        raise Unsupported(
          message="the splice plan adds '\{name}', which already exists in the package",
        )
      Some(existing) =>
        raise Unsupported(
          message="the splice plan adds '\{name}', case-equivalent to existing part '\{existing}'",
        )
      None => seen[key] = name
    }
  }
  for name, expected in plan.source_parts {
    match limits {
      Some(value) => check_plan_part_name_limit(name, value.max_part_name_chars)
      None => ()
    }
    match archive.get(name) {
      Some(actual) if actual != expected =>
        raise Unsupported(
          message="the splice plan is stale: source part '\{name}' changed after the plan was built",
        )
      Some(_) => ()
      None =>
        raise Unsupported(
          message="the splice plan is stale: source part '\{name}' is no longer present",
        )
    }
  }
  // Prepare every existing-part edit and compute the exact result/archive
  // sizes before allocating any replacement payload.
  let prepared : Array[PreparedPartSplice] = []
  let mut result_uncompressed_bytes = source_uncompressed_bytes
  let mut materialized_bytes = 0L
  let mut xml_source_units = 0L
  let mut markup_tokens = 0L
  let edited : StableStringMap[Bytes] = SortedMap([])
  for name, edits in plan.part_edits {
    match archive.get(name) {
      Some(bytes) => {
        let part = prepare_span_edits(name, bytes, edits, limits)
        result_uncompressed_bytes = checked_splice_size_add(
          result_uncompressed_bytes - bytes.length().to_int64(),
          part.result_length.to_int64(),
          "result aggregate",
        )
        materialized_bytes = checked_splice_size_add(
          checked_splice_size_add(
            materialized_bytes,
            part.replacement_bytes,
            "materialization",
          ),
          part.result_length.to_int64(),
          "materialization",
        )
        markup_tokens = checked_splice_size_add(
          markup_tokens,
          part.markup_tokens,
          "XML token",
        )
        xml_source_units = checked_splice_size_add(
          xml_source_units,
          part.result_length.to_int64(),
          "XML source",
        )
        prepared.push(part)
      }
      None =>
        raise Unsupported(
          message="the splice plan edits '\{name}', which the package does not contain",
        )
    }
  }
  for entry in plan.new_parts {
    let (_, bytes) = entry
    result_uncompressed_bytes = checked_splice_size_add(
      result_uncompressed_bytes,
      bytes.length().to_int64(),
      "result aggregate",
    )
    materialized_bytes = checked_splice_size_add(
      materialized_bytes,
      bytes.length().to_int64(),
      "materialization",
    )
  }
  match limits {
    Some(value) => {
      if result_uncompressed_bytes >
        value.max_total_uncompressed_bytes.to_int64() {
        raise Unsupported(
          message="the splice aggregate uncompressed limit is \{value.max_total_uncompressed_bytes} bytes, but the result requires \{result_uncompressed_bytes}",
        )
      }
      if materialized_bytes > value.max_materialized_bytes.to_int64() {
        raise Unsupported(
          message="the splice materialization limit is \{value.max_materialized_bytes} bytes, but replacements, added parts, and edited payloads require \{materialized_bytes}",
        )
      }
      if markup_tokens > value.max_xml_tokens.to_int64() {
        raise Unsupported(
          message="the splice aggregate XML token limit is \{value.max_xml_tokens}, but edited parts require \{markup_tokens}",
        )
      }
    }
    None => ()
  }
  if xml_source_units > 2147483647L {
    raise Unsupported(
      message="the splice aggregate XML source exceeds the maximum addressable parser budget",
    )
  }
  // One mutable parser budget is shared by every edited result. Its byte
  // ceiling is the exact aggregate result size. Transaction-grade copied-text
  // and token ceilings come from the already charged splice allowance;
  // lower-level callers receive a conservative input-linear fallback instead
  // of an unlimited parser. Expanded namespace names can require a small
  // constant multiple of the raw source even for ordinary OOXML, so a 1:1
  // copied-text ceiling would reject valid edits after charging that work.
  let exact_xml_source_units = xml_source_units.to_int()
  let fallback_scaled = exact_xml_source_units.to_int64() *
    lower_level_xml_materialization_scale
  let fallback_allowance = if fallback_scaled <= 0L {
    1
  } else if fallback_scaled > 2147483647L {
    2147483647
  } else {
    fallback_scaled.to_int()
  }
  let xml_budget : @xml.XmlReadBudget = match limits {
    Some(value) =>
      @xml.xml_read_budget(
        max_source_units=exact_xml_source_units,
        max_tokens=value.max_xml_tokens,
        max_materialized_chars=value.max_materialized_bytes,
        max_token_chars=value.max_materialized_bytes,
      )
    None =>
      @xml.xml_read_budget(
        max_source_units=exact_xml_source_units,
        max_tokens=fallback_allowance,
        max_materialized_chars=fallback_allowance,
        max_token_chars=exact_xml_source_units,
      )
  }
  // Only after the whole plan passes the global preflight may edited payloads
  // and their temporary strict-XML validation trees be materialized.
  for part in prepared {
    edited[part.name] = apply_prepared_span_edits(part, xml_budget)
  }
  let output = archive.fork()
  for name, bytes in edited {
    if !output.replace(name, bytes) {
      raise Unsupported(
        message="the splice plan edits '\{name}', which the package does not contain",
      )
    }
  }
  for entry in plan.new_parts {
    let (name, bytes) = entry
    output.add(name, bytes)
  }
  output
}

///|
fn package_part_identity(name : String) -> String {
  name.to_lower()
}

///|
/// Plan-controlled names are stricter than arbitrary legacy ZIP names: every
/// edit/add manifest must be a canonical relative entry path.
fn check_plan_part_name(name : String) -> Unit raise @core.DocxError {
  if name.length() == 0 || name.has_prefix("/") || name.contains("\\") {
    raise Unsupported(
      message="splice plan part names must be canonical relative ZIP entry names ('\{name}')",
    )
  }
  for component in name.split("/") {
    if component.length() == 0 || component == "." || component == ".." {
      raise Unsupported(
        message="splice plan part names must not contain empty, '.', or '..' components ('\{name}')",
      )
    }
  }
}

///|
/// Applies the transaction's Unicode-scalar ceiling to a plan-controlled name
/// and rejects malformed UTF-16 before any diagnostic copies the name.
fn check_plan_part_name_limit(
  name : String,
  max_chars : Int,
) -> Unit raise @core.DocxError {
  let mut offset = 0
  let mut scalars = 0
  while offset < name.length() {
    let unit = name[offset]
    if unit.is_leading_surrogate() {
      if offset + 1 >= name.length() ||
        !name[offset + 1].is_trailing_surrogate() {
        raise Unsupported(
          message="the splice part name limit requires valid UTF-16 names",
        )
      }
      offset = offset + 2
    } else if unit.is_trailing_surrogate() {
      raise Unsupported(
        message="the splice part name limit requires valid UTF-16 names",
      )
    } else {
      offset = offset + 1
    }
    scalars = scalars + 1
    if scalars > max_chars {
      raise Unsupported(
        message="the splice part name limit is \{max_chars} Unicode scalars",
      )
    }
  }
}

///|
/// Conservative strict-parser cardinality proxy counted directly over the
/// future result segments: element starts plus attribute assignments.
fn xml_markup_tokens(bytes : BytesView) -> Int64 {
  let mut count = 0L
  for byte in bytes {
    if byte == b'<' || byte == b'=' {
      count = count + 1L
    }
  }
  count
}

///|
/// Performs the encoding, ordering, overlap, range, exact-size, and XML-token
/// preflight for one part without allocating its edited payload.
fn prepare_span_edits(
  name : String,
  bytes : BytesView,
  edits : Array[SpanEdit],
  limits : SpliceLimits?,
) -> PreparedPartSplice raise @core.DocxError {
  check_part_encoding(name, bytes)
  // Deterministic application order: by start; at EQUAL starts,
  // zero-width insertions land before a consuming edit, and edits that
  // still tie keep their PLAN order (Array::sort_by is unstable, so the
  // plan ordinal is part of the key) — two markers inserted at the same
  // offset of an empty paragraph must splice in the order planned.
  let decorated : Array[(Int, Int, Int, SpanEdit)] = []
  for ordinal, edit in edits {
    let consuming = if edit.end > edit.start { 1 } else { 0 }
    decorated.push((edit.start, consuming, ordinal, edit))
  }
  decorated.sort_by((a, b) => {
    let (a_start, a_consuming, a_ordinal, _) = a
    let (b_start, b_consuming, b_ordinal, _) = b
    if a_start != b_start {
      a_start - b_start
    } else if a_consuming != b_consuming {
      a_consuming - b_consuming
    } else {
      a_ordinal - b_ordinal
    }
  })
  let sorted : Array[SpanEdit] = []
  for entry in decorated {
    let (_, _, _, edit) = entry
    sorted.push(edit)
  }
  let mut previous_end = 0
  for edit in sorted {
    if edit.start < previous_end {
      raise Unsupported(
        message="the splice plan's edits on '\{name}' overlap (an edit starts at \{edit.start}, before the previous edit's end at \{previous_end})",
      )
    }
    if edit.end > bytes.length() {
      raise Unsupported(
        message="a splice edit on '\{name}' ends at \{edit.end}, past the part's \{bytes.length()} bytes",
      )
    }
    previous_end = edit.end
  }
  let mut result_length = bytes.length().to_int64()
  let mut replacement_bytes = 0L
  let mut markup_tokens = 0L
  let mut cursor = 0
  for edit in sorted {
    result_length = checked_splice_size_add(
      result_length - (edit.end - edit.start).to_int64(),
      edit.replacement.length().to_int64(),
      "edited part",
    )
    replacement_bytes = checked_splice_size_add(
      replacement_bytes,
      edit.replacement.length().to_int64(),
      "replacement aggregate",
    )
    markup_tokens = checked_splice_size_add(
      checked_splice_size_add(
        markup_tokens,
        xml_markup_tokens(bytes[cursor:edit.start]),
        "XML token",
      ),
      xml_markup_tokens(edit.replacement),
      "XML token",
    )
    cursor = edit.end
  }
  markup_tokens = checked_splice_size_add(
    markup_tokens,
    xml_markup_tokens(bytes[cursor:]),
    "XML token",
  )
  if result_length > 2147483647L {
    raise Unsupported(
      message="splicing '\{name}' exceeds the maximum addressable part size",
    )
  }
  match limits {
    Some(value) => {
      if result_length > value.max_entry_uncompressed_bytes.to_int64() {
        raise Unsupported(
          message="the splice entry uncompressed limit is \{value.max_entry_uncompressed_bytes} bytes, but edited part '\{name}' requires \{result_length}",
        )
      }
      if markup_tokens > value.max_xml_tokens.to_int64() {
        raise Unsupported(
          message="the splice XML token limit is \{value.max_xml_tokens}, but edited part '\{name}' requires \{markup_tokens}",
        )
      }
    }
    None => ()
  }
  {
    name,
    source: bytes,
    edits: sorted,
    result_length: result_length.to_int(),
    replacement_bytes,
    markup_tokens,
  }
}

///|
fn apply_prepared_span_edits(
  prepared : PreparedPartSplice,
  xml_budget : @xml.XmlReadBudget,
) -> Bytes raise @core.DocxError {
  // Build a view-only segment table, then let Bytes::makei allocate and fill
  // exactly one immutable result buffer. This avoids Buffer growth/copy peaks
  // after the exact materialization preflight.
  let segments : Array[BytesView] = []
  let mut source_at = 0
  for edit in prepared.edits {
    let source_segment = prepared.source[source_at:edit.start]
    if !source_segment.is_empty() {
      segments.push(source_segment)
    }
    if !edit.replacement.is_empty() {
      segments.push(edit.replacement)
    }
    source_at = edit.end
  }
  let tail = prepared.source[source_at:]
  if !tail.is_empty() {
    segments.push(tail)
  }
  let mut segment_total = 0L
  for segment in segments {
    segment_total = checked_splice_size_add(
      segment_total,
      segment.length().to_int64(),
      "materialization segment",
    )
  }
  if segment_total != prepared.result_length.to_int64() {
    raise Unsupported(
      message="the splice size preflight disagreed with materialization",
    )
  }
  let segment_index = Ref(0)
  let segment_start = Ref(0)
  let result = Bytes::makei(prepared.result_length, output_index => {
    while segment_index.val < segments.length() &&
          output_index - segment_start.val >=
          segments[segment_index.val].length() {
      segment_start.val = segment_start.val +
        segments[segment_index.val].length()
      segment_index.val = segment_index.val + 1
    }
    if segment_index.val >= segments.length() {
      b'\x00'
    } else {
      segments[segment_index.val][output_index - segment_start.val]
    }
  })
  validate_spliced_xml(prepared.name, result, xml_budget)
  result
}

///|
/// Well-formedness belt for one edited part. Transaction-grade callers supply
/// the single budget shared across the whole plan; lower-level callers receive
/// an input-linear derived budget rather than an unlimited parser.
fn validate_spliced_xml(
  name : String,
  result : Bytes,
  xml_budget : @xml.XmlReadBudget,
) -> Unit raise @core.DocxError {
  // A span may rewrite the XML declaration itself. Re-run the same encoding
  // gate used for the source so a well-formed byte sequence cannot escape with
  // a newly declared foreign encoding and different consumer semantics.
  check_part_encoding(name, result)
  try @xml.read_xml_bytes_strict_limited(result, xml_budget) catch {
    ResourceLimit(limit~, message~) =>
      raise @core.contextual_docx_xml_resource_limit_error(
        limit,
        "splicing '\{name}' exceeded XML resource limits: \{message}",
      )
    InvalidXml(message~) => {
      if message == "XML source is not valid UTF-8" {
        raise Unsupported(
          message="splicing '\{name}' produced bytes that are not valid UTF-8",
        )
      }
      raise Unsupported(
        message="splicing '\{name}' produced malformed XML (\{message}); refusing to emit",
      )
    }
    err =>
      raise Unsupported(
        message="splicing '\{name}' produced malformed XML (\{repr(err)}); refusing to emit",
      )
  } noraise {
    _ => ()
  }
}

///|
/// The encoding gate: parts are spliced as UTF-8 bytes, so only UTF-8
/// (with or without BOM) is accepted — a UTF-16 BOM or a non-UTF-8
/// declared encoding fails closed, matching the annotation scanner's
/// own UTF-8-only rule (offsets from the scanner index UTF-8 bytes and
/// would be meaningless in any other encoding).
fn check_part_encoding(
  name : String,
  bytes : BytesView,
) -> Unit raise @core.DocxError {
  if bytes.length() >= 2 {
    let first = bytes[0].to_int()
    let second = bytes[1].to_int()
    if (first == 0xFF && second == 0xFE) || (first == 0xFE && second == 0xFF) {
      raise Unsupported(
        message="'\{name}' is UTF-16 encoded; byte-span splicing supports UTF-8 parts only",
      )
    }
  }
  // A declared encoding other than UTF-8 fails closed even when the
  // bytes would decode: offsets and fragments assume UTF-8. A
  // MALFORMED declaration (e.g. an unquoted encoding value) also fails
  // closed — the XML belt skips declarations, so nothing downstream
  // would catch it.
  match declared_encoding(bytes) {
    DeclaredUtf8 => ()
    DeclaredOther =>
      raise Unsupported(
        message="'\{name}' declares a non-UTF-8 encoding; byte-span splicing supports UTF-8 parts only",
      )
    MalformedDeclaration =>
      raise Unsupported(
        message="'\{name}' has a malformed XML declaration (unparseable encoding value); refusing to splice",
      )
    NoDeclaration => ()
  }
}

///|
priv enum EncodingDeclaration {
  NoDeclaration
  DeclaredUtf8
  DeclaredOther
  MalformedDeclaration
}

///|
/// Compares one bounded byte range with a fixed ASCII token without
/// materializing attacker-controlled declaration names or values.
fn declaration_range_equals(
  bytes : BytesView,
  start : Int,
  end : Int,
  expected : BytesView,
) -> Bool {
  if end - start != expected.length() {
    return false
  }
  for offset in 0.. Bool {
  let length = end - start
  if length != 4 && length != 5 {
    return false
  }
  fn folded(byte : Byte) -> Int {
    if byte >= b'A' && byte <= b'Z' {
      byte.to_int() + 32
    } else {
      byte.to_int()
    }
  }
  if folded(bytes[start]) != b'u'.to_int() ||
    folded(bytes[start + 1]) != b't'.to_int() ||
    folded(bytes[start + 2]) != b'f'.to_int() {
    return false
  }
  if length == 4 {
    folded(bytes[start + 3]) == b'8'.to_int()
  } else {
    bytes[start + 3] == b'-' && folded(bytes[start + 4]) == b'8'.to_int()
  }
}

///|
/// The encoding pseudo-attribute of the XML declaration, when the part
/// starts with one (BOM tolerated).
fn declared_encoding(bytes : BytesView) -> EncodingDeclaration {
  let mut at = 0
  // Skip a UTF-8 BOM.
  if bytes.length() >= 3 &&
    bytes[0].to_int() == 0xEF &&
    bytes[1].to_int() == 0xBB &&
    bytes[2].to_int() == 0xBF {
    at = 3
  }
  if at + 5 > bytes.length() {
    return NoDeclaration
  }
  if !(bytes[at] == b'<' &&
    bytes[at + 1] == b'?' &&
    bytes[at + 2] == b'x' &&
    bytes[at + 3] == b'm' &&
    bytes[at + 4] == b'l') {
    return NoDeclaration
  }
  // Only the exact `xml` processing-instruction target introduces an XML
  // declaration. Valid targets such as `xml-stylesheet` and `xmlversion`
  // merely share the prefix and must continue to strict XML validation as
  // ordinary processing instructions.
  if at + 5 < bytes.length() &&
    !is_decl_space(bytes[at + 5]) &&
    bytes[at + 5] != b'?' {
    return NoDeclaration
  }
  // Walk the declaration as PSEUDO-ATTRIBUTES (name = "value" | 'value'),
  // never scanning inside quoted values — "encoding" appearing inside a
  // VALUE is just text (review round 2). Any structural deviation is a
  // malformed declaration and fails closed. The XML grammar demands
  // WHITESPACE after '' and
  // 'value"name' runs are malformed (round 3).
  let limit = bytes.length()
  let mut cursor = at + 5
  if cursor >= limit || !is_decl_space(bytes[cursor]) {
    return MalformedDeclaration
  }
  let mut found : EncodingDeclaration = NoDeclaration
  // The declaration grammar is POSITIONAL: VersionInfo first
  // (required), then EncodingDecl?, then SDDecl?, nothing else —
  // '' without version is malformed (round 4).
  // 0 = expect version; 1 = expect encoding|standalone|end;
  // 2 = expect standalone|end; 3 = expect end.
  let mut state = 0
  for ;; {
    while cursor < limit && is_decl_space(bytes[cursor]) {
      cursor += 1
    }
    if cursor + 1 < limit && bytes[cursor] == b'?' && bytes[cursor + 1] == b'>' {
      if state == 0 {
        return MalformedDeclaration
      }
      return found
    }
    // Pseudo-attribute name: ASCII letters only (version/encoding/
    // standalone).
    let name_start = cursor
    while cursor < limit && is_decl_name_byte(bytes[cursor]) {
      cursor += 1
    }
    if cursor == name_start {
      return MalformedDeclaration
    }
    let name_end = cursor
    while cursor < limit && is_decl_space(bytes[cursor]) {
      cursor += 1
    }
    if cursor >= limit || bytes[cursor] != b'=' {
      return MalformedDeclaration
    }
    cursor += 1
    while cursor < limit && is_decl_space(bytes[cursor]) {
      cursor += 1
    }
    if cursor >= limit || !(bytes[cursor] == b'"' || bytes[cursor] == b'\'') {
      return MalformedDeclaration
    }
    let quote = bytes[cursor]
    cursor += 1
    let value_start = cursor
    while cursor < limit && bytes[cursor] != quote {
      cursor += 1
    }
    if cursor >= limit {
      return MalformedDeclaration
    }
    let is_version = declaration_range_equals(
      bytes, name_start, name_end, b"version",
    )
    let is_encoding = declaration_range_equals(
      bytes, name_start, name_end, b"encoding",
    )
    let is_standalone = declaration_range_equals(
      bytes, name_start, name_end, b"standalone",
    )
    if state == 0 && is_version {
      state = 1
    } else if state == 1 && is_encoding {
      found = if declaration_value_is_utf8(bytes, value_start, cursor) {
        DeclaredUtf8
      } else {
        DeclaredOther
      }
      state = 2
    } else if (state == 1 || state == 2) && is_standalone {
      state = 3
    } else {
      // Unknown names, duplicates, and out-of-order pseudo-attributes
      // are all grammar violations.
      return MalformedDeclaration
    }
    cursor += 1
    // The next thing after a value must be whitespace or the end of
    // the declaration — 'value"name' runs are malformed.
    if cursor < limit && !is_decl_space(bytes[cursor]) && bytes[cursor] != b'?' {
      return MalformedDeclaration
    }
  }
}

///|
fn is_decl_name_byte(byte : Byte) -> Bool {
  (byte >= b'a' && byte <= b'z') || (byte >= b'A' && byte <= b'Z')
}

///|
fn is_decl_space(byte : Byte) -> Bool {
  byte == b' ' || byte == b'\t' || byte == b'\r' || byte == b'\n'
}