///|
enum Scope {
  RowRange(Int, Int)
  ColRange(Int, Int)
  All
  Cell(Int, Int)
}

///|
pub enum HAlign {
  Left
  Right
  Center
}

///|
pub enum VAlign {
  Top
  Bottom
  Center
}

///|
pub enum HorizontalLines {
  All
  HeaderOnly
  None
}

///|
pub struct HorizontalLine {
  main : Char?
  left : Char?
  intersection : Char?
  right : Char?
}

///|
pub fn HAlign::left() -> HAlign {
  Left
}

///|
pub fn HAlign::right() -> HAlign {
  Right
}

///|
pub fn HAlign::center() -> HAlign {
  Center
}

///|
pub fn VAlign::top() -> VAlign {
  Top
}

///|
pub fn VAlign::bottom() -> VAlign {
  Bottom
}

///|
pub fn VAlign::center() -> VAlign {
  Center
}

///|
pub fn HorizontalLines::all() -> HorizontalLines {
  All
}

///|
pub fn HorizontalLines::header_only() -> HorizontalLines {
  HeaderOnly
}

///|
pub fn HorizontalLines::none() -> HorizontalLines {
  None
}

///|
pub fn HorizontalLine::new(ch : Char) -> HorizontalLine {
  { main: Some(ch), left: None, intersection: None, right: None }
}

///|
pub fn HorizontalLine::filled(ch : Char) -> HorizontalLine {
  HorizontalLine::new(ch)
}

///|
pub fn HorizontalLine::full(
  main : Char,
  left : Char,
  intersection : Char,
  right : Char,
) -> HorizontalLine {
  {
    main: Some(main),
    left: Some(left),
    intersection: Some(intersection),
    right: Some(right),
  }
}

///|
pub fn HorizontalLine::from_chars(
  main : Char?,
  left : Char?,
  intersection : Char?,
  right : Char?,
) -> HorizontalLine {
  { main, left, intersection, right }
}

///|
pub fn HorizontalLine::with_left(
  self : HorizontalLine,
  ch : Char,
) -> HorizontalLine {
  { ..self, left: Some(ch) }
}

///|
pub fn HorizontalLine::with_intersection(
  self : HorizontalLine,
  ch : Char,
) -> HorizontalLine {
  { ..self, intersection: Some(ch) }
}

///|
pub fn HorizontalLine::with_right(
  self : HorizontalLine,
  ch : Char,
) -> HorizontalLine {
  { ..self, right: Some(ch) }
}

///|
pub(all) struct CellBorder {
  top : Char?
  bottom : Char?
  left : Char?
  right : Char?
  left_top_corner : Char?
  right_top_corner : Char?
  left_bottom_corner : Char?
  right_bottom_corner : Char?
}

///|
pub fn CellBorder::empty() -> CellBorder {
  {
    top: None,
    bottom: None,
    left: None,
    right: None,
    left_top_corner: None,
    right_top_corner: None,
    left_bottom_corner: None,
    right_bottom_corner: None,
  }
}

///|
pub fn CellBorder::filled(ch : Char) -> CellBorder {
  {
    top: Some(ch),
    bottom: Some(ch),
    left: Some(ch),
    right: Some(ch),
    left_top_corner: Some(ch),
    right_top_corner: Some(ch),
    left_bottom_corner: Some(ch),
    right_bottom_corner: Some(ch),
  }
}

///|
pub struct SpannedConfig {
  mut borders : Borders
  mut padding_left : Int
  mut padding_right : Int
  mut padding_top : Int
  mut padding_bottom : Int
  mut align : HAlign
  mut valign : VAlign
  mut width_mode : WidthMode
  mut height_mode : HeightMode
  mut horizontal_lines : HorizontalLines
  custom_horizontal_lines : Array[(Int, HorizontalLine)]
  width_rules : Array[(Scope, WidthMode)]
  height_rules : Array[(Scope, HeightMode)]
  align_rules : Array[(Scope, HAlign)]
  valign_rules : Array[(Scope, VAlign)]
  padding_rules : Array[(Scope, Int, Int, Int, Int)]
  col_spans : Array[(Int, Int, Int)]
  row_spans : Array[(Int, Int, Int)]
  span_sources : Array[(Int, Int, Int, Int)]
  // Per-position border overrides for Highlight
  // (row_boundary, col, char) — horizontal fill overrides
  border_horizontal : Array[(Int, Int, Char)]
  // (row, col_boundary, char) — vertical fill overrides
  border_vertical : Array[(Int, Int, Char)]
  // (row_boundary, col_boundary, char) — intersection/corner overrides
  border_intersection : Array[(Int, Int, Char)]
  // Margin: per-side size, fill char, and offset
  mut margin_top : Int
  mut margin_bottom : Int
  mut margin_left : Int
  mut margin_right : Int
  mut margin_fill_top : Char
  mut margin_fill_bottom : Char
  mut margin_fill_left : Char
  mut margin_fill_right : Char
  mut margin_offset_top : Int
  mut margin_offset_bottom : Int
  mut margin_offset_left : Int
  mut margin_offset_right : Int
}

