///|
/// The remaining allowance for the next archive part. `maximum` is the tighter
/// of the per-entry ceiling and the aggregate archive space still available;
/// the other fields retain the policy-level error reported when it is crossed.
priv struct WritePartBudget {
  maximum : Int
  resource_kind : String
  resource_limit : Int
}

///|
/// A UTF-8-byte-counted XML builder. The ceiling is checked before appending
/// each fragment, so generated XML cannot first grow past the remaining archive
/// allowance. Escaped text and attributes are streamed directly into this
/// builder instead of allocating an expanded intermediate string.
priv struct LimitedXmlBuilder {
  builder : StringBuilder
  budget : WritePartBudget?
  mut encoded_bytes : Int
}

///|
fn LimitedXmlBuilder::new(budget : WritePartBudget?) -> LimitedXmlBuilder {
  { builder: StringBuilder::new(), budget, encoded_bytes: 0 }
}

///|
fn xml_utf8_length_within(value : StringView, maximum : Int) -> Int? {
  let mut length = 0
  for character in value {
    let scalar = character.to_int()
    let width = if scalar <= 0x7F {
      1
    } else if scalar <= 0x7FF {
      2
    } else if scalar <= 0xFFFF {
      3
    } else {
      4
    }
    if width > maximum - length {
      return None
    }
    length = length + width
  }
  Some(length)
}

///|
fn is_valid_xml_output_character(character : Char) -> Bool {
  let scalar = character.to_int()
  scalar == 0x09 ||
  scalar == 0x0A ||
  scalar == 0x0D ||
  (scalar >= 0x20 && scalar <= 0xD7FF) ||
  (scalar >= 0xE000 && scalar <= 0xFFFD) ||
  (scalar >= 0x10000 && scalar <= 0x10FFFF)
}

///|
fn validate_xml_output(value : StringView) -> Unit raise XlsxError {
  for character in value {
    if !is_valid_xml_output_character(character) {
      raise InvalidXml(
        msg="generated XML contains forbidden code point \{character.to_int()}",
      )
    }
  }
}

///|
fn LimitedXmlBuilder::write_view(
  self : LimitedXmlBuilder,
  value : StringView,
) -> Unit raise XlsxError {
  validate_xml_output(value)
  match self.budget {
    Some(budget) => {
      let remaining = budget.maximum - self.encoded_bytes
      let fragment_bytes = match xml_utf8_length_within(value, remaining) {
        Some(length) => length
        None =>
          raise ResourceLimitExceeded(
            kind=budget.resource_kind,
            limit=budget.resource_limit,
            actual=bounded_actual_above_limit(budget.resource_limit),
          )
      }
      self.builder.write_view(value)
      self.encoded_bytes = self.encoded_bytes + fragment_bytes
    }
    None => self.builder.write_view(value)
  }
}

///|
fn LimitedXmlBuilder::write_char(
  self : LimitedXmlBuilder,
  value : Char,
) -> Unit raise XlsxError {
  if !is_valid_xml_output_character(value) {
    raise InvalidXml(
      msg="generated XML contains forbidden code point \{value.to_int()}",
    )
  }
  let scalar = value.to_int()
  let width = if scalar <= 0x7F {
    1
  } else if scalar <= 0x7FF {
    2
  } else if scalar <= 0xFFFF {
    3
  } else {
    4
  }
  match self.budget {
    Some(budget) if width > budget.maximum - self.encoded_bytes =>
      raise ResourceLimitExceeded(
        kind=budget.resource_kind,
        limit=budget.resource_limit,
        actual=bounded_actual_above_limit(budget.resource_limit),
      )
    _ => ()
  }
  self.builder.write_char(value)
  self.encoded_bytes = self.encoded_bytes + width
}

///|
fn LimitedXmlBuilder::write_xml_text(
  self : LimitedXmlBuilder,
  value : StringView,
) -> Unit raise XlsxError {
  let mut literal_start = 0
  for index in 0.. Some("&")
      unit if unit == ('<' : UInt16) => Some("<")
      unit if unit == ('>' : UInt16) => Some(">")
      _ => None
    }
    match escaped {
      Some(entity) => {
        self.write_view(value[literal_start:index])
        self.write_view(entity)
        literal_start = index + 1
      }
      None => ()
    }
  }
  self.write_view(value[literal_start:])
}

