///|
pub struct IterGridDimension {
  mut widths : Array[Int]
  mut heights : Array[Int]?
}

///|
pub fn IterGridDimension::default() -> IterGridDimension {
  { widths: [], heights: None, }
}

///|
pub fn IterGridDimension::estimate(
  self : IterGridDimension,
  records : IterRecords,
  cfg : SpannedConfig,
) -> Unit {
  let count = records.count_cols()
  let widths = Array::make(count, 0)
  let spanned = []
  for row in 0.. {
        let (padding_left, padding_right, _, _) = cfg.padding_at(
          source.0,
          source.1,
        )
        let width = string_display_width(line) + padding_left + padding_right
        if colspan <= 1 && width > widths[col] {
          widths[col] = width
        } else if colspan > 1 {
          spanned.push((col, colspan, width, row))
        }
      })
    }
  }
  spanned.sort_by((a, b) => {
    if a.1 != b.1 {
      a.1.compare(b.1)
    } else if a.3 != b.3 {
      a.3.compare(b.3)
    } else {
      a.0.compare(b.0)
    }
  })
  spanned.each(item => {
    let end_ = if item.0 + item.1 < count { item.0 + item.1 } else { count }
    let mut current = 0
    for col = item.0; col < end_; col = col + 1 {
      current = current + widths[col]
      if col > item.0 && cfg.has_vertical(col, count) {
        current += 1
      }
    }
    if item.2 > current && end_ > item.0 {
      let extra = item.2 - current
      let each = extra / (end_ - item.0)
      let rest = extra % (end_ - item.0)
      for col in item.0.. Array[Int] {
  self.widths
}

///|
/// Measure the dimensions used by IterGrid, including render-time width and
/// height modes. Explicit dimension overrides are applied by the caller.
/// Unlike the static width/height helpers, this measures processed cell lines.
///
/// ```mbt check
/// test {
///   let records = @papergrid.IterRecords::new([["abcdef"]])
///   let config = @papergrid.SpannedConfig::default()
///   config.set_width_mode(@papergrid.WidthMode::wrap(2))
///   config.set_height_mode(@papergrid.HeightMode::increase(5))
///   assert_eq(
///     @papergrid.IterGridDimension::measure_rendered(records, config),
///     ([2], [5]),
///   )
/// }
/// ```
pub fn IterGridDimension::measure_rendered(
  records : IterRecords,
  config : SpannedConfig,
) -> (Array[Int], Array[Int]) {
  let dims = IterGridDimension::default()
  dims.estimate(records, config)
  (dims.widths(), row_metrics(records, records.count_cols(), config))
}

///|
pub fn IterGridDimension::set_widths(
  self : IterGridDimension,
  widths : Array[Int],
) -> Unit {
  self.widths = widths.copy()
}

///|
pub fn IterGridDimension::measure_widths(
  records : IterRecords,
  config : SpannedConfig,
) -> Array[Int] {
  measure_axis(records, config, true)
}

///|
/// Measure row heights using the upstream per-axis span rules. By default this
/// counts raw text lines; `apply_modes=true` includes render-time width and
/// height modes without changing those span rules or using cached dimensions.
pub fn IterGridDimension::measure_heights(
  records : IterRecords,
  config : SpannedConfig,
  apply_modes? : Bool = false,
) -> Array[Int] {
  measure_axis(records, config, false, apply_modes~)
}

///|
pub fn IterGridDimension::total_width(
  records : IterRecords,
  config : SpannedConfig,
) -> Int {
  IterGridDimension::measure_widths(records, config).fold(init=0, (sum, width) => {
    sum + width
  }) +
  config.count_vertical(records.count_cols())
}

///|
pub fn IterGridDimension::total_height(
  records : IterRecords,
  config : SpannedConfig,
) -> Int {
  IterGridDimension::measure_heights(records, config).fold(init=0, (sum, height) => {
    sum + height
  }) +
  config.count_horizontal(records.count_rows())
}

///|
fn measure_axis(
  records : IterRecords,
  config : SpannedConfig,
  horizontal : Bool,
  cached? : Bool = false,
  apply_modes? : Bool = false,
) -> Array[Int] {
  let count = if horizontal {
    records.count_cols()
  } else {
    records.count_rows()
  }
  let values = Array::make(count, 0)
  let spans : Array[(Int, Int, Int, Int)] = []
  let spanned = if cached {
    config.col_spans.any(entry => entry.2 > 1) ||
    config.row_spans.any(entry => entry.2 > 1)
  } else if horizontal {
    config.col_spans.any(entry => entry.2 > 1)
  } else {
    config.row_spans.any(entry => entry.2 > 1)
  }
  for row in 0.. 1 {
        spans.push((row, col, span, size))
      } else {
        let index = if horizontal { col } else { row }
        values[index] = Int::max(values[index], size)
      }
    }
  }
  spans.sort_by((a, b) => {
    let span_order = if horizontal { a.2.compare(b.2) } else { 0 }
    if span_order != 0 {
      span_order
    } else if a.0 != b.0 {
      a.0.compare(b.0)
    } else {
      a.1.compare(b.1)
    }
  })
  for entry in spans {
    let start = if horizontal { entry.1 } else { entry.0 }
    let end_ = Int::min(count, start + entry.2)
    let mut current = 0
    for index in start.. start &&
        (if horizontal {
          config.has_vertical(index, count)
        } else {
          config.has_horizontal(index, count)
        }) {
        current += 1
      }
    }
    if entry.3 > current && end_ > start {
      let extra = entry.3 - current
      let each = extra / (end_ - start)
      let rest = extra % (end_ - start)
      for index in start..