///|
pub fn SpannedConfig::default() -> SpannedConfig {
  {
    borders: Borders::ascii(),
    padding_left: 1,
    padding_right: 1,
    padding_top: 0,
    padding_bottom: 0,
    align: HAlign::center(),
    valign: VAlign::top(),
    width_mode: WidthMode::none(),
    height_mode: HeightMode::none(),
    horizontal_lines: HorizontalLines::all(),
    custom_horizontal_lines: [],
    width_rules: [],
    height_rules: [],
    align_rules: [],
    valign_rules: [],
    padding_rules: [],
    col_spans: [],
    row_spans: [],
    span_sources: [],
    border_horizontal: [],
    border_vertical: [],
    border_intersection: [],
    margin_top: 0,
    margin_bottom: 0,
    margin_left: 0,
    margin_right: 0,
    margin_fill_top: ' ',
    margin_fill_bottom: ' ',
    margin_fill_left: ' ',
    margin_fill_right: ' ',
    margin_offset_top: 0,
    margin_offset_bottom: 0,
    margin_offset_left: 0,
    margin_offset_right: 0,
  }
}

///|
pub fn SpannedConfig::set_borders(
  self : SpannedConfig,
  borders : Borders,
) -> Unit {
  self.borders = borders
}

///|
pub fn SpannedConfig::borders(self : SpannedConfig) -> Borders {
  self.borders
}

///|
pub fn SpannedConfig::set_padding(
  self : SpannedConfig,
  left : Int,
  right : Int,
  top : Int,
  bottom : Int,
) -> Unit {
  self.padding_left = left
  self.padding_right = right
  self.padding_top = top
  self.padding_bottom = bottom
}

///|
pub fn SpannedConfig::padding_left(self : SpannedConfig) -> Int {
  self.padding_left
}

///|
pub fn SpannedConfig::padding_right(self : SpannedConfig) -> Int {
  self.padding_right
}

///|
pub fn SpannedConfig::padding_top(self : SpannedConfig) -> Int {
  self.padding_top
}

///|
pub fn SpannedConfig::padding_bottom(self : SpannedConfig) -> Int {
  self.padding_bottom
}

///|
pub fn SpannedConfig::set_align(self : SpannedConfig, align : HAlign) -> Unit {
  self.align = align
}

///|
pub fn SpannedConfig::align(self : SpannedConfig) -> HAlign {
  self.align
}

///|
pub fn SpannedConfig::set_valign(self : SpannedConfig, align : VAlign) -> Unit {
  self.valign = align
}

///|
pub fn SpannedConfig::valign(self : SpannedConfig) -> VAlign {
  self.valign
}

///|
pub fn SpannedConfig::set_width_mode(
  self : SpannedConfig,
  mode : WidthMode,
) -> Unit {
  self.width_mode = mode
}

///|
pub fn SpannedConfig::width_mode(self : SpannedConfig) -> WidthMode {
  self.width_mode
}

///|
pub fn SpannedConfig::set_width_mode_for_rows(
  self : SpannedConfig,
  start : Int,
  end : Int,
  mode : WidthMode,
) -> Unit {
  self.width_rules.push((RowRange(start, end), mode))
}

///|
pub fn SpannedConfig::set_width_mode_for_cols(
  self : SpannedConfig,
  start : Int,
  end : Int,
  mode : WidthMode,
) -> Unit {
  self.width_rules.push((ColRange(start, end), mode))
}

///|
pub fn SpannedConfig::set_width_mode_for_all(
  self : SpannedConfig,
  mode : WidthMode,
) -> Unit {
  self.width_rules.push((All, mode))
}

///|
pub fn SpannedConfig::set_width_mode_for_cell(
  self : SpannedConfig,
  row : Int,
  col : Int,
  mode : WidthMode,
) -> Unit {
  self.width_rules.push((Cell(row, col), mode))
}