///|
fn LimitedXmlBuilder::write_xml_attr(
  self : LimitedXmlBuilder,
  value : StringView,
) -> Unit raise XlsxError {
  let mut literal_start = 0
  for index in 0.. Some("&")
      unit if unit == ('<' : UInt16) => Some("<")
      unit if unit == ('>' : UInt16) => Some(">")
      unit if unit == ('"' : UInt16) => Some(""")
      unit if unit == ('\'' : UInt16) => Some("'")
      _ => None
    }
    match escaped {
      Some(entity) => {
        self.write_view(value[literal_start:index])
        self.write_view(entity)
        literal_start = index + 1
      }
      None => ()
    }
  }
  self.write_view(value[literal_start:])
}

///|
/// Write a `` text element, adding `xml:space="preserve"` when the text
/// carries leading/trailing spaces or embedded newlines that XML parsers
/// would otherwise be free to normalize away.
fn LimitedXmlBuilder::write_t_element(
  self : LimitedXmlBuilder,
  text : StringView,
) -> Unit raise XlsxError {
  if text.has_prefix(" ") || text.has_suffix(" ") || text.contains("\n") {
    self.write_view("")
  } else {
    self.write_view("")
  }
  self.write_xml_text(text)
  self.write_view("")
}

///|
/// Write the `` element of a rich-text run font. Theme takes
/// precedence over indexed, which takes precedence over rgb; a zero tint is
/// omitted. Shared by the shared-strings and comments writers.
fn LimitedXmlBuilder::write_rich_text_color(
  self : LimitedXmlBuilder,
  font : RichTextFont,
) -> Unit raise XlsxError {
  let tint = match font.color_tint {
    Some(v) => if v == 0.0 { None } else { Some(v) }
    None => None
  }
  match font.color_theme {
    Some(value) => {
      self.write_view(" self.write_view(" tint=\"\{v}\"")
        None => ()
      }
      self.write_view("/>")
      return
    }
    None => ()
  }
  match font.color_indexed {
    Some(value) => {
      self.write_view(" self.write_view(" tint=\"\{v}\"")
        None => ()
      }
      self.write_view("/>")
      return
    }
    None => ()
  }
  match font.color {
    Some(color) => {
      let normalized = color.replace_all(old="#", new="")
      let rgb = if normalized.length() == 6 {
        "FF" + normalized
      } else {
        normalized
      }
      self.write_view(" self.write_view(" tint=\"\{v}\"")
        None => ()
      }
      self.write_view("/>")
    }
    None => ()
  }
}

///|
/// Write one rich-text run body: the `...` font properties (when
/// present) followed by the run's `` element. The caller writes the
/// surrounding ``/`` markers so each emitter keeps its own indentation
/// and line-break layout.
fn LimitedXmlBuilder::write_rich_text_run(
  self : LimitedXmlBuilder,
  run : RichTextRun,
) -> Unit raise XlsxError {
  match run.font {
    Some(font) => {
      self.write_view("")
      if font.bold {
        self.write_view("")
      }
      if font.italic {
        self.write_view("")
      }
      if font.strike {
        self.write_view("")
      }
      if font.outline {
        self.write_view("")
      }
      if font.shadow {
        self.write_view("")
      }
      if font.condense {
        self.write_view("")
      }
      if font.extended {
        self.write_view("")
      }
      match font.underline {
        Some(value) => {
          self.write_view("")
        }
        None => ()
      }
      match font.size {
        Some(size) => self.write_view("")
        None => ()
      }
      match font.charset {
        Some(value) => self.write_view("")
        None => ()
      }
      match font.family_number {
        Some(value) => self.write_view("")
        None => ()
      }
      match font.family {
        Some(family) => {
          self.write_view("")
        }
        None => ()
      }
      match font.color {
        Some(_) => self.write_rich_text_color(font)
        None =>
          if font.color_theme is Some(_) ||
            font.color_indexed is Some(_) ||
            font.color_tint is Some(_) {
            self.write_rich_text_color(font)
          }
      }
      match font.vert_align {
        Some(value) => {
          self.write_view("")
        }
        None => ()
      }
      match font.scheme {
        Some(value) => {
          self.write_view("")
        }
        None => ()
      }
      self.write_view("")
    }
    None => ()
  }
  self.write_t_element(run.text)
}

///|
fn LimitedXmlBuilder::to_string(self : LimitedXmlBuilder) -> String {
  self.builder.to_string()
}

///|
/// Returns the unused portion of this builder's policy for a nested generator.
/// Passing this budget downstream prevents a child fragment from allocating up
/// to the whole part ceiling after its parent has already emitted content.
fn LimitedXmlBuilder::remaining_budget(
  self : LimitedXmlBuilder,
) -> WritePartBudget? {
  match self.budget {
    Some(budget) =>
      Some({
        maximum: budget.maximum - self.encoded_bytes,
        resource_kind: budget.resource_kind,
        resource_limit: budget.resource_limit,
      })
    None => None
  }
}

///|
/// Tracks XML fragments that must be retained before their parent can append
/// them. Each fragment receives only the allowance left after earlier retained
/// fragments, preventing an array of individually valid fragments from growing
/// beyond the parent part's remaining budget.
priv struct WritePartBudgetTracker {
  budget : WritePartBudget?
  mut encoded_bytes : Int
}

///|
fn WritePartBudgetTracker::new(
  budget : WritePartBudget?,
) -> WritePartBudgetTracker {
  { budget, encoded_bytes: 0 }
}

///|
fn WritePartBudgetTracker::remaining_budget(
  self : WritePartBudgetTracker,
) -> WritePartBudget? {
  match self.budget {
    Some(budget) =>
      Some({
        maximum: budget.maximum - self.encoded_bytes,
        resource_kind: budget.resource_kind,
        resource_limit: budget.resource_limit,
      })
    None => None
  }
}

///|
fn WritePartBudgetTracker::consume(
  self : WritePartBudgetTracker,
  value : StringView,
) -> Unit raise XlsxError {
  match self.budget {
    Some(budget) => {
      let remaining = budget.maximum - self.encoded_bytes
      let fragment_bytes = match xml_utf8_length_within(value, remaining) {
        Some(length) => length
        None =>
          raise ResourceLimitExceeded(
            kind=budget.resource_kind,
            limit=budget.resource_limit,
            actual=bounded_actual_above_limit(budget.resource_limit),
          )
      }
      self.encoded_bytes = self.encoded_bytes + fragment_bytes
    }
    None => ()
  }
}

///|
/// Parent allowance for a generated archive part that is assembled through
/// multiple simultaneously-retained strings. Every individual intermediate
/// remains subject to the per-entry ceiling, while their combined retained
/// bytes debit the aggregate space left after already-archived parts.
priv struct WriteCompositionBudgetTracker {
  limits : WriteLimits?
  archived_bytes : Int
  mut retained_bytes : Int
}

///|
fn WriteCompositionBudgetTracker::remaining_budget(
  self : WriteCompositionBudgetTracker,
) -> WritePartBudget? {
  match self.limits {
    Some(limits) => {
      let aggregate_remaining = limits.max_total_uncompressed_bytes -
        self.archived_bytes -
        self.retained_bytes
      if limits.max_entry_uncompressed_bytes <= aggregate_remaining {
        Some({
          maximum: limits.max_entry_uncompressed_bytes,
          resource_kind: "entry_uncompressed_bytes",
          resource_limit: limits.max_entry_uncompressed_bytes,
        })
      } else {
        Some({
          maximum: aggregate_remaining,
          resource_kind: "total_uncompressed_bytes",
          resource_limit: limits.max_total_uncompressed_bytes,
        })
      }
    }
    None => None
  }
}

///|
fn WriteCompositionBudgetTracker::consume(
  self : WriteCompositionBudgetTracker,
  value : StringView,
) -> Unit raise XlsxError {
  match self.remaining_budget() {
    Some(budget) => {
      let fragment_bytes = match xml_utf8_length_within(value, budget.maximum) {
        Some(length) => length
        None =>
          raise ResourceLimitExceeded(
            kind=budget.resource_kind,
            limit=budget.resource_limit,
            actual=bounded_actual_above_limit(budget.resource_limit),
          )
      }
      self.retained_bytes = self.retained_bytes + fragment_bytes
    }
    None => ()
  }
}

///|
fn write_workbook_protection_xml(
  protection : WorkbookProtection,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("   0 {
    sb.write_view(" workbookSpinCount=\"\{protection.spin_count}\"")
  }
  sb.write_view("/>\n")
  sb.to_string()
}

///|
fn write_shared_strings_xml(
  values : Array[SharedStringEntry],
  total_count : Int,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("\n")
  sb.write_view(
    "\n",
  )
  for entry in values {
    match entry.runs {
      Some(runs) => {
        sb.write_view("  \n")
        for run in runs {
          sb.write_view("    ")
          sb.write_rich_text_run(run)
          sb.write_view("\n")
        }
        sb.write_view("  \n")
      }
      None => {
        sb.write_view("  ")
        sb.write_t_element(entry.text)
        sb.write_view("\n")
      }
    }
  }
  sb.write_view("")
  sb.to_string()
}

///|
fn collect_formula_cells(workbook : Workbook) -> Array[(Int, String)] {
  let formula_cells : Array[(Int, String)] = []
  for i, sheet in workbook.sheets() {
    let sheet_id = i + 1
    for cell in sheet.cells() {
      match cell.formula {
        Some(value) =>
          if value != "" {
            formula_cells.push((sheet_id, cell.reference))
          }
        None => ()
      }
    }
  }
  formula_cells
}

///|
fn write_calc_chain_xml(
  cells : Array[(Int, String)],
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("\n")
  sb.write_view(
    "\n",
  )
  for cell in cells {
    let (sheet_id, reference) = cell
    sb.write_view("  \n")
  }
  sb.write_view("")
  sb.to_string()
}

///|
priv struct DrawingImage {
  image : Image
  rel_id : Int
  hyperlink_rel_id : Int?
}

///|
priv struct DrawingChart {
  reference : String
  offset_x : Int
  offset_y : Int
  width_emu : Int
  height_emu : Int
  print_object : Bool
  locked : Bool
  positioning : PicturePositioning
  rel_id : Int
  drawing_offset_x_emu : Int?
  drawing_offset_y_emu : Int?
  drawing_width_emu : Int?
  drawing_height_emu : Int?
  drawing_order : Int?
}

///|
priv struct DrawingHyperlink {
  rel_id : Int
  target : String
  link_type : HyperlinkType
}

///|
priv struct DrawingSlicer {
  slicer : Slicer
  is_table_slicer : Bool
}

///|
priv struct OrderedDrawingAnchor {
  xml : String
  order : Int?
  fallback_order : Int
}

///|
priv struct ResolvedSlicerCache {
  cache_id : Int
  cache_name : String
  source_name : String
  item_desc : Bool
  source : ResolvedSlicerCacheSource
}

///|
priv enum ResolvedSlicerCacheSource {
  Table(Int, Int)
  Pivot(Int, Int, String)
}

///|
let emu_per_pixel = 9525

///|
fn drawing_int64_to_int_for_write(
  value : Int64,
  field : StringView,
) -> Int raise XlsxError {
  let min_int = -0x7fff_ffffL - 1L
  if value < min_int || value > 0x7fff_ffffL {
    raise InvalidSheetOperation(msg=field.to_owned() + " out of range")
  }
  value.to_int()
}

///|
fn drawing_pixel_offset_for_write(
  value : Int,
  field : StringView,
) -> Int raise XlsxError {
  drawing_int64_to_int_for_write(
    Int64::from_int(value) * Int64::from_int(emu_per_pixel),
    field,
  )
}

///|
fn drawing_extent_for_write(
  value : Int,
  field : StringView,
) -> Int raise XlsxError {
  if value < 0 {
    raise InvalidSheetOperation(msg=field.to_owned() + " must be >= 0")
  }
  value
}

///|
fn drawing_scaled_extent_for_write(
  pixels : Double,
  scale : Double,
  field : StringView,
) -> Int raise XlsxError {
  let value = (pixels * scale * emu_per_pixel.to_double()).round()
  if Double::is_nan(value) ||
    Double::is_inf(value) ||
    value < 0.0 ||
    value > 2147483647.0 {
    raise InvalidSheetOperation(msg=field.to_owned() + " out of range")
  }
  value.to_int()
}

///|
fn drawing_axis_boundary_emu_for_write(
  metrics : DrawingAxisMetrics,
  idx0 : Int,
) -> Int64 raise XlsxError {
  let pixels = metrics.pixel_span(0, idx0)
  let factor = Int64::from_int(emu_per_pixel)
  let max_pixels = (0x7fff_ffff_ffff_ffffL - 0xffff_ffffL) / factor
  if pixels > max_pixels {
    raise InvalidSheetOperation(msg="drawing grid geometry out of range")
  }
  pixels * factor
}

///|
fn two_cell_axis_to(
  metrics : DrawingAxisMetrics,
  start_idx0 : Int,
  start_off_emu : Int,
  size_emu : Int,
) -> (Int, Int) raise XlsxError {
  if start_idx0 < 0 || start_idx0 >= metrics.limit {
    raise InvalidSheetOperation(msg="drawing " + metrics.axis + " invalid")
  }
  ignore(drawing_extent_for_write(size_emu, "drawing extent"))
  let start = drawing_axis_boundary_emu_for_write(metrics, start_idx0) +
    Int64::from_int(start_off_emu)
  let target = start + Int64::from_int(size_emu)
  let grid_end = drawing_axis_boundary_emu_for_write(metrics, metrics.limit)
  if target > grid_end {
    raise InvalidSheetOperation(msg="drawing extends beyond worksheet grid")
  }
  if target < 0L {
    return (0, drawing_int64_to_int_for_write(target, "drawing marker offset"))
  }
  if target == grid_end {
    return (metrics.limit, 0)
  }
  let mut low = 0
  let mut high = metrics.limit + 1
  while low < high {
    let mid = low + (high - low) / 2
    let boundary = drawing_axis_boundary_emu_for_write(metrics, mid)
    if boundary <= target {
      low = mid + 1
    } else {
      high = mid
    }
  }
  let idx0 = low - 1
  let boundary = drawing_axis_boundary_emu_for_write(metrics, idx0)
  (
    idx0,
    drawing_int64_to_int_for_write(target - boundary, "drawing marker offset"),
  )
}

///|
fn write_drawing_open(sb : LimitedXmlBuilder) -> Unit raise XlsxError {
  sb.write_view("\n")
  sb.write_view(
    "\n",
  )
}

///|
fn allocate_drawing_numeric_id(
  next : Int,
  reserved : Set[String],
) -> (Int, Int) raise XlsxError {
  let mut candidate = next
  while reserved.contains("\{candidate}") {
    if candidate == 0x7fff_ffff {
      raise InvalidSheetOperation(msg="drawing object id exhausted")
    }
    candidate = candidate + 1
  }
  if candidate == 0x7fff_ffff {
    raise InvalidSheetOperation(msg="drawing object id exhausted")
  }
  (candidate, candidate + 1)
}

///|
fn allocate_drawing_relationship_id(
  next : Int,
  reserved : Set[String],
) -> (Int, Int) raise XlsxError {
  let mut candidate = next
  while reserved.contains("rId\{candidate}") {
    if candidate == 0x7fff_ffff {
      raise InvalidSheetOperation(msg="drawing relationship id exhausted")
    }
    candidate = candidate + 1
  }
  if candidate == 0x7fff_ffff {
    raise InvalidSheetOperation(msg="drawing relationship id exhausted")
  }
  (candidate, candidate + 1)
}

///|
fn compare_ordered_drawing_anchor(
  left : OrderedDrawingAnchor,
  right : OrderedDrawingAnchor,
) -> Int {
  match (left.order, right.order) {
    (Some(a), Some(b)) =>
      if a < b {
        -1
      } else if a > b {
        1
      } else if left.fallback_order < right.fallback_order {
        -1
      } else if left.fallback_order > right.fallback_order {
        1
      } else {
        0
      }
    (Some(_), None) => -1
    (None, Some(_)) => 1
    (None, None) =>
      if left.fallback_order < right.fallback_order {
        -1
      } else if left.fallback_order > right.fallback_order {
        1
      } else {
        0
      }
  }
}

///|
fn write_drawing_xml(
  sheet : Worksheet?,
  images : Array[DrawingImage],
  charts : Array[DrawingChart],
  shapes : Array[Shape],
  slicers : Array[DrawingSlicer],
  preserved_anchors? : ArrayView[PreservedDrawingAnchor] = [],
  reserved_object_ids? : ArrayView[String] = [],
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let drawing_metrics = match sheet {
    Some(value) => drawing_metrics_for_sheet(value)
    None => drawing_metrics_for_sheet(Worksheet::new("Drawing"))
  }
  let sb = LimitedXmlBuilder::new(budget)
  write_drawing_open(sb)
  let reserved_object_id_set : Set[String] = Set([])
  for id in reserved_object_ids {
    reserved_object_id_set.add(id)
  }
  let mut obj_id = 1
  for item in images {
    let image = item.image
    let (row, col) = cell_ref_to_rc(image.reference)
    let col_idx = col - 1
    let row_idx = row - 1
    let (pic_id, next_obj_id) = allocate_drawing_numeric_id(
      obj_id, reserved_object_id_set,
    )
    obj_id = next_obj_id
    let col_off = match image.drawing_offset_x_emu {
      Some(value) => value
      None =>
        drawing_pixel_offset_for_write(
          image.offset_x,
          "drawing image column offset",
        )
    }
    let row_off = match image.drawing_offset_y_emu {
      Some(value) => value
      None =>
        drawing_pixel_offset_for_write(
          image.offset_y,
          "drawing image row offset",
        )
    }
    let cx = drawing_extent_for_write(
      match image.drawing_width_emu {
        Some(value) => value
        None => image.width_emu
      },
      "drawing image width",
    )
    let cy = drawing_extent_for_write(
      match image.drawing_height_emu {
        Some(value) => value
        None => image.height_emu
      },
      "drawing image height",
    )
    let picture_name = if image.name == "" {
      "Image \{pic_id}"
    } else {
      image.name
    }
    match image.positioning {
      TwoCell => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        let (to_col_idx, to_col_off) = two_cell_axis_to(
          drawing_metrics.cols,
          col_idx,
          col_off,
          cx,
        )
        let (to_row_idx, to_row_off) = two_cell_axis_to(
          drawing_metrics.rows,
          row_idx,
          row_off,
          cy,
        )
        sb.write_view("    \n")
        sb.write_view("      \{to_col_idx}\n")
        sb.write_view("      \{to_col_off}\n")
        sb.write_view("      \{to_row_idx}\n")
        sb.write_view("      \{to_row_off}\n")
        sb.write_view("    \n")
      }
      Absolute => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        let (to_col_idx, to_col_off) = two_cell_axis_to(
          drawing_metrics.cols,
          col_idx,
          col_off,
          cx,
        )
        let (to_row_idx, to_row_off) = two_cell_axis_to(
          drawing_metrics.rows,
          row_idx,
          row_off,
          cy,
        )
        sb.write_view("    \n")
        sb.write_view("      \{to_col_idx}\n")
        sb.write_view("      \{to_col_off}\n")
        sb.write_view("      \{to_row_idx}\n")
        sb.write_view("      \{to_row_off}\n")
        sb.write_view("    \n")
      }
      OneCell => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        sb.write_view("    \n")
      }
    }
    sb.write_view("    \n")
    sb.write_view("      \n")
    match item.hyperlink_rel_id {
      Some(link_id) => {
        sb.write_view("        \n")
        sb.write_view("          \n")
        sb.write_view("        \n")
      }
      None => {
        sb.write_view("        \n")
      }
    }
    if image.lock_aspect_ratio {
      sb.write_view("        \n")
      sb.write_view("          \n")
      sb.write_view("        \n")
    } else {
      sb.write_view("        \n")
    }
    sb.write_view("      \n")
    sb.write_view("      \n")
    sb.write_view("        \n")
    sb.write_view("        \n")
    sb.write_view("      \n")
    sb.write_view("      \n")
    sb.write_view("        \n")
    sb.write_view("      \n")
    sb.write_view("    \n")
    sb.write_view(
      "    \n",
    )
    match image.positioning {
      TwoCell => sb.write_view("  \n")
      Absolute => sb.write_view("  \n")
      OneCell => sb.write_view("  \n")
    }
  }
  for chart in charts {
    let (row, col) = cell_ref_to_rc(chart.reference)
    let col_idx = col - 1
    let row_idx = row - 1
    let (frame_id, next_obj_id) = allocate_drawing_numeric_id(
      obj_id, reserved_object_id_set,
    )
    obj_id = next_obj_id
    let col_off = match chart.drawing_offset_x_emu {
      Some(value) => value
      None =>
        drawing_pixel_offset_for_write(
          chart.offset_x,
          "drawing chart column offset",
        )
    }
    let row_off = match chart.drawing_offset_y_emu {
      Some(value) => value
      None =>
        drawing_pixel_offset_for_write(
          chart.offset_y,
          "drawing chart row offset",
        )
    }
    let cx = drawing_extent_for_write(
      match chart.drawing_width_emu {
        Some(value) => value
        None => chart.width_emu
      },
      "drawing chart width",
    )
    let cy = drawing_extent_for_write(
      match chart.drawing_height_emu {
        Some(value) => value
        None => chart.height_emu
      },
      "drawing chart height",
    )
    match chart.positioning {
      TwoCell => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        let (to_col_idx, to_col_off) = two_cell_axis_to(
          drawing_metrics.cols,
          col_idx,
          col_off,
          cx,
        )
        let (to_row_idx, to_row_off) = two_cell_axis_to(
          drawing_metrics.rows,
          row_idx,
          row_off,
          cy,
        )
        sb.write_view("    \n")
        sb.write_view("      \{to_col_idx}\n")
        sb.write_view("      \{to_col_off}\n")
        sb.write_view("      \{to_row_idx}\n")
        sb.write_view("      \{to_row_off}\n")
        sb.write_view("    \n")
      }
      Absolute => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        let (to_col_idx, to_col_off) = two_cell_axis_to(
          drawing_metrics.cols,
          col_idx,
          col_off,
          cx,
        )
        let (to_row_idx, to_row_off) = two_cell_axis_to(
          drawing_metrics.rows,
          row_idx,
          row_off,
          cy,
        )
        sb.write_view("    \n")
        sb.write_view("      \{to_col_idx}\n")
        sb.write_view("      \{to_col_off}\n")
        sb.write_view("      \{to_row_idx}\n")
        sb.write_view("      \{to_row_off}\n")
        sb.write_view("    \n")
      }
      OneCell => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        sb.write_view("    \n")
      }
    }
    sb.write_view("    \n")
    sb.write_view("      \n")
    sb.write_view(
      "        \n",
    )
    sb.write_view("        \n")
    sb.write_view("      \n")
    sb.write_view("      \n")
    sb.write_view("        \n")
    sb.write_view("        \n")
    sb.write_view("      \n")
    sb.write_view("      \n")
    sb.write_view(
      "        \n",
    )
    sb.write_view(
      "          \n",
    )
    sb.write_view("        \n")
    sb.write_view("      \n")
    sb.write_view("    \n")
    sb.write_view(
      "    \n",
    )
    match chart.positioning {
      TwoCell => sb.write_view("  \n")
      Absolute => sb.write_view("  \n")
      OneCell => sb.write_view("  \n")
    }
  }
  for shape in shapes {
    let (row, col) = cell_ref_to_rc(shape.reference)
    let col_idx = col - 1
    let row_idx = row - 1
    let offset_x = shape.format.offset_x.unwrap_or(0)
    let offset_y = shape.format.offset_y.unwrap_or(0)
    let col_off = match shape.drawing_offset_x_emu {
      Some(value) => value
      None =>
        drawing_pixel_offset_for_write(offset_x, "drawing shape column offset")
    }
    let row_off = match shape.drawing_offset_y_emu {
      Some(value) => value
      None =>
        drawing_pixel_offset_for_write(offset_y, "drawing shape row offset")
    }
    let (shape_id, next_obj_id) = allocate_drawing_numeric_id(
      obj_id, reserved_object_id_set,
    )
    obj_id = next_obj_id
    let cx = match shape.drawing_width_emu {
      Some(value) => drawing_extent_for_write(value, "drawing shape width")
      None =>
        drawing_scaled_extent_for_write(
          shape.width.to_double(),
          shape.scale_x,
          "drawing shape width",
        )
    }
    let cy = match shape.drawing_height_emu {
      Some(value) => drawing_extent_for_write(value, "drawing shape height")
      None =>
        drawing_scaled_extent_for_write(
          shape.height.to_double(),
          shape.scale_y,
          "drawing shape height",
        )
    }
    match shape.positioning {
      TwoCell => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        let (to_col_idx, to_col_off) = two_cell_axis_to(
          drawing_metrics.cols,
          col_idx,
          col_off,
          cx,
        )
        let (to_row_idx, to_row_off) = two_cell_axis_to(
          drawing_metrics.rows,
          row_idx,
          row_off,
          cy,
        )
        sb.write_view("    \n")
        sb.write_view("      \{to_col_idx}\n")
        sb.write_view("      \{to_col_off}\n")
        sb.write_view("      \{to_row_idx}\n")
        sb.write_view("      \{to_row_off}\n")
        sb.write_view("    \n")
      }
      Absolute => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        let (to_col_idx, to_col_off) = two_cell_axis_to(
          drawing_metrics.cols,
          col_idx,
          col_off,
          cx,
        )
        let (to_row_idx, to_row_off) = two_cell_axis_to(
          drawing_metrics.rows,
          row_idx,
          row_off,
          cy,
        )
        sb.write_view("    \n")
        sb.write_view("      \{to_col_idx}\n")
        sb.write_view("      \{to_col_off}\n")
        sb.write_view("      \{to_row_idx}\n")
        sb.write_view("      \{to_row_off}\n")
        sb.write_view("    \n")
      }
      OneCell => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        sb.write_view("    \n")
      }
    }
    sb.write_view("    \n")
    sb.write_view("      \n")
    let shape_name = if shape.name == "" {
      "Shape \{shape_id}"
    } else {
      shape.name
    }
    sb.write_view("        \n")
    sb.write_view("        \n")
    sb.write_view("      \n")
    sb.write_view("      \n")
    sb.write_view("        \n")
    sb.write_view("          \n")
    sb.write_view("          \n")
    sb.write_view("        \n")
    sb.write_view("        \n")
    if shape.fill_color != "" {
      let fill = shape.fill_color.replace_all(old="#", new="").to_upper()
      sb.write_view("         0 {
        let alpha = (100 - shape.fill_transparency) * 1000
        sb.write_view(
          "\">\n",
        )
      } else {
        sb.write_view("\"/>\n")
      }
    }
    if shape.line_color != "" || shape.line_width != 1.0 {
      sb.write_view("        \n")
      } else {
        let line = shape.line_color.replace_all(old="#", new="").to_upper()
        sb.write_view(">\n")
      }
    }
    sb.write_view("      \n")
    if shape.paragraph.length() > 0 || shape.text != "" {
      sb.write_view("      \n")
      sb.write_view("        \n")
      sb.write_view("        \n")
      let runs = if shape.paragraph.length() > 0 {
        shape.paragraph[:]
      } else {
        [{ text: shape.text, font: None }]
      }
      for run in runs {
        sb.write_view("        \n")
        sb.write_view("          \n")
        match run.font {
          Some(font) => {
            sb.write_view("             {
                sb.write_view(" u=\"")
                sb.write_xml_attr(value)
                sb.write_view("\"")
              }
              None => ()
            }
            match font.size {
              Some(size) =>
                sb.write_view(" sz=\"\{(size * 100.0).round().to_int()}\"")
              None => ()
            }
            sb.write_view(">")
            match font.family {
              Some(family) => {
                sb.write_view("")
                sb.write_view("")
                sb.write_view("")
              }
              None => ()
            }
            match font.color {
              Some(color) => {
                let normalized = color.replace_all(old="#", new="").to_upper()
                sb.write_view("")
              }
              None => ()
            }
            sb.write_view("\n")
          }
          None => sb.write_view("            \n")
        }
        let text_value = if run.text == "" { " " } else { run.text }
        if text_value.has_prefix(" ") ||
          text_value.has_suffix(" ") ||
          text_value.contains("\n") {
          sb.write_view("            ")
        } else {
          sb.write_view("            ")
        }
        sb.write_xml_text(text_value)
        sb.write_view("\n")
        sb.write_view("          \n")
        sb.write_view("        \n")
      }
      sb.write_view("      \n")
    }
    sb.write_view("    \n")
    sb.write_view(
      "    \n",
    )
    match shape.positioning {
      TwoCell => sb.write_view("  \n")
      Absolute => sb.write_view("  \n")
      OneCell => sb.write_view("  \n")
    }
  }
  for item in slicers {
    let slicer = item.slicer
    let (row, col) = cell_ref_to_rc(slicer.cell)
    let col_idx = col - 1
    let row_idx = row - 1
    let (frame_id, next_obj_id) = allocate_drawing_numeric_id(
      obj_id, reserved_object_id_set,
    )
    obj_id = next_obj_id
    let offset_x = match slicer.format.offset_x {
      Some(v) => v
      None => 0
    }
    let offset_y = match slicer.format.offset_y {
      Some(v) => v
      None => 0
    }
    let col_off = match slicer.drawing_offset_x_emu {
      Some(value) => value
      None =>
        drawing_pixel_offset_for_write(offset_x, "drawing slicer column offset")
    }
    let row_off = match slicer.drawing_offset_y_emu {
      Some(value) => value
      None =>
        drawing_pixel_offset_for_write(offset_y, "drawing slicer row offset")
    }
    let scale_x = match slicer.format.scale_x {
      Some(v) => if v <= 0.0 { 1.0 } else { v }
      None => 1.0
    }
    let scale_y = match slicer.format.scale_y {
      Some(v) => if v <= 0.0 { 1.0 } else { v }
      None => 1.0
    }
    let cx = match slicer.drawing_width_emu {
      Some(value) => drawing_extent_for_write(value, "drawing slicer width")
      None =>
        drawing_scaled_extent_for_write(
          slicer.width.to_double(),
          scale_x,
          "drawing slicer width",
        )
    }
    let cy = match slicer.drawing_height_emu {
      Some(value) => drawing_extent_for_write(value, "drawing slicer height")
      None =>
        drawing_scaled_extent_for_write(
          slicer.height.to_double(),
          scale_y,
          "drawing slicer height",
        )
    }
    let positioning = match slicer.format.positioning {
      Some(v) => v
      None => TwoCell
    }
    match positioning {
      TwoCell => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        let (to_col_idx, to_col_off) = two_cell_axis_to(
          drawing_metrics.cols,
          col_idx,
          col_off,
          cx,
        )
        let (to_row_idx, to_row_off) = two_cell_axis_to(
          drawing_metrics.rows,
          row_idx,
          row_off,
          cy,
        )
        sb.write_view("    \n")
        sb.write_view("      \{to_col_idx}\n")
        sb.write_view("      \{to_col_off}\n")
        sb.write_view("      \{to_row_idx}\n")
        sb.write_view("      \{to_row_off}\n")
        sb.write_view("    \n")
      }
      Absolute => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        let (to_col_idx, to_col_off) = two_cell_axis_to(
          drawing_metrics.cols,
          col_idx,
          col_off,
          cx,
        )
        let (to_row_idx, to_row_off) = two_cell_axis_to(
          drawing_metrics.rows,
          row_idx,
          row_off,
          cy,
        )
        sb.write_view("    \n")
        sb.write_view("      \{to_col_idx}\n")
        sb.write_view("      \{to_col_off}\n")
        sb.write_view("      \{to_row_idx}\n")
        sb.write_view("      \{to_row_off}\n")
        sb.write_view("    \n")
      }
      OneCell => {
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \{col_idx}\n")
        sb.write_view("      \{col_off}\n")
        sb.write_view("      \{row_idx}\n")
        sb.write_view("      \{row_off}\n")
        sb.write_view("    \n")
        sb.write_view("    \n")
      }
    }
    let alt_text = match slicer.format.alt_text {
      Some(v) => v
      None => ""
    }
    sb.write_view(
      "    \n",
    )
    if item.is_table_slicer {
      sb.write_view(
        "      \n",
      )
    } else {
      sb.write_view(
        "      \n",
      )
    }
    sb.write_view("        \n")
    sb.write_view("          \n")
    sb.write_view("            \n")
    sb.write_view("            \n")
    sb.write_view("          \n")
    sb.write_view("          \n")
    sb.write_view("            \n")
    sb.write_view("            \n")
    sb.write_view("          \n")
    sb.write_view("          \n")
    sb.write_view(
      "            \n",
    )
    sb.write_view(
      "              \n")
    sb.write_view("            \n")
    sb.write_view("          \n")
    sb.write_view("        \n")
    sb.write_view("      \n")
    sb.write_view("      \n")
    sb.write_view("        \n")
    sb.write_view("          \n")
    sb.write_view("            \n")
    sb.write_view("            \n")
    sb.write_view("          \n")
    sb.write_view("          \n")
    sb.write_view("            \n")
    sb.write_view("              \n")
    sb.write_view("              \n")
    sb.write_view("            \n")
    sb.write_view(
      "            \n",
    )
    sb.write_view(
      "            \n",
    )
    sb.write_view(
      "            \n",
    )
    sb.write_view("          \n")
    sb.write_view("          \n")
    sb.write_view(
      "            \n",
    )
    sb.write_view("            ")
    sb.write_xml_text(
      "This shape represents a table slicer. Table slicers are not supported in this version of Excel.",
    )
    sb.write_view("\n")
    sb.write_view("            ")
    sb.write_xml_text(
      "If the shape was modified in an earlier version of Excel, or if the workbook was saved in Excel 2007 or earlier, the slicer can't be used.",
    )
    sb.write_view("\n")
    sb.write_view("          \n")
    sb.write_view("        \n")
    sb.write_view("      \n")
    sb.write_view("    \n")
    let locked = match slicer.format.locked {
      Some(v) => v
      None => true
    }
    let print_object = match slicer.format.print_object {
      Some(v) => v
      None => true
    }
    sb.write_view(
      "    \n",
    )
    match positioning {
      TwoCell => sb.write_view("  \n")
      Absolute => sb.write_view("  \n")
      OneCell => sb.write_view("  \n")
    }
  }
  sb.write_view("")
  let grouped_xml = sb.to_string()
  let known_orders : Array[Int?] = []
  let mut needs_ordering = preserved_anchors.length() > 0
  for item in images {
    known_orders.push(item.image.drawing_order)
    if item.image.drawing_order is Some(_) {
      needs_ordering = true
    }
  }
  for item in charts {
    known_orders.push(item.drawing_order)
    if item.drawing_order is Some(_) {
      needs_ordering = true
    }
  }
  for shape in shapes {
    known_orders.push(shape.drawing_order)
    if shape.drawing_order is Some(_) {
      needs_ordering = true
    }
  }
  for item in slicers {
    known_orders.push(item.slicer.drawing_order)
    if item.slicer.drawing_order is Some(_) {
      needs_ordering = true
    }
  }
  if !needs_ordering {
    return grouped_xml
  }
  let generated_anchors = parse_drawing_anchors(grouped_xml)
  if generated_anchors.length() != known_orders.length() {
    raise InvalidSheetOperation(msg="generated drawing anchor count mismatch")
  }
  let ordered : Array[OrderedDrawingAnchor] = []
  for index, anchor in generated_anchors {
    ordered.push({
      xml: anchor.xml,
      order: known_orders[index],
      fallback_order: index,
    })
  }
  for index, anchor in preserved_anchors {
    ordered.push({
      xml: anchor.xml,
      order: Some(anchor.order),
      fallback_order: generated_anchors.length() + index,
    })
  }
  ordered.sort_by(compare_ordered_drawing_anchor)
  let output = LimitedXmlBuilder::new(budget)
  write_drawing_open(output)
  for anchor in ordered {
    output.write_view("  ")
    output.write_view(anchor.xml)
    output.write_view("\n")
  }
  output.write_view("")
  output.to_string()
}

///|
fn write_sheet_pr_xml(
  options : SheetPropsOptions,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("   {
      sb.write_view(" codeName=\"")
      sb.write_xml_attr(value)
      sb.write_view("\"")
    }
    None => ()
  }
  match options.enable_format_conditions_calculation {
    Some(value) =>
      sb.write_view(
        " enableFormatConditionsCalculation=\"\{bool_attr(value)}\"",
      )
    None => ()
  }
  match options.published {
    Some(value) => sb.write_view(" published=\"\{bool_attr(value)}\"")
    None => ()
  }
  let has_tab_color = options.tab_color_indexed is Some(_) ||
    options.tab_color_rgb is Some(_) ||
    options.tab_color_theme is Some(_) ||
    options.tab_color_tint is Some(_)
  let has_outline = options.outline_summary_below is Some(_) ||
    options.outline_summary_right is Some(_)
  let has_page_setup = options.auto_page_breaks is Some(_) ||
    options.fit_to_page is Some(_)
  if !has_tab_color && !has_outline && !has_page_setup {
    sb.write_view("/>\n")
    return sb.to_string()
  }
  sb.write_view(">\n")
  if has_tab_color {
    sb.write_view("     sb.write_view(" indexed=\"\{value}\"")
      None => ()
    }
    match options.tab_color_rgb {
      Some(value) => {
        sb.write_view(" rgb=\"")
        sb.write_xml_attr(value)
        sb.write_view("\"")
      }
      None => ()
    }
    match options.tab_color_theme {
      Some(value) => sb.write_view(" theme=\"\{value}\"")
      None => ()
    }
    match options.tab_color_tint {
      Some(value) => sb.write_view(" tint=\"\{value}\"")
      None => ()
    }
    sb.write_view("/>\n")
  }
  if has_outline {
    sb.write_view("     sb.write_view(" summaryBelow=\"\{bool_attr(value)}\"")
      None => ()
    }
    match options.outline_summary_right {
      Some(value) => sb.write_view(" summaryRight=\"\{bool_attr(value)}\"")
      None => ()
    }
    sb.write_view("/>\n")
  }
  if has_page_setup {
    sb.write_view("     sb.write_view(" autoPageBreaks=\"\{bool_attr(value)}\"")
      None => ()
    }
    match options.fit_to_page {
      Some(value) => sb.write_view(" fitToPage=\"\{bool_attr(value)}\"")
      None => ()
    }
    sb.write_view("/>\n")
  }
  sb.write_view("  \n")
  sb.to_string()
}

///|
fn write_sheet_format_pr_xml(
  options : SheetPropsOptions,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("   sb.write_view(" baseColWidth=\"\{value}\"")
    None => ()
  }
  match options.default_col_width {
    Some(value) => sb.write_view(" defaultColWidth=\"\{value}\"")
    None => ()
  }
  match options.default_row_height {
    Some(value) => sb.write_view(" defaultRowHeight=\"\{value}\"")
    None => ()
  }
  match options.custom_height {
    Some(value) => sb.write_view(" customHeight=\"\{bool_attr(value)}\"")
    None => ()
  }
  match options.zero_height {
    Some(value) => sb.write_view(" zeroHeight=\"\{bool_attr(value)}\"")
    None => ()
  }
  match options.thick_top {
    Some(value) => sb.write_view(" thickTop=\"\{bool_attr(value)}\"")
    None => ()
  }
  match options.thick_bottom {
    Some(value) => sb.write_view(" thickBottom=\"\{bool_attr(value)}\"")
    None => ()
  }
  sb.write_view("/>\n")
  sb.to_string()
}

///|
fn write_sheet_views_xml(
  sheet : Worksheet,
  budget? : WritePartBudget,
) -> String? raise XlsxError {
  if sheet.sheet_views.length() == 0 {
    return None
  }
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("  \n")
  for view in sheet.sheet_views {
    sb.write_view("     sb.write_view(" defaultGridColor=\"\{bool_attr(value)}\"")
      None => ()
    }
    match view.right_to_left {
      Some(value) => sb.write_view(" rightToLeft=\"\{bool_attr(value)}\"")
      None => ()
    }
    match view.show_formulas {
      Some(value) => sb.write_view(" showFormulas=\"\{bool_attr(value)}\"")
      None => ()
    }
    match view.show_grid_lines {
      Some(value) => sb.write_view(" showGridLines=\"\{bool_attr(value)}\"")
      None => ()
    }
    match view.show_row_col_headers {
      Some(value) => sb.write_view(" showRowColHeaders=\"\{bool_attr(value)}\"")
      None => ()
    }
    match view.show_ruler {
      Some(value) => sb.write_view(" showRuler=\"\{bool_attr(value)}\"")
      None => ()
    }
    match view.show_zeros {
      Some(value) => sb.write_view(" showZeros=\"\{bool_attr(value)}\"")
      None => ()
    }
    match view.top_left_cell {
      Some(value) => {
        sb.write_view(" topLeftCell=\"")
        sb.write_xml_attr(normalize_cell_ref_for_write(value))
        sb.write_view("\"")
      }
      None => ()
    }
    match view.view {
      Some(value) => {
        sb.write_view(" view=\"")
        sb.write_xml_attr(value)
        sb.write_view("\"")
      }
      None => ()
    }
    match view.zoom_scale {
      Some(value) => sb.write_view(" zoomScale=\"\{value}\"")
      None => ()
    }
    if view.tab_selected {
      sb.write_view(" tabSelected=\"1\"")
    }
    sb.write_view(" workbookViewId=\"\{view.workbook_view_id}\"")
    if view.pane is None && view.selection.length() == 0 {
      sb.write_view("/>\n")
      continue
    }
    sb.write_view(">\n")
    match view.pane {
      Some(pane) => {
        sb.write_view("      \n")
      }
      None => ()
    }
    for selection in view.selection {
      sb.write_view("      \n")
    }
    sb.write_view("    \n")
  }
  sb.write_view("  \n")
  Some(sb.to_string())
}

///|
fn write_chartsheet_xml(
  drawing_rel_id : Int,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("\n")
  sb.write_view(
    "\n",
  )
  sb.write_view("  \n")
  sb.write_view(
    "    \n",
  )
  sb.write_view("  \n")
  sb.write_view("  \n")
  sb.write_view("")
  sb.to_string()
}

///|
fn worksheet_dimension(sheet : Worksheet) -> String? raise XlsxError {
  match sheet.dimension_ref {
    Some(value) => return Some(value)
    None => ()
  }
  let mut has_any = false
  let mut min_row = 0
  let mut min_col = 0
  let mut max_row = 0
  let mut max_col = 0
  for cell in sheet.cells() {
    let row = cell.row
    let col = cell.col
    if !has_any {
      has_any = true
      min_row = row
      max_row = row
      min_col = col
      max_col = col
    } else {
      if row < min_row {
        min_row = row
      }
      if row > max_row {
        max_row = row
      }
      if col < min_col {
        min_col = col
      }
      if col > max_col {
        max_col = col
      }
    }
  }
  for range_ref in sheet.merged_cells() {
    let (row1, col1, row2, col2) = parse_range_ref(range_ref)
    if !has_any {
      has_any = true
      min_row = row1
      max_row = row2
      min_col = col1
      max_col = col2
    } else {
      if row1 < min_row {
        min_row = row1
      }
      if row2 > max_row {
        max_row = row2
      }
      if col1 < min_col {
        min_col = col1
      }
      if col2 > max_col {
        max_col = col2
      }
    }
  }
  for table in sheet.tables() {
    let (row1, col1, row2, col2) = parse_range_ref(table.range_ref)
    if !has_any {
      has_any = true
      min_row = row1
      max_row = row2
      min_col = col1
      max_col = col2
    } else {
      if row1 < min_row {
        min_row = row1
      }
      if row2 > max_row {
        max_row = row2
      }
      if col1 < min_col {
        min_col = col1
      }
      if col2 > max_col {
        max_col = col2
      }
    }
  }
  match sheet.auto_filter() {
    Some(filter) => {
      let (row1, col1, row2, col2) = parse_range_ref(filter.range_ref)
      if !has_any {
        has_any = true
        min_row = row1
        max_row = row2
        min_col = col1
        max_col = col2
      } else {
        if row1 < min_row {
          min_row = row1
        }
        if row2 > max_row {
          max_row = row2
        }
        if col1 < min_col {
          min_col = col1
        }
        if col2 > max_col {
          max_col = col2
        }
      }
    }
    None => ()
  }
  if !has_any {
    return None
  }
  let start_ref = cell_ref_from(min_row, min_col)
  let end_ref = cell_ref_from(max_row, max_col)
  Some("\{start_ref}:\{end_ref}")
}

///|
fn write_worksheet_xml(
  sheet : Worksheet,
  string_map : Map[String, Int],
  rich_text_map : Map[String, Int],
  hyperlink_rel_ids : Map[String, Int],
  hyperlinks : ArrayView[Hyperlink],
  drawing_rel_id : Int?,
  picture_rel_id : Int?,
  table_rel_ids : ArrayView[Int],
  _pivot_rel_ids : ArrayView[Int],
  slicer_rel_ids_x14 : ArrayView[Int],
  slicer_rel_ids_x15 : ArrayView[Int],
  legacy_drawing_rel_id : Int?,
  legacy_drawing_hf_rel_id : Int?,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  fn write_worksheet_ext_lst_xml(
    slicer_rel_ids_x14 : ArrayView[Int],
    slicer_rel_ids_x15 : ArrayView[Int],
    sparkline_groups : ArrayView[SparklineGroup],
    x14_data_bars : Map[String, X14DataBarProps],
    unknown_ext_blocks : ArrayView[String],
    budget : WritePartBudget?,
  ) -> String? raise XlsxError {
    fn write_x14_conditional_formattings_ext_block_xml(
      x14_data_bars : Map[String, X14DataBarProps],
      budget : WritePartBudget?,
    ) -> String? raise XlsxError {
      let by_sqref : Map[String, Array[(String, X14DataBarProps)]] = Map([])
      for id, props in x14_data_bars {
        if props.bar_direction == "" &&
          !props.bar_solid &&
          props.bar_border_color == "" {
          continue
        }
        match by_sqref.get(props.sqref) {
          Some(ids) => {
            ids.push((id, props))
            by_sqref[props.sqref] = ids
          }
          None => by_sqref[props.sqref] = [(id, props)]
        }
      }
      if by_sqref.length() == 0 {
        return None
      }
      let sb = LimitedXmlBuilder::new(budget)
      sb.write_view(
        "    \n",
      )
      sb.write_view(
        "      \n",
      )
      for sqref, entries in by_sqref {
        sb.write_view("        \n")
        for entry in entries {
          let (id, props) = entry
          sb.write_view("          \n")
          sb.write_view("            \n")
          sb.write_view("              \n")
          sb.write_view("              \n")
          if props.bar_border_color != "" {
            sb.write_view("              \n")
          }
          sb.write_view(
            "              \n",
          )
          sb.write_view("              \n")
          sb.write_view("            \n")
          sb.write_view("          \n")
        }
        sb.write_view("          ")
        sb.write_xml_text(sqref)
        sb.write_view("\n")
        sb.write_view("        \n")
      }
      sb.write_view("      \n")
      sb.write_view("    \n")
      Some(sb.to_string())
    }

    let mut has_conditional = false
    for _, props in x14_data_bars {
      if props.bar_direction != "" ||
        props.bar_solid ||
        props.bar_border_color != "" {
        has_conditional = true
        break
      }
    }
    let has_slicers = slicer_rel_ids_x14.length() > 0 ||
      slicer_rel_ids_x15.length() > 0
    let has_sparklines = sparkline_groups.length() > 0
    let has_unknown = unknown_ext_blocks.length() > 0
    if !has_slicers && !has_sparklines && !has_conditional && !has_unknown {
      return None
    }
    if has_sparklines && !has_slicers && !has_conditional && !has_unknown {
      return Some(
        write_sparkline_ext_xml(sheet.name(), sparkline_groups, budget?),
      )
    }
    let sb = LimitedXmlBuilder::new(budget)
    sb.write_view("  \n")
    if has_conditional {
      match
        write_x14_conditional_formattings_ext_block_xml(
          x14_data_bars,
          sb.remaining_budget(),
        ) {
        Some(block) => sb.write_view(block)
        None => ()
      }
    }
    if has_sparklines {
      sb.write_view(
        write_sparkline_ext_block_xml(
          sheet.name(),
          sparkline_groups,
          budget?=sb.remaining_budget(),
        ),
      )
    }
    if slicer_rel_ids_x14.length() > 0 {
      sb.write_view(
        "    \n",
      )
      sb.write_view(
        "      \n",
      )
      for rel_id in slicer_rel_ids_x14 {
        sb.write_view("        \n")
      }
      sb.write_view("      \n")
      sb.write_view("    \n")
    }
    if slicer_rel_ids_x15.length() > 0 {
      sb.write_view(
        "    \n",
      )
      sb.write_view(
        "      \n",
      )
      for rel_id in slicer_rel_ids_x15 {
        sb.write_view("        \n")
      }
      sb.write_view("      \n")
      sb.write_view("    \n")
    }
    if unknown_ext_blocks.length() > 0 {
      for block in unknown_ext_blocks {
        let block_text = block.to_string()
        if block_text.has_prefix("\n")
    Some(sb.to_string())
  }

  let sb = LimitedXmlBuilder::new(budget)
  sb.write_view("\n")
  let needs_rels = drawing_rel_id is Some(_) ||
    table_rel_ids.length() > 0 ||
    _pivot_rel_ids.length() > 0 ||
    slicer_rel_ids_x14.length() > 0 ||
    slicer_rel_ids_x15.length() > 0 ||
    hyperlink_rel_ids.length() > 0 ||
    legacy_drawing_rel_id is Some(_) ||
    legacy_drawing_hf_rel_id is Some(_) ||
    picture_rel_id is Some(_)
  let needs_x14_ns = sheet.sparkline_groups().length() > 0 ||
    sheet.x14_data_bars.length() > 0 ||
    slicer_rel_ids_x14.length() > 0 ||
    slicer_rel_ids_x15.length() > 0
  sb.write_view(
    "\n")
  match sheet.sheet_props {
    Some(options) =>
      if sheet_props_has_sheet_pr(options) {
        sb.write_view(
          write_sheet_pr_xml(options, budget?=sb.remaining_budget()),
        )
      }
    None => ()
  }
  match worksheet_dimension(sheet) {
    Some(value) => {
      sb.write_view("  \n")
    }
    None => ()
  }
  match write_sheet_views_xml(sheet, budget?=sb.remaining_budget()) {
    Some(value) => sb.write_view(value)
    None => ()
  }
  match sheet.sheet_props {
    Some(options) =>
      if sheet_props_has_sheet_format(options) {
        sb.write_view(
          write_sheet_format_pr_xml(options, budget?=sb.remaining_budget()),
        )
      }
    None => ()
  }
  if sheet.col_dimensions.length() > 0 {
    let cols : Array[Int] = []
    for col, _ in sheet.col_dimensions {
      cols.push(col)
    }
    cols.sort_by((a, b) => a - b)
    sb.write_view("  \n")
    for col in cols {
      let dim = sheet.col_dimensions
        .get(col)
        .unwrap_or({
          width: None,
          hidden: false,
          outline_level: 0,
          style_id: None,
        })
      write_col_attributes(sb, col, dim)
    }
    sb.write_view("  \n")
  }
  sb.write_view("  \n")
  let cells = sheet.sorted_cells()
  let row_cells : Map[Int, Array[Cell]] = Map([])
  for cell in cells {
    let entries = match row_cells.get(cell.row) {
      Some(value) => value
      None => []
    }
    entries.push(cell)
    row_cells[cell.row] = entries
  }
  let row_keys : Array[Int] = []
  let row_present : Map[Int, Bool] = Map([])
  for row, _ in row_cells {
    row_keys.push(row)
    row_present[row] = true
  }
  for row, _ in sheet.row_dimensions {
    if !row_present.contains(row) {
      row_keys.push(row)
    }
  }
  row_keys.sort_by((a, b) => a - b)
  for row in row_keys {
    let row_entries = match row_cells.get(row) {
      Some(value) => value
      None => []
    }
    sb.write_view("     write_row_attributes(sb, dim)
      None => ()
    }
    if row_entries.length() == 0 {
      sb.write_view("/>\n")
      continue
    }
    sb.write_view(">\n")
    for cell in row_entries {
      sb.write_view("       {
          match cell.value_type {
            String =>
              if cell.formula_value_present {
                sb.write_view(" t=\"str\"")
              }
            Bool => sb.write_view(" t=\"b\"")
            Error => sb.write_view(" t=\"e\"")
            Number => ()
          }
          sb.write_view(">")
          sb.write_view(" {
              sb.write_view(" t=\"")
              sb.write_view(formula_type_to_ooxml(kind))
              sb.write_view("\"")
            }
            None => ()
          }
          match cell.formula_ref {
            Some(value) => {
              sb.write_view(" ref=\"")
              sb.write_xml_attr(value)
              sb.write_view("\"")
            }
            None => ()
          }
          match cell.formula_shared_index {
            Some(si) => sb.write_view(" si=\"\{si}\"")
            None => ()
          }
          sb.write_view(">")
          sb.write_xml_text(formula)
          sb.write_view("")
          if cell.formula_value_present {
            sb.write_view("")
            match cell.value_type {
              String => sb.write_xml_text(cell.value)
              Error => sb.write_xml_text(cell.value)
              _ => sb.write_view(cell.value)
            }
            sb.write_view("")
          }
          sb.write_view("\n")
        }
        None =>
          match cell.value_type {
            String => {
              let idx = match cell.rich_text {
                Some(_) =>
                  match rich_text_map.get(cell.reference) {
                    Some(value) => value
                    None => raise InvalidSharedString(index=cell.row)
                  }
                None =>
                  match string_map.get(cell.value) {
                    Some(value) => value
                    None => raise InvalidSharedString(index=cell.row)
                  }
              }
              sb.write_view(" t=\"s\">\{idx}\n")
            }
            Bool => {
              sb.write_view(" t=\"b\">")
              sb.write_view(cell.value)
              sb.write_view("\n")
            }
            Error => {
              sb.write_view(" t=\"e\">")
              sb.write_xml_text(cell.value)
              sb.write_view("\n")
            }
            Number => {
              sb.write_view(">")
              sb.write_view(cell.value)
              sb.write_view("\n")
            }
          }
      }
    }
    sb.write_view("    \n")
  }
  sb.write_view("  \n")
  match sheet.sheet_protection() {
    Some(protection) =>
      sb.write_view(
        write_sheet_protection_xml(protection, budget?=sb.remaining_budget()),
      )
    None => ()
  }
  match sheet.auto_filter() {
    Some(filter) =>
      sb.write_view(
        write_auto_filter_xml(filter, budget?=sb.remaining_budget()),
      )
    None => ()
  }
  if sheet.merged_cells().length() > 0 {
    sb.write_view("  \n")
    for range_ref in sheet.merged_cells() {
      sb.write_view("    \n")
    }
    sb.write_view("  \n")
  }
  for xml in sheet.conditional_formats() {
    sb.write_view("  ")
    sb.write_view(xml)
    sb.write_view("\n")
  }
  if sheet.data_validations().length() > 0 {
    sb.write_view(
      "  \n",
    )
    for xml in sheet.data_validations() {
      sb.write_view("    ")
      sb.write_view(xml)
      sb.write_view("\n")
    }
    sb.write_view("  \n")
  }
  if hyperlinks.length() > 0 {
    let mut wrote = false
    for link in hyperlinks {
      if link.link_type == Unset {
        continue
      }
      if !wrote {
        sb.write_view("  \n")
        wrote = true
      }
      sb.write_view("     sb.write_view(" r:id=\"rId\{rel_id}\"")
          None => raise InvalidHyperlink(msg="hyperlink relationship missing")
        }
        match link.location {
          Some(value) => {
            sb.write_view(" location=\"")
            sb.write_xml_attr(value)
            sb.write_view("\"")
          }
          None => ()
        }
      } else {
        sb.write_view(" location=\"")
        sb.write_xml_attr(link.target)
        sb.write_view("\"")
      }
      match link.display {
        Some(value) => {
          sb.write_view(" display=\"")
          sb.write_xml_attr(value)
          sb.write_view("\"")
        }
        None => ()
      }
      match link.tooltip {
        Some(value) => {
          sb.write_view(" tooltip=\"")
          sb.write_xml_attr(value)
          sb.write_view("\"")
        }
        None => ()
      }
      sb.write_view("/>\n")
    }
    if wrote {
      sb.write_view("  \n")
    }
  }
  match sheet.page_margins {
    Some(margins) => {
      match write_print_options_xml(margins, budget?=sb.remaining_budget()) {
        Some(xml) => sb.write_view(xml)
        None => ()
      }
      let merged = page_margins_with_defaults(Some(margins))
      sb.write_view(
        write_page_margins_xml(merged, budget?=sb.remaining_budget()),
      )
    }
    None => ()
  }
  match sheet.page_layout {
    Some(layout) =>
      sb.write_view(write_page_setup_xml(layout, budget?=sb.remaining_budget()))
    None => ()
  }
  match sheet.header_footer() {
    Some(options) =>
      sb.write_view(
        write_header_footer_xml(options, budget?=sb.remaining_budget()),
      )
    None => ()
  }
  match
    write_page_breaks_xml(
      "rowBreaks",
      sheet.row_breaks(),
      budget?=sb.remaining_budget(),
    ) {
    Some(xml) => sb.write_view(xml)
    None => ()
  }
  match
    write_page_breaks_xml(
      "colBreaks",
      sheet.col_breaks(),
      budget?=sb.remaining_budget(),
    ) {
    Some(xml) => sb.write_view(xml)
    None => ()
  }
  match
    write_ignored_errors_xml(
      sheet.ignored_errors(),
      budget?=sb.remaining_budget(),
    ) {
    Some(value) => sb.write_view(value)
    None => ()
  }
  match drawing_rel_id {
    Some(rel_id) => sb.write_view("  \n")
    None => ()
  }
  match legacy_drawing_rel_id {
    Some(rel_id) => sb.write_view("  \n")
    None => ()
  }
  match legacy_drawing_hf_rel_id {
    Some(rel_id) =>
      sb.write_view("  \n")
    None => ()
  }
  match picture_rel_id {
    Some(rel_id) => sb.write_view("  \n")
    None => ()
  }
  if table_rel_ids.length() > 0 {
    sb.write_view("  \n")
    for rel_id in table_rel_ids {
      sb.write_view("    \n")
    }
    sb.write_view("  \n")
  }
  match
    write_worksheet_ext_lst_xml(
      slicer_rel_ids_x14,
      slicer_rel_ids_x15,
      sheet.sparkline_groups(),
      sheet.x14_data_bars,
      sheet.unknown_ext_blocks,
      sb.remaining_budget(),
    ) {
    Some(xml) => sb.write_view(xml)
    None => ()
  }
  sb.write_view("")
  sb.to_string()
}

///|
let default_theme_xml =
  #|
  #|
  #|  
  #|    
  #|      
  #|      
  #|      
  #|      
  #|      
  #|      
  #|      
  #|      
  #|      
  #|      
  #|      
  #|      
  #|    
  #|    
  #|      
  #|        
  #|        
  #|        
  #|      
  #|      
  #|        
  #|        
  #|        
  #|      
  #|    
  #|    
  #|      
  #|        
  #|        
  #|          
  #|            
  #|            
  #|            
  #|          
  #|          
  #|        
  #|        
  #|          
  #|            
  #|            
  #|            
  #|          
  #|          
  #|        
  #|      
  #|      
  #|        
  #|        
  #|        
  #|      
  #|      
  #|        
  #|        
  #|        
  #|      
  #|      
  #|        
  #|        
  #|        
  #|          
  #|            
  #|            
  #|            
  #|          
  #|          
  #|        
  #|      
  #|    
  #|  
  #|  
  #|  
  #|

///|
fn write_theme_xml(
  theme_colors : Array[String]?,
  budget? : WritePartBudget,
) -> String raise XlsxError {
  fn write_theme_color(
    sb : LimitedXmlBuilder,
    value : StringView,
  ) -> Unit raise XlsxError {
    let normalized = if value.length() == 8 && value.has_prefix("FF") {
      value[2:]
    } else {
      value
    }
    for character in normalized {
      if character != '#' {
        sb.write_char(character.to_ascii_uppercase())
      }
    }
  }

  match theme_colors {
    Some(colors) =>
      if colors.length() >= 10 {
        // Order matches parse_theme_colors: lt1, dk1, lt2, dk2, accent1..accent6.
        let sb = LimitedXmlBuilder::new(budget)
        sb.write_view("\n")
        sb.write_view(
          "\n",
        )
        sb.write_view("  \n")
        sb.write_view("    \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view(
          "      \n",
        )
        sb.write_view("    \n")
        sb.write_view("    \n")
        sb.write_view("      \n")
        sb.write_view(
          "        \n",
        )
        sb.write_view("        \n")
        sb.write_view("        \n")
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view(
          "        \n",
        )
        sb.write_view("        \n")
        sb.write_view("        \n")
        sb.write_view("      \n")
        sb.write_view("    \n")
        sb.write_view("    \n")
        sb.write_view("      \n")
        sb.write_view(
          "        \n",
        )
        sb.write_view(
          "        \n",
        )
        sb.write_view(
          "        \n",
        )
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view(
          "        \n",
        )
        sb.write_view(
          "        \n",
        )
        sb.write_view(
          "        \n",
        )
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view("        \n")
        sb.write_view("        \n")
        sb.write_view(
          "        \n",
        )
        sb.write_view("      \n")
        sb.write_view("      \n")
        sb.write_view(
          "        \n",
        )
        sb.write_view(
          "        \n",
        )
        sb.write_view(
          "        \n",
        )
        sb.write_view("      \n")
        sb.write_view("    \n")
        sb.write_view("  \n")
        sb.write_view("  \n")
        sb.write_view("  \n")
        sb.write_view("\n")
        sb.to_string()
      } else {
        default_theme_xml
      }
    None => default_theme_xml
  }
}

///|
/// Archive builder used by bounded writes. Limits are checked before
/// `Archive::add` takes its defensive payload copy, so aggregate growth cannot
/// first materialize an oversized retained archive and only fail at ZIP output.
priv struct WriteArchiveBuilder {
  archive : @zip.Archive
  limits : WriteLimits?
  mut entry_count : Int
  mut total_uncompressed_bytes : Int
}

///|
fn WriteArchiveBuilder::new(limits : WriteLimits?) -> WriteArchiveBuilder {
  {
    archive: @zip.Archive::new(),
    limits,
    entry_count: 0,
    total_uncompressed_bytes: 0,
  }
}

///|
fn WriteArchiveBuilder::next_composition_budget(
  self : WriteArchiveBuilder,
) -> WriteCompositionBudgetTracker raise XlsxError {
  match self.limits {
    Some(limits) if self.entry_count >= limits.max_archive_entries =>
      raise ResourceLimitExceeded(
        kind="entry_count",
        limit=limits.max_archive_entries,
        actual=bounded_actual_above_limit(limits.max_archive_entries),
      )
    _ => ()
  }
  {
    limits: self.limits,
    archived_bytes: self.total_uncompressed_bytes,
    retained_bytes: 0,
  }
}

///|
fn WriteArchiveBuilder::next_part_budget(
  self : WriteArchiveBuilder,
) -> WritePartBudget? raise XlsxError {
  match self.limits {
    Some(limits) => {
      if self.entry_count >= limits.max_archive_entries {
        raise ResourceLimitExceeded(
          kind="entry_count",
          limit=limits.max_archive_entries,
          actual=bounded_actual_above_limit(limits.max_archive_entries),
        )
      }
      let aggregate_remaining = limits.max_total_uncompressed_bytes -
        self.total_uncompressed_bytes
      if limits.max_entry_uncompressed_bytes <= aggregate_remaining {
        Some({
          maximum: limits.max_entry_uncompressed_bytes,
          resource_kind: "entry_uncompressed_bytes",
          resource_limit: limits.max_entry_uncompressed_bytes,
        })
      } else {
        Some({
          maximum: aggregate_remaining,
          resource_kind: "total_uncompressed_bytes",
          resource_limit: limits.max_total_uncompressed_bytes,
        })
      }
    }
    None => None
  }
}

///|
/// Adds an already-retained text part after checking its exact UTF-8 length and
/// before allocating the encoded byte buffer.
fn WriteArchiveBuilder::add_text(
  self : WriteArchiveBuilder,
  name : String,
  text : StringView,
) -> Unit raise XlsxError {
  match self.next_part_budget() {
    Some(budget) =>
      match xml_utf8_length_within(text, budget.maximum) {
        Some(_) => ()
        None =>
          raise ResourceLimitExceeded(
            kind=budget.resource_kind,
            limit=budget.resource_limit,
            actual=bounded_actual_above_limit(budget.resource_limit),
          )
      }
    None => ()
  }
  self.add(name, @encoding/utf8.encode(text))
}

///|
fn WriteArchiveBuilder::add(
  self : WriteArchiveBuilder,
  name : String,
  data : BytesView,
) -> Unit raise XlsxError {
  match self.limits {
    Some(limits) => {
      if self.entry_count >= limits.max_archive_entries {
        raise ResourceLimitExceeded(
          kind="entry_count",
          limit=limits.max_archive_entries,
          actual=bounded_actual_above_limit(limits.max_archive_entries),
        )
      }
      let next_entry_count = self.entry_count + 1
      let size = data.length()
      if size > limits.max_entry_uncompressed_bytes {
        raise ResourceLimitExceeded(
          kind="entry_uncompressed_bytes",
          limit=limits.max_entry_uncompressed_bytes,
          actual=size,
        )
      }
      if size >
        limits.max_total_uncompressed_bytes - self.total_uncompressed_bytes {
        raise ResourceLimitExceeded(
          kind="total_uncompressed_bytes",
          limit=limits.max_total_uncompressed_bytes,
          actual=bounded_actual_above_limit(limits.max_total_uncompressed_bytes),
        )
      }
      let next_total = self.total_uncompressed_bytes + size
      self.entry_count = next_entry_count
      self.total_uncompressed_bytes = next_total
    }
    None => ()
  }
  self.archive.add(name, data)
}

///|
fn WriteArchiveBuilder::finish(self : WriteArchiveBuilder) -> @zip.Archive {
  self.archive
}

///|
fn write_with_io(
  workbook : Workbook,
  io_context : WorkbookIOContext,
  limits? : WriteLimits,
) -> Bytes raise XlsxError {
  let (shared_strings, string_map, rich_text_map, total_count) = collect_shared_strings(
    workbook,
  )
  let include_shared = shared_strings.length() > 0
  let sheet_count = workbook.sheets().length()
  let formula_cells = collect_formula_cells(workbook)
  let include_calc_chain = formula_cells.length() > 0
  let rel_offset = 2 +
    (if include_shared { 1 } else { 0 }) +
    (if include_calc_chain { 1 } else { 0 })
  let chartsheet_rel_start = sheet_count + rel_offset + 1
  let manifest = @ooxml.WorkbookManifest::new(
    sheet_count,
    include_shared_strings=include_shared,
    include_calc_chain~,
  )
  let mut has_path_content_type = false
  match io_context.file_path {
    Some(path) =>
      match extension_from_path(path) {
        Some(ext) =>
          match workbook_content_type_for_extension(ext) {
            Some(content_type) => {
              manifest.set_workbook_content_type(content_type)
              has_path_content_type = true
            }
            None => ()
          }
        None => ()
      }
    None => ()
  }
  if has_path_content_type {
    manifest.add_content_default("bin", ct_vba_project)
  }
  for i, _sheet in workbook.chart_sheets() {
    let sheet_id = i + 1
    manifest.add_content_override(
      content_override_path(chartsheet_part_path(sheet_id)),
      ct_chartsheet,
    )
    manifest.add_workbook_relationship(
      rel_chartsheet,
      chartsheet_workbook_rel_target(sheet_id),
    )
  }
  match workbook.vba_project {
    Some(_) => {
      if !has_path_content_type {
        manifest.set_workbook_content_type(ct_workbook_macro)
      }
      manifest.add_content_default("bin", ct_vba_project)
      manifest.add_workbook_relationship(rel_vba_project, "vbaProject.bin")
    }
    None => ()
  }
  let archive = WriteArchiveBuilder::new(limits)
  let root_rels_xml = match archive.next_part_budget() {
    Some(budget) =>
      match manifest.root_rels_xml_limited(budget.maximum) {
        Some(xml) => xml
        None =>
          raise ResourceLimitExceeded(
            kind=budget.resource_kind,
            limit=budget.resource_limit,
            actual=bounded_actual_above_limit(budget.resource_limit),
          )
      }
    None => manifest.root_rels_xml()
  }
  archive.add_text(root_rels_part_path, root_rels_xml)
  let tables_by_sheet : Array[Array[(Int, Table)]] = []
  let mut next_table_id = 1
  for sheet in workbook.sheets() {
    for table in sheet.tables() {
      if table.id >= next_table_id {
        next_table_id = table.id + 1
      }
    }
  }
  for sheet in workbook.sheets() {
    let sheet_tables : Array[(Int, Table)] = []
    for table in sheet.tables() {
      let table_id = if table.id > 0 {
        table.id
      } else {
        let id = next_table_id
        next_table_id = next_table_id + 1
        id
      }
      sheet_tables.push((table_id, table))
    }
    tables_by_sheet.push(sheet_tables)
  }
  let pivots_by_sheet : Array[Array[(Int, Int, PivotTable)]] = []
  let mut next_pivot_table_id = 1
  let mut next_cache_id = 1
  for sheet in workbook.sheets() {
    for pivot in sheet.pivot_tables() {
      if pivot.table_id >= next_pivot_table_id {
        next_pivot_table_id = pivot.table_id + 1
      }
      if pivot.cache_id >= next_cache_id {
        next_cache_id = pivot.cache_id + 1
      }
    }
  }
  for sheet in workbook.sheets() {
    let sheet_pivots : Array[(Int, Int, PivotTable)] = []
    for pivot in sheet.pivot_tables() {
      let table_id = if pivot.table_id > 0 {
        pivot.table_id
      } else {
        let id = next_pivot_table_id
        next_pivot_table_id = next_pivot_table_id + 1
        id
      }
      let cache_id = if pivot.cache_id > 0 {
        pivot.cache_id
      } else {
        let id = next_cache_id
        next_cache_id = next_cache_id + 1
        id
      }
      sheet_pivots.push((table_id, cache_id, pivot))
    }
    pivots_by_sheet.push(sheet_pivots)
  }
  let table_lookup : Map[String, (Int, Table)] = Map([])
  for i, sheet in workbook.sheets() {
    let sheet_key = normalize_sheet_name(sheet.name())
    for entry in tables_by_sheet[i] {
      let (table_id, table) = entry
      table_lookup["\{sheet_key}|\{table.name.to_lower()}"] = (table_id, table)
    }
  }
  let pivot_lookup : Map[String, (Int, Int, PivotTable)] = Map([])
  for i, sheet in workbook.sheets() {
    let sheet_key = normalize_sheet_name(sheet.name())
    let sheet_id = i + 1
    for entry in pivots_by_sheet[i] {
      let (_pivot_id, cache_id, pivot) = entry
      if pivot.name != "" {
        pivot_lookup["\{sheet_key}|\{pivot.name.to_lower()}"] = (
          cache_id, sheet_id, pivot,
        )
      }
    }
  }
  let pivot_cache_slicer_id_by_cache_id : Map[Int, Int] = Map([])
  let mut next_pivot_cache_slicer_id = 1
  for sheet_pivots in pivots_by_sheet {
    for entry in sheet_pivots {
      let (_pivot_id, cache_id, pivot) = entry
      match parse_pivot_cache_slicer_id(pivot.cache_definition_xml) {
        Some(id) => {
          pivot_cache_slicer_id_by_cache_id[cache_id] = id
          if id >= next_pivot_cache_slicer_id {
            next_pivot_cache_slicer_id = id + 1
          }
        }
        None => ()
      }
    }
  }
  let slicer_cache_id_by_name : Map[String, Int] = Map([])
  let slicer_cache_rel_id_by_name : Map[String, String] = Map([])
  let resolved_slicer_caches : Array[ResolvedSlicerCache] = []
  let workbook_slicer_cache_rel_ids_x14 : Array[String] = []
  let workbook_slicer_cache_rel_ids_x15 : Array[String] = []
  let mut next_slicer_cache_id = 1
  for sheet in workbook.sheets() {
    for slicer in sheet.slicers() {
      let cache_name = slicer.cache
      if slicer_cache_id_by_name.contains(cache_name) {
        continue
      }
      let cache_id = next_slicer_cache_id
      next_slicer_cache_id = next_slicer_cache_id + 1
      slicer_cache_id_by_name[cache_name] = cache_id
      let source_sheet_key = normalize_sheet_name(slicer.table_sheet)
      let source_key = "\{source_sheet_key}|\{slicer.table_name.to_lower()}"
      match table_lookup.get(source_key) {
        Some((table_id, table)) => {
          let mut column : Int? = None
          for i, col_name in table.columns {
            if col_name.to_lower() == slicer.source_name.to_lower() {
              column = Some(i + 1)
              break
            }
          }
          let col = match column {
            Some(v) => v
            None =>
              raise InvalidSheetOperation(msg="slicer table field missing")
          }
          let rel_id = manifest.add_workbook_relationship_with_id(
            rel_slicer_cache,
            slicer_cache_workbook_rel_target(cache_id),
          )
          slicer_cache_rel_id_by_name[cache_name] = rel_id
          workbook_slicer_cache_rel_ids_x15.push(rel_id)
          resolved_slicer_caches.push({
            cache_id,
            cache_name,
            source_name: slicer.source_name,
            item_desc: slicer.item_desc,
            source: Table(table_id, col),
          })
        }
        None =>
          match pivot_lookup.get(source_key) {
            Some((pivot_cache_ref_id, pivot_sheet_id, pivot)) => {
              let slicer_pivot_cache_id = match
                pivot_cache_slicer_id_by_cache_id.get(pivot_cache_ref_id) {
                Some(id) => id
                None => {
                  let id = next_pivot_cache_slicer_id
                  next_pivot_cache_slicer_id = next_pivot_cache_slicer_id + 1
                  pivot_cache_slicer_id_by_cache_id[pivot_cache_ref_id] = id
                  id
                }
              }
              let rel_id = manifest.add_workbook_relationship_with_id(
                rel_slicer_cache,
                slicer_cache_workbook_rel_target(cache_id),
              )
              slicer_cache_rel_id_by_name[cache_name] = rel_id
              workbook_slicer_cache_rel_ids_x14.push(rel_id)
              resolved_slicer_caches.push({
                cache_id,
                cache_name,
                source_name: slicer.source_name,
                item_desc: slicer.item_desc,
                source: Pivot(slicer_pivot_cache_id, pivot_sheet_id, pivot.name),
              })
            }
            None => raise InvalidSheetOperation(msg="slicer source missing")
          }
      }
    }
  }
  let workbook_pivot_cache_rel_entries : Array[(Int, String)] = []
  let pivot_cache_rel_written : Map[Int, Bool] = Map([])
  for sheet_pivots in pivots_by_sheet {
    for entry in sheet_pivots {
      let (_pivot_id, cache_id, _pivot) = entry
      if pivot_cache_rel_written.contains(cache_id) {
        continue
      }
      pivot_cache_rel_written[cache_id] = true
      let rel_id = manifest.add_workbook_relationship_with_id(
        rel_pivot_cache,
        pivot_cache_workbook_rel_target(cache_id),
      )
      workbook_pivot_cache_rel_entries.push((cache_id, rel_id))
    }
  }
  archive.add_text(
    workbook_part_path,
    write_workbook_xml(
      workbook,
      chartsheet_rel_start,
      workbook_pivot_cache_rel_entries,
      workbook_slicer_cache_rel_ids_x14,
      workbook_slicer_cache_rel_ids_x15,
      budget?=archive.next_part_budget(),
    ),
  )
  let added_extensions : Map[String, Bool] = Map([])
  let mut image_index = 1
  let mut chart_index = 1
  let mut vml_index = 1
  let mut comment_index = 1
  let mut drawing_index = sheet_count + 1
  let mut slicer_part_index = 1
  for i, sheet in workbook.sheets() {
    let sheet_id = i + 1
    let preserved_drawing = preserved_drawing_write_plan(sheet, sheet_id)
    for part in preserved_drawing.parts {
      archive.add(part.path, part.data)
      manifest.add_content_override(
        content_override_path(part.path),
        part.content_type,
      )
      if part.relationships.length() > 0 {
        archive.add_text(
          rels_path_for_part(part.path),
          write_preserved_drawing_rels_xml(
            part.relationships,
            budget?=archive.next_part_budget(),
          ),
        )
      }
    }
    let sheet_tables = tables_by_sheet[i]
    let sheet_pivots = pivots_by_sheet[i]
    let has_drawing = sheet.images().length() > 0 ||
      sheet.charts().length() > 0 ||
      sheet.shapes().length() > 0 ||
      sheet.slicers().length() > 0 ||
      preserved_drawing.anchors.length() > 0
    let mut rel_id = 1
    let drawing_rel_id = if has_drawing {
      let id = rel_id
      rel_id = rel_id + 1
      Some(id)
    } else {
      None
    }
    let table_rel_ids : Array[Int] = []
    let table_targets : Array[String] = []
    for entry in sheet_tables {
      let (table_id, _table) = entry
      table_rel_ids.push(rel_id)
      table_targets.push(table_rel_target(table_id))
      rel_id = rel_id + 1
    }
    let pivot_rel_ids : Array[Int] = []
    let pivot_targets : Array[String] = []
    for entry in sheet_pivots {
      let (pivot_id, _cache_id, _pivot) = entry
      pivot_rel_ids.push(rel_id)
      pivot_targets.push(pivot_table_rel_target(pivot_id))
      rel_id = rel_id + 1
    }
    let slicer_targets : Array[String] = []
    let slicer_rel_ids_x14 : Array[Int] = []
    let slicer_rel_ids_x15 : Array[Int] = []
    let pivot_slicers : Array[Slicer] = []
    let table_slicers : Array[Slicer] = []
    for slicer in sheet.slicers() {
      let source_sheet_key = normalize_sheet_name(slicer.table_sheet)
      let source_key = "\{source_sheet_key}|\{slicer.table_name.to_lower()}"
      if table_lookup.contains(source_key) {
        table_slicers.push(slicer)
      } else {
        pivot_slicers.push(slicer)
      }
    }
    let mut pivot_slicer_part_id : Int? = None
    let mut table_slicer_part_id : Int? = None
    if pivot_slicers.length() > 0 {
      let part_id = slicer_part_index
      slicer_part_index = slicer_part_index + 1
      pivot_slicer_part_id = Some(part_id)
      slicer_rel_ids_x14.push(rel_id)
      slicer_targets.push(slicer_rel_target(part_id))
      rel_id = rel_id + 1
    }
    if table_slicers.length() > 0 {
      let part_id = slicer_part_index
      slicer_part_index = slicer_part_index + 1
      table_slicer_part_id = Some(part_id)
      slicer_rel_ids_x15.push(rel_id)
      slicer_targets.push(slicer_rel_target(part_id))
      rel_id = rel_id + 1
    }
    let hyperlink_targets : Array[String] = []
    let hyperlink_rel_ids : Map[String, Int] = Map([])
    for link in sheet.hyperlinks() {
      if link.link_type == External {
        hyperlink_targets.push(link.target)
        hyperlink_rel_ids[link.reference] = rel_id
        rel_id = rel_id + 1
      }
    }
    let comment_targets : Array[String] = []
    let stored_comments = sheet.stored_comments()
    let comment_xml = if stored_comments.length() > 0 {
      Some(
        write_comments_xml(stored_comments, budget?=archive.next_part_budget()),
      )
    } else {
      None
    }
    match comment_xml {
      Some(xml) => {
        let comment_id = comment_index
        comment_index = comment_index + 1
        comment_targets.push(comment_rel_target(comment_id))
        archive.add_text(comment_part_path(comment_id), xml)
        manifest.add_content_override(
          content_override_path(comment_part_path(comment_id)),
          ct_comments,
        )
      }
      None => ()
    }
    if comment_targets.length() > 0 {
      rel_id = rel_id + comment_targets.length()
    }
    let vml_targets : Array[String] = []
    // Comment/control VML can require several retained intermediates before
    // the final part is known. Give the whole composition one parent budget,
    // debit each generated intermediate immediately, and archive the final
    // part below before beginning any other generated VML.
    let legacy_vml_budget = archive.next_composition_budget()
    let source_vml = sheet.vml_drawing_xml()
    let form_controls = sheet.form_controls()
    let (base_vml, missing_comments) = match source_vml {
      Some(xml) =>
        if stored_comments.length() > 0 {
          let (reconciled, missing, _) = reconcile_vml_comment_shapes(
            xml,
            stored_comments,
            budget?=legacy_vml_budget.remaining_budget(),
          )
          if reconciled != xml {
            legacy_vml_budget.consume(reconciled)
          }
          (Some(reconciled), missing)
        } else {
          (Some(xml), [])
        }
      None => (None, [])
    }
    let generated_comment_count = match source_vml {
      Some(_) => missing_comments.length()
      None => stored_comments.length()
    }
    let needs_generated_vml_ids = generated_comment_count > 0 ||
      form_controls.length() > 0
    let (used_vml_shape_ids, max_retained_shape_id) = if needs_generated_vml_ids {
      match base_vml {
        Some(xml) => vml_used_shape_numeric_ids(xml)
        None => (Map([]), 1024)
      }
    } else {
      (Map([]), 1024)
    }
    let next_comment_shape_id = if generated_comment_count > 0 {
      allocate_vml_shape_id_range(
        used_vml_shape_ids, max_retained_shape_id, 1025, generated_comment_count,
      )
    } else {
      1025
    }
    reserve_vml_shape_id_range(
      used_vml_shape_ids, next_comment_shape_id, generated_comment_count,
    )
    let preferred_after_comments = if generated_comment_count > 0 {
      let last_comment_shape_id = next_comment_shape_id +
        generated_comment_count -
        1
      last_comment_shape_id.max(max_retained_shape_id)
    } else {
      max_retained_shape_id
    }
    let form_control_shape_id_start = if form_controls.length() > 0 {
      allocate_vml_shape_id_range(
        used_vml_shape_ids,
        preferred_after_comments,
        2048,
        form_controls.length(),
      )
    } else {
      2048
    }
    let comment_vml_xml = match source_vml {
      Some(_) =>
        if missing_comments.length() > 0 {
          let xml = write_comments_vml(
            sheet_id,
            missing_comments,
            shape_id_start=next_comment_shape_id,
            budget?=legacy_vml_budget.remaining_budget(),
          )
          legacy_vml_budget.consume(xml)
          Some(xml)
        } else {
          None
        }
      None =>
        if stored_comments.length() > 0 {
          let xml = write_comments_vml(
            sheet_id,
            stored_comments,
            shape_id_start=next_comment_shape_id,
            budget?=legacy_vml_budget.remaining_budget(),
          )
          legacy_vml_budget.consume(xml)
          Some(xml)
        } else {
          None
        }
    }
    let form_control_vml_xml = if form_controls.length() > 0 {
      let xml = write_form_controls_vml(
        sheet_id,
        sheet,
        form_controls,
        shape_id_start=form_control_shape_id_start,
        budget?=legacy_vml_budget.remaining_budget(),
      )
      legacy_vml_budget.consume(xml)
      Some(xml)
    } else {
      None
    }
    let merged_vml = match base_vml {
      Some(xml) =>
        match form_control_vml_xml {
          Some(control_xml) => {
            let merged = merge_vml_drawings(
              xml,
              control_xml,
              budget?=legacy_vml_budget.remaining_budget(),
            )
            legacy_vml_budget.consume(merged)
            Some(merged)
          }
          None => Some(xml)
        }
      None => form_control_vml_xml
    }
    let legacy_drawing_xml = match merged_vml {
      Some(xml) =>
        match comment_vml_xml {
          Some(comment_xml) => {
            let merged = merge_vml_drawings(
              xml,
              comment_xml,
              budget?=legacy_vml_budget.remaining_budget(),
            )
            legacy_vml_budget.consume(merged)
            Some(merged)
          }
          None => Some(xml)
        }
      None => comment_vml_xml
    }
    let legacy_drawing_rel_id = match legacy_drawing_xml {
      Some(xml) => {
        let id = rel_id
        rel_id = rel_id + 1
        let vml_id = vml_index
        vml_index = vml_index + 1
        vml_targets.push(vml_rel_target(vml_id))
        archive.add_text(vml_part_path(vml_id), xml)
        manifest.add_content_override(
          content_override_path(vml_part_path(vml_id)),
          ct_vml,
        )
        Some(id)
      }
      None => None
    }
    let header_footer_images = sheet.header_footer_images
    let header_footer_vml_budget = archive.next_composition_budget()
    let header_footer_vml = if header_footer_images.length() > 0 {
      let xml = write_header_footer_vml(
        sheet_id,
        header_footer_images,
        budget?=header_footer_vml_budget.remaining_budget(),
      )
      header_footer_vml_budget.consume(xml)
      Some(xml)
    } else {
      None
    }
    let header_footer_shape_ids : Array[String] = []
    if header_footer_images.length() > 0 {
      for image in header_footer_images {
        header_footer_shape_ids.push(
          header_footer_image_shape_id(
            image.position,
            image.is_footer,
            image.first_page,
          ),
        )
      }
    }
    let legacy_drawing_hf_xml = match sheet.vml_drawing_hf_xml() {
      Some(xml) =>
        match header_footer_vml {
          Some(extra_xml) => {
            let filtered = filter_vml_shapes_by_id(
              xml,
              header_footer_shape_ids,
              budget?=header_footer_vml_budget.remaining_budget(),
            )
            header_footer_vml_budget.consume(filtered)
            let merged = merge_vml_drawings_with_shapetype(
              filtered,
              extra_xml,
              "_x0000_t75",
              budget?=header_footer_vml_budget.remaining_budget(),
            )
            header_footer_vml_budget.consume(merged)
            Some(merged)
          }
          None => Some(xml)
        }
      None => header_footer_vml
    }
    let legacy_drawing_hf_ids : (Int, Int)? = match legacy_drawing_hf_xml {
      Some(xml) => {
        let id = rel_id
        rel_id = rel_id + 1
        let vml_id = vml_index
        vml_index = vml_index + 1
        vml_targets.push(vml_rel_target(vml_id))
        archive.add_text(vml_part_path(vml_id), xml)
        manifest.add_content_override(
          content_override_path(vml_part_path(vml_id)),
          ct_vml,
        )
        Some((id, vml_id))
      }
      None => None
    }
    let header_footer_image_targets : Array[String] = []
    if header_footer_images.length() > 0 {
      for image in header_footer_images {
        let file_name = "image\{image_index}.\{image.extension}"
        image_index = image_index + 1
        archive.add(media_part_path(file_name), image.data)
        header_footer_image_targets.push(media_rel_target(file_name))
        if !added_extensions.contains(image.extension) {
          manifest.add_content_default(image.extension, image.content_type)
          added_extensions.set(image.extension, true)
        }
      }
    }
    match legacy_drawing_hf_ids {
      Some((_id, vml_id)) if header_footer_image_targets.length() > 0 => {
        let vml_rels_xml = write_vml_rels_xml(
          header_footer_image_targets,
          budget?=archive.next_part_budget(),
        )
        archive.add_text(vml_rels_part_path(vml_id), vml_rels_xml)
      }
      _ => ()
    }
    let legacy_drawing_hf_rel_id = match legacy_drawing_hf_ids {
      Some((id, _vml_id)) => Some(id)
      None => None
    }
    let picture_targets : Array[String] = []
    let picture_rel_id = match sheet.sheet_background() {
      Some(background) => {
        let id = rel_id
        rel_id = rel_id + 1
        let file_name = "image\{image_index}.\{background.extension}"
        image_index = image_index + 1
        archive.add(media_part_path(file_name), background.data)
        picture_targets.push(media_rel_target(file_name))
        if !added_extensions.contains(background.extension) {
          manifest.add_content_default(
            background.extension,
            background.content_type,
          )
          added_extensions.set(background.extension, true)
        }
        Some(id)
      }
      None => None
    }
    let sheet_xml = write_worksheet_xml(
      sheet,
      string_map,
      rich_text_map,
      hyperlink_rel_ids,
      sheet.hyperlinks(),
      drawing_rel_id,
      picture_rel_id,
      table_rel_ids,
      pivot_rel_ids,
      slicer_rel_ids_x14,
      slicer_rel_ids_x15,
      legacy_drawing_rel_id,
      legacy_drawing_hf_rel_id,
      budget?=archive.next_part_budget(),
    )
    archive.add_text(worksheet_part_path(sheet_id), sheet_xml)
    if drawing_rel_id is Some(_) ||
      table_targets.length() > 0 ||
      pivot_targets.length() > 0 ||
      slicer_targets.length() > 0 ||
      hyperlink_targets.length() > 0 ||
      comment_targets.length() > 0 ||
      vml_targets.length() > 0 ||
      picture_targets.length() > 0 {
      let drawing_target = match drawing_rel_id {
        Some(_) => Some(drawing_rel_target(sheet_id))
        None => None
      }
      let sheet_rels_xml = write_sheet_rels_xml(
        drawing_target,
        table_targets,
        pivot_targets,
        slicer_targets,
        hyperlink_targets,
        comment_targets,
        vml_targets,
        picture_targets,
        budget?=archive.next_part_budget(),
      )
      archive.add_text(worksheet_rels_part_path(sheet_id), sheet_rels_xml)
    }
    for entry in sheet_tables {
      let (table_id, table) = entry
      let table_xml = write_table_xml(
        table_id,
        table,
        budget?=archive.next_part_budget(),
      )
      archive.add_text(table_part_path(table_id), table_xml)
      manifest.add_content_override(
        content_override_path(table_part_path(table_id)),
        ct_table,
      )
    }
    for entry in sheet_pivots {
      let (pivot_id, cache_id, pivot) = entry
      archive.add_text(pivot_table_part_path(pivot_id), pivot.table_xml)
      manifest.add_content_override(
        content_override_path(pivot_table_part_path(pivot_id)),
        ct_pivot_table,
      )
      let pivot_rels_xml = write_pivot_table_rels_xml(
        cache_id,
        budget?=archive.next_part_budget(),
      )
      archive.add_text(pivot_table_rels_part_path(pivot_id), pivot_rels_xml)
      let cache_definition_xml = match
        pivot_cache_slicer_id_by_cache_id.get(cache_id) {
        Some(slicer_pivot_cache_id) =>
          ensure_pivot_cache_slicer_id(
            pivot.cache_definition_xml,
            slicer_pivot_cache_id,
            budget?=archive.next_part_budget(),
          )
        None => pivot.cache_definition_xml
      }
      archive.add_text(
        pivot_cache_definition_part_path(cache_id),
        cache_definition_xml,
      )
      manifest.add_content_override(
        content_override_path(pivot_cache_definition_part_path(cache_id)),
        ct_pivot_cache_def,
      )
      match pivot.cache_records_xml {
        Some(records) => {
          archive.add_text(pivot_cache_records_part_path(cache_id), records)
          manifest.add_content_override(
            content_override_path(pivot_cache_records_part_path(cache_id)),
            ct_pivot_cache_records,
          )
          let cache_rels_xml = write_pivot_cache_rels_xml(
            cache_id,
            budget?=archive.next_part_budget(),
          )
          archive.add_text(
            pivot_cache_definition_rels_part_path(cache_id),
            cache_rels_xml,
          )
        }
        None => ()
      }
    }
    match pivot_slicer_part_id {
      Some(part_id) => {
        let slicers_xml = write_slicers_part_xml(
          pivot_slicers,
          budget?=archive.next_part_budget(),
        )
        archive.add_text(slicer_part_path(part_id), slicers_xml)
        manifest.add_content_override(
          content_override_path(slicer_part_path(part_id)),
          ct_slicer,
        )
      }
      None => ()
    }
    match table_slicer_part_id {
      Some(part_id) => {
        let slicers_xml = write_slicers_part_xml(
          table_slicers,
          budget?=archive.next_part_budget(),
        )
        archive.add_text(slicer_part_path(part_id), slicers_xml)
        manifest.add_content_override(
          content_override_path(slicer_part_path(part_id)),
          ct_slicer,
        )
      }
      None => ()
    }
    if !has_drawing {
      continue
    }
    let image_targets : Array[(Int, String)] = []
    let chart_targets : Array[(Int, String)] = []
    let drawing_images : Array[DrawingImage] = []
    let drawing_hyperlinks : Array[DrawingHyperlink] = []
    let drawing_charts : Array[DrawingChart] = []
    let drawing_shapes : Array[Shape] = []
    let drawing_slicers : Array[DrawingSlicer] = []
    let reserved_relationship_ids : Set[String] = Set([])
    for id in preserved_drawing.relationship_ids {
      reserved_relationship_ids.add(id)
    }
    let mut rel_id = 1
    for image in sheet.images() {
      let file_name = "image\{image_index}.\{image.extension}"
      image_index = image_index + 1
      archive.add(media_part_path(file_name), image.data)
      let (image_rel_id, next_rel_id) = allocate_drawing_relationship_id(
        rel_id, reserved_relationship_ids,
      )
      rel_id = next_rel_id
      let mut hyperlink_rel_id : Int? = None
      if image.hyperlink != "" && image.hyperlink_type != Unset {
        let (link_rel_id, next_rel_id) = allocate_drawing_relationship_id(
          rel_id, reserved_relationship_ids,
        )
        rel_id = next_rel_id
        drawing_hyperlinks.push({
          rel_id: link_rel_id,
          target: image.hyperlink,
          link_type: image.hyperlink_type,
        })
        hyperlink_rel_id = Some(link_rel_id)
      }
      image_targets.push((image_rel_id, media_rel_target(file_name)))
      drawing_images.push({ image, rel_id: image_rel_id, hyperlink_rel_id })
      if !added_extensions.contains(image.extension) {
        manifest.add_content_default(image.extension, image.content_type)
        added_extensions.set(image.extension, true)
      }
    }
    for chart in sheet.charts() {
      let file_name = "chart\{chart_index}.xml"
      chart_index = chart_index + 1
      archive.add_text(chart_part_path(file_name), chart.xml)
      let (chart_rel_id, next_rel_id) = allocate_drawing_relationship_id(
        rel_id, reserved_relationship_ids,
      )
      rel_id = next_rel_id
      chart_targets.push((chart_rel_id, chart_rel_target(file_name)))
      drawing_charts.push({
        reference: chart.reference,
        offset_x: chart.offset_x,
        offset_y: chart.offset_y,
        width_emu: chart.width_emu,
        height_emu: chart.height_emu,
        print_object: chart.print_object,
        locked: chart.locked,
        positioning: chart.positioning,
        rel_id: chart_rel_id,
        drawing_offset_x_emu: chart.drawing_offset_x_emu,
        drawing_offset_y_emu: chart.drawing_offset_y_emu,
        drawing_width_emu: chart.drawing_width_emu,
        drawing_height_emu: chart.drawing_height_emu,
        drawing_order: chart.drawing_order,
      })
      manifest.add_content_override(
        content_override_path(chart_part_path(file_name)),
        ct_chart,
      )
    }
    manifest.add_content_override(
      content_override_path(drawing_part_path(sheet_id)),
      ct_drawing,
    )
    for shape in sheet.shapes() {
      drawing_shapes.push(shape)
    }
    for slicer in sheet.slicers() {
      let source_sheet_key = normalize_sheet_name(slicer.table_sheet)
      let source_key = "\{source_sheet_key}|\{slicer.table_name.to_lower()}"
      let is_table_slicer = table_lookup.contains(source_key)
      drawing_slicers.push({ slicer, is_table_slicer })
    }
    let drawing_xml = write_drawing_xml(
      Some(sheet),
      drawing_images,
      drawing_charts,
      drawing_shapes,
      drawing_slicers,
      preserved_anchors=preserved_drawing.anchors,
      reserved_object_ids=preserved_drawing.object_ids,
      budget?=archive.next_part_budget(),
    )
    archive.add_text(drawing_part_path(sheet_id), drawing_xml)
    let drawing_rels_xml = write_drawing_rels_xml(
      image_targets,
      chart_targets,
      drawing_hyperlinks,
      preserved_relationships=preserved_drawing.drawing_relationships,
      budget?=archive.next_part_budget(),
    )
    archive.add_text(drawing_rels_part_path(sheet_id), drawing_rels_xml)
  }
  for entry in resolved_slicer_caches {
    let cache_id = entry.cache_id
    let cache_xml = match entry.source {
      Table(table_id, column) =>
        write_slicer_cache_xml_table(
          entry.cache_name,
          entry.source_name,
          table_id,
          column,
          entry.item_desc,
          budget?=archive.next_part_budget(),
        )
      Pivot(pivot_cache_id, pivot_sheet_id, pivot_name) =>
        write_slicer_cache_xml_pivot(
          entry.cache_name,
          entry.source_name,
          pivot_sheet_id,
          pivot_name,
          pivot_cache_id,
          entry.item_desc,
          budget?=archive.next_part_budget(),
        )
    }
    archive.add_text(slicer_cache_part_path(cache_id), cache_xml)
    manifest.add_content_override(
      content_override_path(slicer_cache_part_path(cache_id)),
      ct_slicer_cache,
    )
  }
  for i, sheet in workbook.chart_sheets() {
    let sheet_id = i + 1
    let chart_name = "chart\{chart_index}.xml"
    chart_index = chart_index + 1
    archive.add_text(chart_part_path(chart_name), sheet.chart_xml())
    manifest.add_content_override(
      content_override_path(chart_part_path(chart_name)),
      ct_chart,
    )
    let drawing_id = drawing_index
    drawing_index = drawing_index + 1
    manifest.add_content_override(
      content_override_path(drawing_part_path(drawing_id)),
      ct_drawing,
    )
    let drawing_images : Array[DrawingImage] = []
    let drawing_charts : Array[DrawingChart] = [
      {
        reference: "A1",
        offset_x: 0,
        offset_y: 0,
        width_emu: emu_per_pixel * 480,
        height_emu: emu_per_pixel * 260,
        print_object: true,
        locked: true,
        positioning: OneCell,
        rel_id: 1,
        drawing_offset_x_emu: None,
        drawing_offset_y_emu: None,
        drawing_width_emu: None,
        drawing_height_emu: None,
        drawing_order: None,
      },
    ]
    let drawing_xml = write_drawing_xml(
      None,
      drawing_images,
      drawing_charts,
      [],
      [],
      budget?=archive.next_part_budget(),
    )
    archive.add_text(drawing_part_path(drawing_id), drawing_xml)
    let chart_targets : Array[(Int, String)] = [
      (1, chart_rel_target(chart_name)),
    ]
    let drawing_rels_xml = write_drawing_rels_xml(
      [],
      chart_targets,
      [],
      budget?=archive.next_part_budget(),
    )
    archive.add_text(drawing_rels_part_path(drawing_id), drawing_rels_xml)
    let chartsheet_xml = write_chartsheet_xml(
      1,
      budget?=archive.next_part_budget(),
    )
    archive.add_text(chartsheet_part_path(sheet_id), chartsheet_xml)
    let chartsheet_rels_xml = write_sheet_rels_xml(
      Some(drawing_rel_target(drawing_id)),
      [],
      [],
      [],
      [],
      [],
      [],
      [],
      budget?=archive.next_part_budget(),
    )
    archive.add_text(chartsheet_rels_part_path(sheet_id), chartsheet_rels_xml)
  }
  match workbook.vba_project {
    Some(value) => archive.add(vba_project_part_path, value)
    None => ()
  }
  if include_shared {
    archive.add_text(
      shared_strings_part_path,
      write_shared_strings_xml(
        shared_strings,
        total_count,
        budget?=archive.next_part_budget(),
      ),
    )
  }
  if include_calc_chain {
    archive.add_text(
      calc_chain_part_path,
      write_calc_chain_xml(formula_cells, budget?=archive.next_part_budget()),
    )
  }
  archive.add_text(
    styles_part_path,
    write_styles_xml(
      workbook.styles(),
      workbook.conditional_styles(),
      workbook.default_font(),
      workbook.default_table_style,
      workbook.default_pivot_style,
      workbook.indexed_colors,
      workbook.mru_colors_xml,
      workbook.styles_ext_lst_xml,
      budget?=archive.next_part_budget(),
    ),
  )
  archive.add_text(
    theme_part_path,
    match workbook.theme_xml {
      Some(xml) => xml
      None =>
        write_theme_xml(
          workbook.theme_colors,
          budget?=archive.next_part_budget(),
        )
    },
  )
  archive.add_text(
    core_props_part_path,
    write_core_properties_xml(
      workbook.core_properties(),
      budget?=archive.next_part_budget(),
    ),
  )
  archive.add_text(
    app_props_part_path,
    write_app_properties_xml(
      workbook.app_properties(),
      budget?=archive.next_part_budget(),
    ),
  )
  let mut has_custom_props = false
  for prop in workbook.custom_properties() {
    if prop.value is Some(_) {
      has_custom_props = true
      break
    }
  }
  if has_custom_props {
    manifest.add_custom_properties()
    archive.add_text(
      custom_props_part_path,
      write_custom_properties_xml(
        workbook.custom_properties(),
        budget?=archive.next_part_budget(),
      ),
    )
  }
  let workbook_rels_xml = match archive.next_part_budget() {
    Some(budget) =>
      match manifest.workbook_rels_xml_limited(budget.maximum) {
        Some(xml) => xml
        None =>
          raise ResourceLimitExceeded(
            kind=budget.resource_kind,
            limit=budget.resource_limit,
            actual=bounded_actual_above_limit(budget.resource_limit),
          )
      }
    None => manifest.workbook_rels_xml()
  }
  archive.add_text(workbook_rels_part_path, workbook_rels_xml)
  let content_types_xml = match archive.next_part_budget() {
    Some(budget) =>
      match manifest.content_types_xml_limited(budget.maximum) {
        Some(xml) => xml
        None =>
          raise ResourceLimitExceeded(
            kind=budget.resource_kind,
            limit=budget.resource_limit,
            actual=bounded_actual_above_limit(budget.resource_limit),
          )
      }
    None => manifest.content_types_xml()
  }
  archive.add_text(content_types_part_path, content_types_xml)
  let archive = archive.finish()
  match limits {
    Some(policy) =>
      @zip.write_limited(archive, max_output_bytes=policy.max_output_bytes) catch {
        OutputLimitExceeded(limit~) =>
          raise ResourceLimitExceeded(
            kind="output_bytes",
            limit~,
            actual=bounded_actual_above_limit(limit),
          )
        err => raise UnsupportedFeature(msg=repr(err))
      }
    None =>
      match io_context.zip_writer {
        Some(writer) =>
          writer(archive) catch {
            err => raise UnsupportedFeature(msg=repr(err))
          }
        None =>
          @zip.write(archive) catch {
            err => raise UnsupportedFeature(msg=repr(err))
          }
      }
  }
}

///|
pub fn write(workbook : Workbook) -> Bytes raise XlsxError {
  write_with_io(workbook, workbook_io_context(workbook))
}

///|
/// Serializes a workbook under a fail-closed construction and output policy.
/// Generated parts are bounded before archive retention, then the ZIP writer
/// proves the final package size before allocating its output buffer. A custom
/// zip writer installed on the workbook is intentionally bypassed so it cannot
/// weaken this policy.
pub fn write_limited(
  workbook : Workbook,
  limits : WriteLimits,
) -> Bytes raise XlsxError {
  write_with_io(workbook, workbook_io_context(workbook), limits~)
}

///|
pub fn write_with_password(
  workbook : Workbook,
  password : String,
) -> Bytes raise XlsxError {
  let raw = write_with_io(workbook, workbook_io_context(workbook))
  if password.length() == 0 {
    raw
  } else {
    encrypt_package(raw, password)
  }
}

///|
fn wb_count_substr(text : StringView, needle : StringView) -> Int {
  let mut count = 0
  let mut rest = text.to_owned()
  let target = needle.to_owned()
  if target == "" {
    return 0
  }
  while true {
    match rest.find(target) {
      Some(pos) => {
        count = count + 1
        let next = pos + target.length()
        rest = rest[next:].to_owned()
      }
      None => break
    }
  }
  count
}

///|
fn wb_drawing_image_with_offset(
  positioning : PicturePositioning,
  offset_x : Int,
) -> DrawingImage {
  let image : Image = {
    reference: "B3",
    data: @encoding/utf8.encode("img"),
    extension: "png",
    content_type: "image/png",
    offset_x,
    offset_y: 0,
    scale_x: 1.0,
    scale_y: 1.0,
    width_emu: 1000,
    height_emu: 2000,
    print_object: true,
    locked: true,
    hyperlink: "",
    hyperlink_type: Unset,
    name: "",
    alt_text: "",
    lock_aspect_ratio: false,
    positioning,
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  }
  { image, rel_id: 1, hyperlink_rel_id: None }
}

///|
fn wb_drawing_image(positioning : PicturePositioning) -> DrawingImage {
  wb_drawing_image_with_offset(positioning, 0)
}

///|
fn wb_drawing_chart(positioning : PicturePositioning) -> DrawingChart {
  {
    reference: "C4",
    offset_x: 0,
    offset_y: 0,
    width_emu: 3000,
    height_emu: 4000,
    print_object: true,
    locked: true,
    positioning,
    rel_id: 1,
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  }
}

///|
fn wb_shape_for_anchor(
  positioning : PicturePositioning,
) -> Shape raise XlsxError {
  let font : RichTextFont = {
    bold: false,
    italic: false,
    strike: true,
    outline: false,
    shadow: false,
    condense: false,
    extended: false,
    underline: None,
    size: Some(11.0),
    color: None,
    color_theme: None,
    color_indexed: None,
    color_tint: None,
    charset: None,
    family_number: None,
    scheme: None,
    vert_align: None,
    family: None,
  }
  Shape::new(
    "D5",
    "rect",
    paragraph=[{ text: "shape", font: Some(font) }],
    line_width=2.0,
    positioning~,
  )
}

///|
fn wb_drawing_slicer(
  name : String,
  positioning : PicturePositioning?,
  scale_x : Double?,
  scale_y : Double?,
  is_table_slicer : Bool,
) -> DrawingSlicer {
  let format = GraphicOptions::new()
  match positioning {
    Some(value) => format.positioning = Some(value)
    None => ()
  }
  match scale_x {
    Some(value) => format.scale_x = Some(value)
    None => ()
  }
  match scale_y {
    Some(value) => format.scale_y = Some(value)
    None => ()
  }
  let slicer : Slicer = {
    name,
    cache: "Slicer_" + name,
    source_name: name,
    cell: "C3",
    table_sheet: "Sheet1",
    table_name: "Table1",
    caption: "",
    macro_name: "",
    width: 200,
    height: 200,
    display_header: None,
    item_desc: false,
    format,
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  }
  { slicer, is_table_slicer }
}

///|
test "write hardening wb: write_drawing_xml uses None-sheet fallback for two-cell anchors" {
  let xml = write_drawing_xml(None, [wb_drawing_image(TwoCell)], [], [], [])
  inspect(xml.contains(""), content="true")
  inspect(xml.contains("1"), content="true")
  inspect(xml.contains("2"), content="true")
  inspect(xml.contains("1000"), content="true")
  inspect(xml.contains("2000"), content="true")
}

///|
test "write hardening wb: write_drawing_xml uses None-sheet fallback for absolute anchors" {
  let xml = write_drawing_xml(None, [wb_drawing_image(Absolute)], [], [], [])
  inspect(
    xml.contains(""),
    content="true",
  )
  inspect(xml.contains("1"), content="true")
  inspect(xml.contains("2"), content="true")
  inspect(xml.contains("1000"), content="true")
  inspect(xml.contains("2000"), content="true")
}

///|
test "write hardening wb: write_shared_strings rich-text color and preserve-space branches" {
  let font_indexed_tint : RichTextFont = {
    bold: false,
    italic: false,
    strike: false,
    outline: false,
    shadow: false,
    condense: false,
    extended: false,
    underline: None,
    size: None,
    color: None,
    color_theme: None,
    color_indexed: Some(64),
    color_tint: Some(0.25),
    charset: None,
    family_number: None,
    scheme: None,
    vert_align: None,
    family: None,
  }
  let font_rgb_tint : RichTextFont = {
    bold: false,
    italic: false,
    strike: false,
    outline: false,
    shadow: false,
    condense: false,
    extended: false,
    underline: None,
    size: None,
    color: Some("FF00AA00"),
    color_theme: None,
    color_indexed: None,
    color_tint: Some(0.5),
    charset: None,
    family_number: None,
    scheme: None,
    vert_align: None,
    family: None,
  }
  let font_tint_only : RichTextFont = {
    bold: false,
    italic: false,
    strike: false,
    outline: false,
    shadow: false,
    condense: false,
    extended: false,
    underline: None,
    size: None,
    color: None,
    color_theme: None,
    color_indexed: None,
    color_tint: Some(0.75),
    charset: None,
    family_number: None,
    scheme: None,
    vert_align: None,
    family: None,
  }
  let font_theme_no_tint : RichTextFont = {
    bold: false,
    italic: false,
    strike: false,
    outline: false,
    shadow: false,
    condense: false,
    extended: false,
    underline: None,
    size: None,
    color: None,
    color_theme: Some(5),
    color_indexed: None,
    color_tint: None,
    charset: None,
    family_number: None,
    scheme: None,
    vert_align: None,
    family: None,
  }
  let entries : Array[SharedStringEntry] = [
    { text: "I", runs: Some([{ text: "I", font: Some(font_indexed_tint) }]) },
    { text: "R", runs: Some([{ text: "R", font: Some(font_rgb_tint) }]) },
    { text: "T", runs: Some([{ text: "T", font: Some(font_tint_only) }]) },
    { text: "M", runs: Some([{ text: "M", font: Some(font_theme_no_tint) }]) },
    { text: " plain", runs: None },
  ]
  let xml = write_shared_strings_xml(entries, 5)
  inspect(xml.contains(""), content="true")
  inspect(
    xml.contains(""),
    content="true",
  )
  inspect(xml.contains(""), content="true")
  inspect(wb_count_substr(xml, "= 2, content="true")
  inspect(xml.contains(""), content="true")
}

///|
test "write hardening wb: write_drawing_xml covers chart none-sheet anchor branches" {
  let xml = write_drawing_xml(
    None,
    [],
    [wb_drawing_chart(TwoCell), wb_drawing_chart(Absolute)],
    [],
    [],
  )
  inspect(
    xml.contains(""),
    content="true",
  )
  inspect(wb_count_substr(xml, ""), content="2")
  inspect(wb_count_substr(xml, "") >= 2, content="true")
}

///|
test "write hardening wb: write_drawing_xml covers shape none-sheet and run-font branches" {
  let shape_two = wb_shape_for_anchor(TwoCell) catch {
    err => fail("shape init failed: \{repr(err)}")
  }
  let shape_abs = wb_shape_for_anchor(Absolute) catch {
    err => fail("shape init failed: \{repr(err)}")
  }
  let xml = write_drawing_xml(None, [], [], [shape_two, shape_abs], [])
  inspect(
    xml.contains(""),
    content="true",
  )
  inspect(xml.contains(""), content="true")
  inspect(xml.contains("strike=\"sngStrike\""), content="true")
  inspect(wb_count_substr(xml, ""), content="2")
}

///|
test "write hardening wb: two_cell_axis_to skips zero-size cells" {
  let sheet = Worksheet::new("Sheet1")
  sheet.set_col_visible(2, false)
  sheet.set_row_visible(3, false)
  let xml = write_drawing_xml(
    Some(sheet),
    [wb_drawing_image(TwoCell)],
    [],
    [],
    [],
  )
  inspect(xml.contains(""), content="true")
  inspect(xml.contains("2"), content="true")
}

///|
test "write hardening wb: two-cell anchors enforce the worksheet boundary" {
  let chart : DrawingChart = {
    reference: "XFD1",
    offset_x: 0,
    offset_y: 0,
    width_emu: emu_per_pixel * 480,
    height_emu: emu_per_pixel * 260,
    print_object: true,
    locked: true,
    positioning: TwoCell,
    rel_id: 1,
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  }
  let result : Result[String, Error] = Ok(
    write_drawing_xml(Some(Worksheet::new("Sheet1")), [], [chart], [], []),
  ) catch {
    error => Err(error)
  }
  match result {
    Err(XlsxError::InvalidSheetOperation(msg~)) =>
      inspect(msg, content="drawing extends beyond worksheet grid")
    _ => fail("expected drawing grid boundary error")
  }
}

///|
test "write hardening wb: signed offsets and exact end markers stay readable" {
  let image = wb_drawing_image_with_offset(TwoCell, -1)
  let signed_xml = write_drawing_xml(None, [image], [], [], [])
  inspect(signed_xml.contains("-9525"), content="true")

  let chart : DrawingChart = {
    reference: "XFD1",
    offset_x: 0,
    offset_y: 0,
    width_emu: 64 * emu_per_pixel,
    height_emu: emu_per_pixel,
    print_object: true,
    locked: true,
    positioning: TwoCell,
    rel_id: 1,
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  }
  let boundary_xml = write_drawing_xml(
    Some(Worksheet::new("Sheet1")),
    [],
    [chart],
    [],
    [],
  )
  inspect(boundary_xml.contains("16384"), content="true")
  inspect(boundary_xml.contains("0"), content="true")
}

///|
test "write hardening wb: drawing arithmetic rejects overflow" {
  let image = wb_drawing_image_with_offset(OneCell, 0x7fff_ffff)
  let offset_result : Result[String, Error] = Ok(
    write_drawing_xml(None, [image], [], [], []),
  ) catch {
    error => Err(error)
  }
  match offset_result {
    Err(XlsxError::InvalidSheetOperation(msg~)) =>
      inspect(msg, content="drawing image column offset out of range")
    _ => fail("expected drawing offset overflow")
  }

  let extent_result : Result[Int, Error] = Ok(
    drawing_scaled_extent_for_write(2147483647.0, 2.0, "drawing shape width"),
  ) catch {
    error => Err(error)
  }
  match extent_result {
    Err(XlsxError::InvalidSheetOperation(msg~)) =>
      inspect(msg, content="drawing shape width out of range")
    _ => fail("expected drawing extent overflow")
  }
}

///|
test "write hardening wb: write_drawing_xml covers chart absolute with sheet anchors" {
  let sheet = Worksheet::new("Sheet1")
  let xml = write_drawing_xml(
    Some(sheet),
    [],
    [wb_drawing_chart(Absolute)],
    [],
    [],
  )
  inspect(
    xml.contains(""),
    content="true",
  )
  inspect(wb_count_substr(xml, ""), content="1")
}

///|
test "write hardening wb: write_drawing_xml covers slicer positioning and defaults" {
  let two = wb_drawing_slicer("SlicerTwo", None, Some(0.0), Some(-1.0), true)
  let abs = wb_drawing_slicer("SlicerAbs", Some(Absolute), None, None, false)
  let one = wb_drawing_slicer("SlicerOne", Some(OneCell), None, None, false)
  let xml = write_drawing_xml(None, [], [], [], [two, abs, one])
  inspect(xml.contains("Requires=\"sle15\""), content="true")
  inspect(xml.contains("Requires=\"a14\""), content="true")
  inspect(
    xml.contains(""),
    content="true",
  )
  inspect(xml.contains(""), content="true")
  inspect(
    xml.contains("fLocksWithSheet=\"1\" fPrintsWithSheet=\"1\""),
    content="true",
  )
}

///|
test "write hardening wb: sheet property emitters cover option branches" {
  let empty_xml = write_sheet_pr_xml(SheetPropsOptions::new())
  inspect(empty_xml.contains(""), content="true")

  let tab_indexed_xml = write_sheet_pr_xml(
    SheetPropsOptions::with_values(tab_color_indexed=4),
  )
  inspect(tab_indexed_xml.contains(""), content="true")

  let outline_right_xml = write_sheet_pr_xml(
    SheetPropsOptions::with_values(outline_summary_right=true),
  )
  inspect(
    outline_right_xml.contains(""),
    content="true",
  )

  let outline_below_xml = write_sheet_pr_xml(
    SheetPropsOptions::with_values(outline_summary_below=false),
  )
  inspect(
    outline_below_xml.contains(""),
    content="true",
  )

  let page_setup_xml = write_sheet_pr_xml(
    SheetPropsOptions::with_values(auto_page_breaks=true),
  )
  inspect(
    page_setup_xml.contains(""),
    content="true",
  )

  let format_xml = write_sheet_format_pr_xml(
    SheetPropsOptions::with_values(base_col_width=8),
  )
  inspect(
    format_xml.contains(" value
    None => fail("missing sheet views xml")
  }
  inspect(xml.contains("rightToLeft=\"1\""), content="true")
  inspect(xml.contains("showFormulas=\"1\""), content="true")
  inspect(xml.contains("showRuler=\"0\""), content="true")
  inspect(xml.contains("showZeros=\"0\""), content="true")
}

///|
test "write hardening wb: worksheet_dimension covers cell merge and table min/max branches" {
  let sheet = Worksheet::new("Dim")
  sheet.set_cell("D3", "x")
  sheet.set_cell("C4", "y")
  sheet.merge_cells("B2:B5")
  ignore(sheet.add_table("A1:E6", "DimTable", ["A", "B", "C", "D", "E"]))
  let dimension = worksheet_dimension(sheet) catch {
    err => fail("worksheet_dimension failed: \{repr(err)}")
  }
  debug_inspect(dimension, content="Some(\"A1:E6\")")
}

///|
test "write hardening wb: worksheet_dimension covers auto-filter expansion branches" {
  let sheet = Worksheet::new("FilterDim")
  sheet.set_cell("C3", "x")
  sheet.auto_filter = Some({ range_ref: "B2:D4", columns: [] })
  let dimension = worksheet_dimension(sheet) catch {
    err => fail("worksheet_dimension failed: \{repr(err)}")
  }
  debug_inspect(dimension, content="Some(\"B2:D4\")")
}

///|
test "write hardening wb: write_worksheet_xml covers x14 map aggregation and unknown ext fallback" {
  let sheet = Worksheet::new("Exts")
  sheet.x14_data_bars["skip"] = {
    sqref: "A1",
    bar_direction: "",
    bar_solid: false,
    bar_border_color: "",
  }
  sheet.x14_data_bars["id1"] = {
    sqref: "A1",
    bar_direction: "leftToRight",
    bar_solid: false,
    bar_border_color: "",
  }
  sheet.x14_data_bars["id2"] = {
    sqref: "A1",
    bar_direction: "",
    bar_solid: true,
    bar_border_color: "",
  }
  sheet.unknown_ext_blocks.push("")
  let xml = write_worksheet_xml(
    sheet,
    Map([]),
    Map([]),
    Map([]),
    [],
    None,
    None,
    [],
    [],
    [],
    [],
    None,
    None,
  ) catch {
    err => fail("write_worksheet_xml failed: \{repr(err)}")
  }
  inspect(xml.contains(""), content="true")
  inspect(xml.contains(""), content="true")
  inspect(xml.contains(""), content="true")
}

///|
test "write hardening wb: write_theme_xml covers custom palette and fallback branches" {
  let custom = write_theme_xml(
    Some([
      "111111", "222222", "333333", "444444", "555555", "666666", "777777", "888888",
      "999999", "AAAAAA",
    ]),
  )
  inspect(custom.contains(""), content="true")
  inspect(custom.contains("val=\"111111\""), content="true")

  let fallback = write_theme_xml(Some(["FFFFFF"]))
  inspect(fallback == default_theme_xml, content="true")
}

///|
test "write hardening wb: write_worksheet_xml covers formula bool and error branches" {
  let sheet = Worksheet::new("FormulaBranches")
  sheet.cells.push({
    reference: "A1",
    row: 1,
    col: 1,
    value: "1",
    value_type: Bool,
    rich_text: None,
    formula: Some("1=1"),
    formula_type: None,
    formula_ref: None,
    formula_shared_index: None,
    formula_value_present: true,
    style_explicit: false,
    style_id: 0,
  })
  sheet.cells.push({
    reference: "A2",
    row: 2,
    col: 1,
    value: "#N/A",
    value_type: Error,
    rich_text: None,
    formula: Some("NA()"),
    formula_type: None,
    formula_ref: None,
    formula_shared_index: None,
    formula_value_present: true,
    style_explicit: false,
    style_id: 0,
  })
  let xml = write_worksheet_xml(
    sheet,
    Map([]),
    Map([]),
    Map([]),
    [],
    None,
    None,
    [],
    [],
    [],
    [],
    None,
    None,
  ) catch {
    err => fail("write_worksheet_xml failed: \{repr(err)}")
  }
  inspect(xml.contains("r=\"A1\" t=\"b\""), content="true")
  inspect(xml.contains("1=11"), content="true")
  inspect(xml.contains("r=\"A2\" t=\"e\""), content="true")
  inspect(xml.contains("NA()#N/A"), content="true")
}

///|
test "write hardening wb: write_worksheet_xml covers shared-string and hyperlink error branches" {
  let rich_missing = Worksheet::new("RichMissing")
  rich_missing.cells.push({
    reference: "A1",
    row: 1,
    col: 1,
    value: "ignored",
    value_type: String,
    rich_text: Some([{ text: "R", font: None }]),
    formula: None,
    formula_type: None,
    formula_ref: None,
    formula_shared_index: None,
    formula_value_present: false,
    style_explicit: false,
    style_id: 0,
  })
  let missing_rich : Result[String, Error] = Ok(
    write_worksheet_xml(
      rich_missing,
      Map([]),
      Map([]),
      Map([]),
      [],
      None,
      None,
      [],
      [],
      [],
      [],
      None,
      None,
    ),
  ) catch {
    e => Err(e)
  }
  inspect(
    missing_rich is Err(XlsxError::InvalidSharedString(_)),
    content="true",
  )

  let plain_missing = Worksheet::new("PlainMissing")
  plain_missing.cells.push({
    reference: "A1",
    row: 1,
    col: 1,
    value: "plain",
    value_type: String,
    rich_text: None,
    formula: None,
    formula_type: None,
    formula_ref: None,
    formula_shared_index: None,
    formula_value_present: false,
    style_explicit: false,
    style_id: 0,
  })
  let missing_plain : Result[String, Error] = Ok(
    write_worksheet_xml(
      plain_missing,
      Map([]),
      Map([]),
      Map([]),
      [],
      None,
      None,
      [],
      [],
      [],
      [],
      None,
      None,
    ),
  ) catch {
    e => Err(e)
  }
  inspect(
    missing_plain is Err(XlsxError::InvalidSharedString(_)),
    content="true",
  )

  let no_hyperlinks_xml = write_worksheet_xml(
    Worksheet::new("NoLinks"),
    Map([]),
    Map([]),
    Map([]),
    [
      {
        reference: "A1",
        target: "",
        link_type: Unset,
        location: None,
        display: None,
        tooltip: None,
      },
    ],
    None,
    None,
    [],
    [],
    [],
    [],
    None,
    None,
  ) catch {
    err => fail("write_worksheet_xml failed: \{repr(err)}")
  }
  inspect(no_hyperlinks_xml.contains(""), content="false")

  let missing_rel : Result[String, Error] = Ok(
    write_worksheet_xml(
      Worksheet::new("MissingRel"),
      Map([]),
      Map([]),
      Map([]),
      [
        {
          reference: "A1",
          target: "https://example.com",
          link_type: External,
          location: None,
          display: None,
          tooltip: None,
        },
      ],
      None,
      None,
      [],
      [],
      [],
      [],
      None,
      None,
    ),
  ) catch {
    e => Err(e)
  }
  inspect(missing_rel is Err(XlsxError::InvalidHyperlink(_)), content="true")
}

///|
test "write hardening wb: write_with_io covers file-path extension fallback branches" {
  let workbook = Workbook::new()
  ignore(workbook.add_sheet("Sheet1"))

  let no_ext = write_with_io(workbook, {
    file_path: Some("demo"),
    zip_writer: None,
    charset_transcoder: None,
  }) catch {
    err => fail("write_with_io without extension failed: \{repr(err)}")
  }
  inspect(no_ext.length() > 0, content="true")

  let unsupported_ext = write_with_io(workbook, {
    file_path: Some("demo.unsupported"),
    zip_writer: None,
    charset_transcoder: None,
  }) catch {
    err => fail("write_with_io unsupported extension failed: \{repr(err)}")
  }
  inspect(unsupported_ext.length() > 0, content="true")
}

///|
test "write hardening wb: write_with_password empty password returns raw bytes" {
  let workbook = Workbook::new()
  ignore(workbook.add_sheet("Sheet1"))
  let raw = write(workbook) catch { err => fail("write failed: \{repr(err)}") }
  let with_empty_password = write_with_password(workbook, "") catch {
    err => fail("write_with_password failed: \{repr(err)}")
  }
  inspect(raw == with_empty_password, content="true")
}

///|
test "write hardening wb: write merges base vml with form-control and comment vml" {
  let workbook = Workbook::new()
  let sheet = workbook.add_sheet("Sheet1")
  sheet.add_vml_drawing_xml("base")
  sheet.add_form_control(FormControl::new("A1", "Button"))
  sheet.add_comment({
    cell: "B2",
    author: "A",
    author_id: None,
    text: "c",
    paragraph: [],
    width: None,
    height: None,
  })
  let bytes = write(workbook) catch {
    err => fail("write failed: \{repr(err)}")
  }
  inspect(bytes.length() > 0, content="true")
}

///|
test "write hardening wb: write fails for slicer source missing" {
  let workbook = Workbook::new()
  let sheet = workbook.add_sheet("Sheet1")
  sheet.slicers.push({
    name: "BadSlicer",
    cache: "Slicer_BadSlicer",
    source_name: "Field",
    cell: "A1",
    table_sheet: "Sheet1",
    table_name: "MissingTable",
    caption: "",
    macro_name: "",
    width: 200,
    height: 200,
    display_header: None,
    item_desc: false,
    format: GraphicOptions::new(),
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  })
  let result : Result[Bytes, Error] = Ok(write(workbook)) catch { e => Err(e) }
  inspect(result is Err(XlsxError::InvalidSheetOperation(_)), content="true")
}

///|
test "write hardening wb: write fails for slicer table field missing" {
  let workbook = Workbook::new()
  let sheet = workbook.add_sheet("Sheet1")
  ignore(sheet.add_table("A1:B2", "Table1", ["A", "B"]))
  sheet.slicers.push({
    name: "BadField",
    cache: "Slicer_BadField",
    source_name: "MissingColumn",
    cell: "A1",
    table_sheet: "Sheet1",
    table_name: "Table1",
    caption: "",
    macro_name: "",
    width: 200,
    height: 200,
    display_header: None,
    item_desc: false,
    format: GraphicOptions::new(),
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  })
  let result : Result[Bytes, Error] = Ok(write(workbook)) catch { e => Err(e) }
  inspect(result is Err(XlsxError::InvalidSheetOperation(_)), content="true")
}

///|
test "write hardening wb: write handles duplicate pivot cache relationship entries" {
  let workbook = Workbook::new()
  let sheet = workbook.add_sheet("Sheet1")
  sheet.pivot_tables.push({
    name: "",
    table_id: 1,
    cache_id: 1,
    table_xml: "",
    cache_definition_xml: "",
    cache_records_xml: None,
  })
  sheet.pivot_tables.push({
    name: "",
    table_id: 2,
    cache_id: 1,
    table_xml: "",
    cache_definition_xml: "",
    cache_records_xml: None,
  })
  let bytes = write(workbook) catch {
    err => fail("write failed: \{repr(err)}")
  }
  inspect(bytes.length() > 0, content="true")
}

///|
test "write hardening wb: failing custom zip writer maps to UnsupportedFeature" {
  let workbook = Workbook::new()
  ignore(workbook.add_sheet("Sheet1"))
  workbook.set_zip_writer(_archive => {
    raise InvalidSheetOperation(msg="zip writer failed")
  })
  let result : Result[Bytes, Error] = Ok(workbook.write_to_buffer()) catch {
    e => Err(e)
  }
  inspect(result is Err(XlsxError::UnsupportedFeature(_)), content="true")
}

///|
test "write hardening wb: default zip writer failure maps to UnsupportedFeature" {
  let workbook = Workbook::new()
  let sheet = workbook.add_sheet("Sheet1")
  sheet.images.push({
    reference: "A1",
    data: @encoding/utf8.encode("x"),
    extension: "a".repeat(70000),
    content_type: "image/png",
    offset_x: 0,
    offset_y: 0,
    scale_x: 1.0,
    scale_y: 1.0,
    width_emu: 1,
    height_emu: 1,
    print_object: true,
    locked: true,
    hyperlink: "",
    hyperlink_type: Unset,
    name: "",
    alt_text: "",
    lock_aspect_ratio: false,
    positioning: TwoCell,
    drawing_offset_x_emu: None,
    drawing_offset_y_emu: None,
    drawing_width_emu: None,
    drawing_height_emu: None,
    drawing_order: None,
  })
  let result : Result[Bytes, Error] = Ok(write(workbook)) catch { e => Err(e) }
  inspect(result is Err(XlsxError::UnsupportedFeature(_)), content="true")
}