///|
pub fn SpannedConfig::width_mode_at(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> WidthMode {
  let mut value = self.width_mode
  self.width_rules.each(rule => {
    if scope_matches(rule.0, row, col) {
      value = rule.1
    }
  })
  value
}

///|
pub fn SpannedConfig::set_height_mode(
  self : SpannedConfig,
  mode : HeightMode,
) -> Unit {
  self.height_mode = mode
}

///|
pub fn SpannedConfig::height_mode(self : SpannedConfig) -> HeightMode {
  self.height_mode
}

///|
pub fn SpannedConfig::set_height_mode_for_rows(
  self : SpannedConfig,
  start : Int,
  end : Int,
  mode : HeightMode,
) -> Unit {
  self.height_rules.push((RowRange(start, end), mode))
}

///|
pub fn SpannedConfig::set_height_mode_for_cols(
  self : SpannedConfig,
  start : Int,
  end : Int,
  mode : HeightMode,
) -> Unit {
  self.height_rules.push((ColRange(start, end), mode))
}

///|
pub fn SpannedConfig::set_height_mode_for_all(
  self : SpannedConfig,
  mode : HeightMode,
) -> Unit {
  self.height_rules.push((All, mode))
}

///|
pub fn SpannedConfig::set_height_mode_for_cell(
  self : SpannedConfig,
  row : Int,
  col : Int,
  mode : HeightMode,
) -> Unit {
  self.height_rules.push((Cell(row, col), mode))
}

///|
pub fn SpannedConfig::height_mode_at(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> HeightMode {
  let mut value = self.height_mode
  self.height_rules.each(rule => {
    if scope_matches(rule.0, row, col) {
      value = rule.1
    }
  })
  value
}

///|
pub fn SpannedConfig::set_horizontal_lines(
  self : SpannedConfig,
  mode : HorizontalLines,
) -> Unit {
  self.horizontal_lines = mode
}

///|
pub fn SpannedConfig::horizontal_lines(self : SpannedConfig) -> HorizontalLines {
  self.horizontal_lines
}

///|
pub fn SpannedConfig::set_custom_horizontal_lines(
  self : SpannedConfig,
  lines : Array[(Int, HorizontalLine)],
) -> Unit {
  self.custom_horizontal_lines.clear()
  lines.each(line => self.custom_horizontal_lines.push(line))
}

///|
pub fn SpannedConfig::custom_horizontal_line(
  self : SpannedConfig,
  row : Int,
) -> HorizontalLine? {
  let mut index = self.custom_horizontal_lines.length() - 1
  while index >= 0 {
    let line = self.custom_horizontal_lines[index]
    if line.0 == row {
      return Some(line.1)
    }
    index = index - 1
  }
  None
}

///|
fn scope_matches(scope : Scope, row : Int, col : Int) -> Bool {
  match scope {
    RowRange(start, end) => row >= start && row < end
    ColRange(start, end) => col >= start && col < end
    Cell(r, c) => row == r && col == c
    All => true
  }
}

///|
pub fn SpannedConfig::set_align_for_rows(
  self : SpannedConfig,
  start : Int,
  end : Int,
  align : HAlign,
) -> Unit {
  self.align_rules.push((RowRange(start, end), align))
}

///|
pub fn SpannedConfig::set_align_for_cols(
  self : SpannedConfig,
  start : Int,
  end : Int,
  align : HAlign,
) -> Unit {
  self.align_rules.push((ColRange(start, end), align))
}

///|
pub fn SpannedConfig::set_align_for_all(
  self : SpannedConfig,
  align : HAlign,
) -> Unit {
  self.align_rules.push((All, align))
}

///|
pub fn SpannedConfig::set_align_for_cell(
  self : SpannedConfig,
  row : Int,
  col : Int,
  align : HAlign,
) -> Unit {
  self.align_rules.push((Cell(row, col), align))
}

///|
pub fn SpannedConfig::align_at(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> HAlign {
  let mut value = self.align
  self.align_rules.each(rule => {
    if scope_matches(rule.0, row, col) {
      value = rule.1
    }
  })
  value
}

///|
pub fn SpannedConfig::set_valign_for_rows(
  self : SpannedConfig,
  start : Int,
  end : Int,
  align : VAlign,
) -> Unit {
  self.valign_rules.push((RowRange(start, end), align))
}

///|
pub fn SpannedConfig::set_valign_for_cols(
  self : SpannedConfig,
  start : Int,
  end : Int,
  align : VAlign,
) -> Unit {
  self.valign_rules.push((ColRange(start, end), align))
}

///|
pub fn SpannedConfig::set_valign_for_all(
  self : SpannedConfig,
  align : VAlign,
) -> Unit {
  self.valign_rules.push((All, align))
}

///|
pub fn SpannedConfig::set_valign_for_cell(
  self : SpannedConfig,
  row : Int,
  col : Int,
  align : VAlign,
) -> Unit {
  self.valign_rules.push((Cell(row, col), align))
}

///|
pub fn SpannedConfig::valign_at(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> VAlign {
  let mut value = self.valign
  self.valign_rules.each(rule => {
    if scope_matches(rule.0, row, col) {
      value = rule.1
    }
  })
  value
}

///|
pub fn SpannedConfig::set_padding_for_rows(
  self : SpannedConfig,
  start : Int,
  end : Int,
  left : Int,
  right : Int,
  top : Int,
  bottom : Int,
) -> Unit {
  self.padding_rules.push((RowRange(start, end), left, right, top, bottom))
}

///|
pub fn SpannedConfig::set_padding_for_cols(
  self : SpannedConfig,
  start : Int,
  end : Int,
  left : Int,
  right : Int,
  top : Int,
  bottom : Int,
) -> Unit {
  self.padding_rules.push((ColRange(start, end), left, right, top, bottom))
}

///|
pub fn SpannedConfig::set_padding_for_all(
  self : SpannedConfig,
  left : Int,
  right : Int,
  top : Int,
  bottom : Int,
) -> Unit {
  self.padding_rules.push((All, left, right, top, bottom))
}

///|
pub fn SpannedConfig::set_padding_for_cell(
  self : SpannedConfig,
  row : Int,
  col : Int,
  left : Int,
  right : Int,
  top : Int,
  bottom : Int,
) -> Unit {
  self.padding_rules.push((Cell(row, col), left, right, top, bottom))
}

///|
pub fn SpannedConfig::padding_at(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> (Int, Int, Int, Int) {
  let mut left = self.padding_left
  let mut right = self.padding_right
  let mut top = self.padding_top
  let mut bottom = self.padding_bottom
  self.padding_rules.each(rule => {
    if scope_matches(rule.0, row, col) {
      left = rule.1
      right = rule.2
      top = rule.3
      bottom = rule.4
    }
  })
  (left, right, top, bottom)
}

///|
pub fn SpannedConfig::set_col_span(
  self : SpannedConfig,
  row : Int,
  col : Int,
  span : Int,
) -> Unit {
  let next = []
  self.col_spans.each(item => {
    if item.0 != row || item.1 != col {
      next.push(item)
    }
  })
  self.col_spans.clear()
  next.each(item => self.col_spans.push(item))
  if span > 1 {
    self.col_spans.push((row, col, span))
  }
}

///|
pub fn SpannedConfig::set_row_span(
  self : SpannedConfig,
  row : Int,
  col : Int,
  span : Int,
) -> Unit {
  let next = []
  self.row_spans.each(item => {
    if item.0 != row || item.1 != col {
      next.push(item)
    }
  })
  self.row_spans.clear()
  next.each(item => self.row_spans.push(item))
  if span > 1 {
    self.row_spans.push((row, col, span))
  }
}

///|
pub fn SpannedConfig::set_span_source(
  self : SpannedConfig,
  anchor_row : Int,
  anchor_col : Int,
  source_row : Int,
  source_col : Int,
) -> Unit {
  let next = []
  self.span_sources.each(item => {
    if item.0 != anchor_row || item.1 != anchor_col {
      next.push(item)
    }
  })
  self.span_sources.clear()
  next.each(item => self.span_sources.push(item))
  if anchor_row != source_row || anchor_col != source_col {
    self.span_sources.push((anchor_row, anchor_col, source_row, source_col))
  }
}

///|
pub fn SpannedConfig::clear_span_source(
  self : SpannedConfig,
  anchor_row : Int,
  anchor_col : Int,
) -> Unit {
  let next = []
  self.span_sources.each(item => {
    if item.0 != anchor_row || item.1 != anchor_col {
      next.push(item)
    }
  })
  self.span_sources.clear()
  next.each(item => self.span_sources.push(item))
}

///|
/// Clear all spans and border override arrays.
/// Used after operations like Split that completely reshape the table.
pub fn SpannedConfig::clear_spans_and_overrides(self : SpannedConfig) -> Unit {
  self.col_spans.clear()
  self.row_spans.clear()
  self.span_sources.clear()
  self.border_horizontal.clear()
  self.border_vertical.clear()
  self.border_intersection.clear()
}

///|
pub fn SpannedConfig::clear_unused_span_source(
  self : SpannedConfig,
  anchor_row : Int,
  anchor_col : Int,
) -> Unit {
  if self.col_span_at(anchor_row, anchor_col) <= 1 &&
    self.row_span_at(anchor_row, anchor_col) <= 1 {
    self.clear_span_source(anchor_row, anchor_col)
  }
}

///|
pub fn SpannedConfig::span_source_at(
  self : SpannedConfig,
  anchor_row : Int,
  anchor_col : Int,
) -> (Int, Int) {
  let mut index = self.span_sources.length() - 1
  while index >= 0 {
    let item = self.span_sources[index]
    if item.0 == anchor_row && item.1 == anchor_col {
      return (item.2, item.3)
    }
    index = index - 1
  }
  (anchor_row, anchor_col)
}

///|
pub fn SpannedConfig::span_anchor_for_source(
  self : SpannedConfig,
  source_row : Int,
  source_col : Int,
) -> (Int, Int)? {
  let mut index = self.span_sources.length() - 1
  while index >= 0 {
    let item = self.span_sources[index]
    if item.2 == source_row && item.3 == source_col {
      return Some((item.0, item.1))
    }
    index = index - 1
  }
  None
}

///|
pub fn SpannedConfig::get_col_span(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Int? {
  let mut index = self.col_spans.length() - 1
  while index >= 0 {
    let item = self.col_spans[index]
    if item.0 == row && item.1 == col {
      return Some(item.2)
    }
    index = index - 1
  }
  None
}

///|
pub fn SpannedConfig::get_row_span(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Int? {
  let mut index = self.row_spans.length() - 1
  while index >= 0 {
    let item = self.row_spans[index]
    if item.0 == row && item.1 == col {
      return Some(item.2)
    }
    index = index - 1
  }
  None
}

///|
pub fn SpannedConfig::col_span_at(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Int {
  match self.get_col_span(row, col) {
    Some(span) => span
    None => 1
  }
}

///|
pub fn SpannedConfig::row_span_at(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Int {
  match self.get_row_span(row, col) {
    Some(span) => span
    None => 1
  }
}

///|
pub fn SpannedConfig::has_col_spans(self : SpannedConfig) -> Bool {
  !self.col_spans.is_empty()
}

///|
pub fn SpannedConfig::has_row_spans(self : SpannedConfig) -> Bool {
  !self.row_spans.is_empty()
}

///|
pub fn SpannedConfig::is_cell_covered_by_col_span(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Bool {
  for index in 0.. item.1 && col < item.1 + item.2 {
      return true
    }
  }
  false
}

///|
pub fn SpannedConfig::is_cell_covered_by_row_span(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Bool {
  for index in 0.. item.0 &&
      row < item.0 + item.2 &&
      col >= item.1 &&
      col < item.1 + colspan {
      return true
    }
  }
  false
}

///|
pub fn SpannedConfig::is_cell_covered_by_both_spans(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Bool {
  // Find anchor cells that have BOTH row and col spans,
  // then check if (row, col) is strictly inside both ranges.
  for i in 0.. anchor_row && row < anchor_row + row_span {
      // This anchor's row span covers our row. Check if it also has a col span.
      for j in 0.. anchor_col && col < anchor_col + col_span {
            return true
          }
        }
      }
    }
  }
  false
}

///|
pub fn SpannedConfig::is_cell_visible(
  self : SpannedConfig,
  row : Int,
  col : Int,
) -> Bool {
  for index in 0..= item.0 &&
      row < item.0 + rowspan &&
      col > item.1 &&
      col < item.1 + item.2 {
      return false
    }
  }
  for index in 0.. item.0 &&
      row < item.0 + item.2 &&
      col >= item.1 &&
      col < item.1 + colspan {
      return false
    }
  }
  true
}

///|
fn map_index(index_map : Array[Int], index : Int) -> Int {
  if index < 0 || index >= index_map.length() {
    -1
  } else {
    index_map[index]
  }
}

///|
fn mapped_runs(
  index_map : Array[Int],
  start : Int,
  end : Int,
) -> Array[(Int, Int)] {
  let runs = []
  let mut run_start = -1
  let mut previous = -1
  for old = start; old < end; old = old + 1 {
    let mapped = map_index(index_map, old)
    if mapped < 0 {
      continue
    }
    if run_start < 0 {
      run_start = mapped
      previous = mapped
    } else if mapped == previous + 1 {
      previous = mapped
    } else {
      runs.push((run_start, previous + 1))
      run_start = mapped
      previous = mapped
    }
  }
  if run_start >= 0 {
    runs.push((run_start, previous + 1))
  }
  runs
}

///|
fn set_or_overwrite_3(
  arr : Array[(Int, Int, Char)],
  a : Int,
  b : Int,
  ch : Char,
) -> Unit {
  for i in 0.. Char? {
  let mut result : Char? = None
  arr.each(item => if item.0 == a && item.1 == b { result = Some(item.2) })
  result
}

///|
/// Set per-cell border overrides at the grid-position level.
/// Each border component is stored at the actual grid position it affects.
pub fn SpannedConfig::set_cell_border(
  self : SpannedConfig,
  row : Int,
  col : Int,
  border : CellBorder,
) -> Unit {
  match border.top {
    Some(ch) => set_or_overwrite_3(self.border_horizontal, row, col, ch)
    None => ()
  }
  match border.bottom {
    Some(ch) => set_or_overwrite_3(self.border_horizontal, row + 1, col, ch)
    None => ()
  }
  match border.left {
    Some(ch) => set_or_overwrite_3(self.border_vertical, row, col, ch)
    None => ()
  }
  match border.right {
    Some(ch) => set_or_overwrite_3(self.border_vertical, row, col + 1, ch)
    None => ()
  }
  match border.left_top_corner {
    Some(ch) => set_or_overwrite_3(self.border_intersection, row, col, ch)
    None => ()
  }
  match border.right_top_corner {
    Some(ch) => set_or_overwrite_3(self.border_intersection, row, col + 1, ch)
    None => ()
  }
  match border.left_bottom_corner {
    Some(ch) => set_or_overwrite_3(self.border_intersection, row + 1, col, ch)
    None => ()
  }
  match border.right_bottom_corner {
    Some(ch) =>
      set_or_overwrite_3(self.border_intersection, row + 1, col + 1, ch)
    None => ()
  }
}

///|
pub fn SpannedConfig::horizontal_override_at(
  self : SpannedConfig,
  row_boundary : Int,
  col : Int,
) -> Char? {
  lookup_3(self.border_horizontal, row_boundary, col)
}

///|
pub fn SpannedConfig::vertical_override_at(
  self : SpannedConfig,
  row : Int,
  col_boundary : Int,
) -> Char? {
  lookup_3(self.border_vertical, row, col_boundary)
}

///|
pub fn SpannedConfig::intersection_override_at(
  self : SpannedConfig,
  row_boundary : Int,
  col_boundary : Int,
) -> Char? {
  lookup_3(self.border_intersection, row_boundary, col_boundary)
}

///|
pub fn SpannedConfig::has_cell_borders(self : SpannedConfig) -> Bool {
  !self.border_horizontal.is_empty() ||
  !self.border_vertical.is_empty() ||
  !self.border_intersection.is_empty()
}

///|
/// Get the effective cell border for a cell at (row, col) given table shape (count_rows, count_cols).
/// Returns a CellBorder with the resolved border characters including overrides.
pub fn SpannedConfig::get_cell_border(
  self : SpannedConfig,
  row : Int,
  col : Int,
  count_rows : Int,
  count_cols : Int,
) -> CellBorder {
  let borders = self.borders()
  // Top border
  let top = if row == 0 {
    match self.horizontal_override_at(0, col) {
      Some(ch) => Some(ch)
      None => borders.top
    }
  } else {
    match self.horizontal_override_at(row, col) {
      Some(ch) => Some(ch)
      None => borders.horizontal
    }
  }
  // Bottom border
  let bottom = if row + 1 == count_rows {
    match self.horizontal_override_at(count_rows, col) {
      Some(ch) => Some(ch)
      None => borders.bottom
    }
  } else {
    match self.horizontal_override_at(row + 1, col) {
      Some(ch) => Some(ch)
      None => borders.horizontal
    }
  }
  // Left border
  let left = if col == 0 {
    match self.vertical_override_at(row, 0) {
      Some(ch) => Some(ch)
      None => borders.left
    }
  } else {
    match self.vertical_override_at(row, col) {
      Some(ch) => Some(ch)
      None => borders.vertical
    }
  }
  // Right border
  let right = if col + 1 == count_cols {
    match self.vertical_override_at(row, count_cols) {
      Some(ch) => Some(ch)
      None => borders.right
    }
  } else {
    match self.vertical_override_at(row, col + 1) {
      Some(ch) => Some(ch)
      None => borders.vertical
    }
  }
  // Corners
  let left_top = match self.intersection_override_at(row, col) {
    Some(ch) => Some(ch)
    None =>
      if row == 0 && col == 0 {
        borders.top_left
      } else if row == 0 {
        borders.top_intersection
      } else if col == 0 {
        borders.left_intersection
      } else {
        borders.intersection
      }
  }
  let right_top = match self.intersection_override_at(row, col + 1) {
    Some(ch) => Some(ch)
    None =>
      if row == 0 && col + 1 == count_cols {
        borders.top_right
      } else if row == 0 {
        borders.top_intersection
      } else if col + 1 == count_cols {
        borders.right_intersection
      } else {
        borders.intersection
      }
  }
  let left_bottom = match self.intersection_override_at(row + 1, col) {
    Some(ch) => Some(ch)
    None =>
      if row + 1 == count_rows && col == 0 {
        borders.bottom_left
      } else if row + 1 == count_rows {
        borders.bottom_intersection
      } else if col == 0 {
        borders.left_intersection
      } else {
        borders.intersection
      }
  }
  let right_bottom = match self.intersection_override_at(row + 1, col + 1) {
    Some(ch) => Some(ch)
    None =>
      if row + 1 == count_rows && col + 1 == count_cols {
        borders.bottom_right
      } else if row + 1 == count_rows {
        borders.bottom_intersection
      } else if col + 1 == count_cols {
        borders.right_intersection
      } else {
        borders.intersection
      }
  }
  {
    top,
    bottom,
    left,
    right,
    left_top_corner: left_top,
    right_top_corner: right_top,
    left_bottom_corner: left_bottom,
    right_bottom_corner: right_bottom,
  }
}

///|
/// Check if a cell has a "left" border (for border correction).
/// Returns false if the cell is covered by a col span or both spans.
pub fn SpannedConfig::has_left_border(
  self : SpannedConfig,
  row : Int,
  col : Int,
  count_rows : Int,
  count_cols : Int,
) -> Bool {
  if self.is_cell_covered_by_both_spans(row, col) ||
    self.is_cell_covered_by_col_span(row, col) {
    return false
  }
  let border = self.get_cell_border(row, col, count_rows, count_cols)
  !(border.left is None) ||
  !(border.left_top_corner is None) ||
  !(border.left_bottom_corner is None)
}

///|
/// Check if a cell has a "top" border (for border correction).
/// Returns false if the cell is covered by a row span or both spans.
pub fn SpannedConfig::has_top_border(
  self : SpannedConfig,
  row : Int,
  col : Int,
  count_rows : Int,
  count_cols : Int,
) -> Bool {
  if self.is_cell_covered_by_both_spans(row, col) ||
    self.is_cell_covered_by_row_span(row, col) {
    return false
  }
  let border = self.get_cell_border(row, col, count_rows, count_cols)
  !(border.top is None) ||
  !(border.left_top_corner is None) ||
  !(border.right_top_corner is None)
}

///|
pub fn SpannedConfig::set_margin(
  self : SpannedConfig,
  top : Int,
  bottom : Int,
  left : Int,
  right : Int,
  fill_top : Char,
  fill_bottom : Char,
  fill_left : Char,
  fill_right : Char,
) -> Unit {
  self.margin_top = top
  self.margin_bottom = bottom
  self.margin_left = left
  self.margin_right = right
  self.margin_fill_top = fill_top
  self.margin_fill_bottom = fill_bottom
  self.margin_fill_left = fill_left
  self.margin_fill_right = fill_right
}

///|
pub fn SpannedConfig::set_margin_offset(
  self : SpannedConfig,
  top : Int,
  bottom : Int,
  left : Int,
  right : Int,
) -> Unit {
  self.margin_offset_top = top
  self.margin_offset_bottom = bottom
  self.margin_offset_left = left
  self.margin_offset_right = right
}

///|
pub fn SpannedConfig::has_margin(self : SpannedConfig) -> Bool {
  self.margin_top > 0 ||
  self.margin_bottom > 0 ||
  self.margin_left > 0 ||
  self.margin_right > 0
}

///|
pub fn SpannedConfig::remap(
  self : SpannedConfig,
  row_map : Array[Int],
  col_map : Array[Int],
) -> Unit {
  let previous_col_spans = self.col_spans.copy()
  let previous_row_spans = self.row_spans.copy()

  let next_align_rules = []
  self.align_rules.each(rule => {
    match rule.0 {
      RowRange(start, end) =>
        mapped_runs(row_map, start, end).each(run => {
          next_align_rules.push((RowRange(run.0, run.1), rule.1))
        })
      ColRange(start, end) =>
        mapped_runs(col_map, start, end).each(run => {
          next_align_rules.push((ColRange(run.0, run.1), rule.1))
        })
      All => next_align_rules.push(rule)
      Cell(r, c) => {
        let row = map_index(row_map, r)
        let col = map_index(col_map, c)
        if row >= 0 && col >= 0 {
          next_align_rules.push((Cell(row, col), rule.1))
        }
      }
    }
  })
  self.align_rules.clear()
  next_align_rules.each(rule => self.align_rules.push(rule))

  let next_valign_rules = []
  self.valign_rules.each(rule => {
    match rule.0 {
      RowRange(start, end) =>
        mapped_runs(row_map, start, end).each(run => {
          next_valign_rules.push((RowRange(run.0, run.1), rule.1))
        })
      ColRange(start, end) =>
        mapped_runs(col_map, start, end).each(run => {
          next_valign_rules.push((ColRange(run.0, run.1), rule.1))
        })
      All => next_valign_rules.push(rule)
      Cell(r, c) => {
        let row = map_index(row_map, r)
        let col = map_index(col_map, c)
        if row >= 0 && col >= 0 {
          next_valign_rules.push((Cell(row, col), rule.1))
        }
      }
    }
  })
  self.valign_rules.clear()
  next_valign_rules.each(rule => self.valign_rules.push(rule))

  let next_padding_rules = []
  self.padding_rules.each(rule => {
    match rule.0 {
      RowRange(start, end) =>
        mapped_runs(row_map, start, end).each(run => {
          next_padding_rules.push(
            (RowRange(run.0, run.1), rule.1, rule.2, rule.3, rule.4),
          )
        })
      ColRange(start, end) =>
        mapped_runs(col_map, start, end).each(run => {
          next_padding_rules.push(
            (ColRange(run.0, run.1), rule.1, rule.2, rule.3, rule.4),
          )
        })
      All => next_padding_rules.push(rule)
      Cell(r, c) => {
        let row = map_index(row_map, r)
        let col = map_index(col_map, c)
        if row >= 0 && col >= 0 {
          next_padding_rules.push(
            (Cell(row, col), rule.1, rule.2, rule.3, rule.4),
          )
        }
      }
    }
  })
  self.padding_rules.clear()
  next_padding_rules.each(rule => self.padding_rules.push(rule))

  let next_width_rules = []
  self.width_rules.each(rule => {
    match rule.0 {
      RowRange(start, end) =>
        mapped_runs(row_map, start, end).each(run => {
          next_width_rules.push((RowRange(run.0, run.1), rule.1))
        })
      ColRange(start, end) =>
        mapped_runs(col_map, start, end).each(run => {
          next_width_rules.push((ColRange(run.0, run.1), rule.1))
        })
      All => next_width_rules.push(rule)
      Cell(r, c) => {
        let row = map_index(row_map, r)
        let col = map_index(col_map, c)
        if row >= 0 && col >= 0 {
          next_width_rules.push((Cell(row, col), rule.1))
        }
      }
    }
  })
  self.width_rules.clear()
  next_width_rules.each(rule => self.width_rules.push(rule))

  let next_height_rules = []
  self.height_rules.each(rule => {
    match rule.0 {
      RowRange(start, end) =>
        mapped_runs(row_map, start, end).each(run => {
          next_height_rules.push((RowRange(run.0, run.1), rule.1))
        })
      ColRange(start, end) =>
        mapped_runs(col_map, start, end).each(run => {
          next_height_rules.push((ColRange(run.0, run.1), rule.1))
        })
      All => next_height_rules.push(rule)
      Cell(r, c) => {
        let row = map_index(row_map, r)
        let col = map_index(col_map, c)
        if row >= 0 && col >= 0 {
          next_height_rules.push((Cell(row, col), rule.1))
        }
      }
    }
  })
  self.height_rules.clear()
  next_height_rules.each(rule => self.height_rules.push(rule))

  let next_custom_horizontal_lines = []
  self.custom_horizontal_lines.each(line => {
    let boundary = line.0
    if boundary <= 0 || boundary >= row_map.length() {
      return
    }
    let prev_row = map_index(row_map, boundary - 1)
    let next_row = map_index(row_map, boundary)
    if prev_row >= 0 && next_row >= 0 && next_row == prev_row + 1 {
      next_custom_horizontal_lines.push((next_row, line.1))
    }
  })
  self.custom_horizontal_lines.clear()
  next_custom_horizontal_lines.each(line => {
    self.custom_horizontal_lines.push(line)
  })

  let next_col_spans = []
  self.col_spans.each(item => {
    let mapped_row = map_index(row_map, item.0)
    if mapped_row < 0 {
      return
    }
    let cols = mapped_runs(col_map, item.1, item.1 + item.2)
    if cols.is_empty() {
      return
    }
    let run = cols[0]
    let span = run.1 - run.0
    if span > 1 {
      next_col_spans.push((mapped_row, run.0, span))
    }
  })
  self.col_spans.clear()
  next_col_spans.each(item => self.col_spans.push(item))

  let next_row_spans = []
  self.row_spans.each(item => {
    let rows = mapped_runs(row_map, item.0, item.0 + item.2)
    let mapped_col = map_index(col_map, item.1)
    if rows.is_empty() || mapped_col < 0 {
      return
    }
    let run = rows[0]
    let span = run.1 - run.0
    if span > 1 {
      next_row_spans.push((run.0, mapped_col, span))
    }
  })
  self.row_spans.clear()
  next_row_spans.each(item => self.row_spans.push(item))

  let next_span_sources = []
  self.span_sources.each(item => {
    let mut row_span = 1
    previous_row_spans.each(span => {
      if span.0 == item.0 && span.1 == item.1 {
        row_span = span.2
      }
    })
    let mut col_span = 1
    previous_col_spans.each(span => {
      if span.0 == item.0 && span.1 == item.1 {
        col_span = span.2
      }
    })
    let row_runs = mapped_runs(row_map, item.0, item.0 + row_span)
    let col_runs = mapped_runs(col_map, item.1, item.1 + col_span)
    let source_row = map_index(row_map, item.2)
    let source_col = map_index(col_map, item.3)
    if !row_runs.is_empty() &&
      !col_runs.is_empty() &&
      source_row >= 0 &&
      source_col >= 0 {
      let anchor_row = row_runs[0].0
      let anchor_col = col_runs[0].0
      next_span_sources.push((anchor_row, anchor_col, source_row, source_col))
    }
  })
  self.span_sources.clear()
  next_span_sources.each(item => self.span_sources.push(item))
}

///|
/// Shift all spans whose row >= target_row downward by 1.
/// Used when inserting a horizontal panel row.
pub fn SpannedConfig::shift_row_spans_down(
  self : SpannedConfig,
  target_row : Int,
) -> Unit {
  // Collect current spans, then rewrite them shifted
  let col_spans : Array[(Int, Int, Int)] = []
  self.col_spans.each(item => col_spans.push(item))
  let row_spans : Array[(Int, Int, Int)] = []
  self.row_spans.each(item => row_spans.push(item))
  // Process col_spans: shift rows >= target_row
  col_spans.each(item => {
    if item.0 >= target_row {
      self.set_col_span(item.0, item.1, 1) // remove old
      self.set_col_span(item.0 + 1, item.1, item.2) // add shifted
    }
  })
  // Process row_spans: shift rows >= target_row
  row_spans.each(item => {
    if item.0 >= target_row {
      self.set_row_span(item.0, item.1, 1) // remove old
      self.set_row_span(item.0 + 1, item.1, item.2) // add shifted
    }
  })
  // Shift span sources
  let sources : Array[(Int, Int, Int, Int)] = []
  self.span_sources.each(item => sources.push(item))
  self.span_sources.clear()
  sources.each(item => {
    let anchor_row = if item.0 >= target_row { item.0 + 1 } else { item.0 }
    let source_row = if item.2 >= target_row { item.2 + 1 } else { item.2 }
    self.span_sources.push((anchor_row, item.1, source_row, item.3))
  })
  // Shift border overrides
  let h = self.border_horizontal.copy()
  self.border_horizontal.clear()
  h.each(item => {
    let rb = if item.0 >= target_row { item.0 + 1 } else { item.0 }
    self.border_horizontal.push((rb, item.1, item.2))
  })
  let v = self.border_vertical.copy()
  self.border_vertical.clear()
  v.each(item => {
    let r = if item.0 >= target_row { item.0 + 1 } else { item.0 }
    self.border_vertical.push((r, item.1, item.2))
  })
  let ix = self.border_intersection.copy()
  self.border_intersection.clear()
  ix.each(item => {
    let rb = if item.0 >= target_row { item.0 + 1 } else { item.0 }
    self.border_intersection.push((rb, item.1, item.2))
  })
}

///|
/// Shift all spans whose col >= target_col rightward by 1.
/// Used when inserting a vertical panel col.
pub fn SpannedConfig::shift_col_spans_right(
  self : SpannedConfig,
  target_col : Int,
) -> Unit {
  let col_spans : Array[(Int, Int, Int)] = []
  self.col_spans.each(item => col_spans.push(item))
  let row_spans : Array[(Int, Int, Int)] = []
  self.row_spans.each(item => row_spans.push(item))
  // Process col_spans: shift cols >= target_col
  col_spans.each(item => {
    if item.1 >= target_col {
      self.set_col_span(item.0, item.1, 1) // remove old
      self.set_col_span(item.0, item.1 + 1, item.2) // add shifted
    }
  })
  // Process row_spans: shift cols >= target_col
  row_spans.each(item => {
    if item.1 >= target_col {
      self.set_row_span(item.0, item.1, 1) // remove old
      self.set_row_span(item.0, item.1 + 1, item.2) // add shifted
    }
  })
  // Shift span sources
  let sources : Array[(Int, Int, Int, Int)] = []
  self.span_sources.each(item => sources.push(item))
  self.span_sources.clear()
  sources.each(item => {
    let anchor_col = if item.1 >= target_col { item.1 + 1 } else { item.1 }
    let source_col = if item.3 >= target_col { item.3 + 1 } else { item.3 }
    self.span_sources.push((item.0, anchor_col, item.2, source_col))
  })
  // Shift border overrides
  let h = self.border_horizontal.copy()
  self.border_horizontal.clear()
  h.each(item => {
    let c = if item.1 >= target_col { item.1 + 1 } else { item.1 }
    self.border_horizontal.push((item.0, c, item.2))
  })
  let v = self.border_vertical.copy()
  self.border_vertical.clear()
  v.each(item => {
    let cb = if item.1 >= target_col { item.1 + 1 } else { item.1 }
    self.border_vertical.push((item.0, cb, item.2))
  })
  let ix = self.border_intersection.copy()
  self.border_intersection.clear()
  ix.each(item => {
    let cb = if item.1 >= target_col { item.1 + 1 } else { item.1 }
    self.border_intersection.push((item.0, cb, item.2))
  })
}