///|
pub struct IterGrid {
  records : IterRecords
  config : SpannedConfig
  dimension : IterGridDimension
}

///|
pub fn IterGrid::new(
  records : IterRecords,
  config : SpannedConfig,
  dimension : IterGridDimension,
) -> IterGrid {
  { records, config, dimension }
}

///|
fn render_padding(size : Int) -> String {
  let mut out = ""
  for i in 0.. String {
  let available = width - padding_left - padding_right
  let gap = available - string_display_width(content)
  let (left_fill, right_fill) = match align {
    Left => (0, gap)
    Right => (gap, 0)
    Center => (gap / 2, gap - gap / 2)
  }
  render_padding(padding_left) +
  render_padding(left_fill) +
  content +
  render_padding(right_fill) +
  render_padding(padding_right)
}

///|
fn span_width(
  widths : Array[Int],
  borders : Borders,
  start : Int,
  span : Int,
) -> Int {
  let end_ = if start + span < widths.length() {
    start + span
  } else {
    widths.length()
  }
  let mut total = 0
  for col = start; col < end_; col = col + 1 {
    total = total + widths[col]
  }
  if borders.vertical is Some(_) && end_ - start > 1 {
    total = total + (end_ - start - 1)
  }
  total
}

///|
fn blank_values(count_cols : Int) -> Array[String] {
  let values = []
  for _ in 0.. Int {
  let mut total = 0
  for index = start; index < end_; index = index + 1 {
    total = total + values[index]
  }
  total
}

///|
fn boundary_has_line(
  config : SpannedConfig,
  boundary_row : Int,
  count_rows : Int,
) -> Bool {
  if boundary_row <= 0 || boundary_row >= count_rows {
    return false
  }
  let standard = match config.horizontal_lines() {
    All => true
    HeaderOnly => boundary_row == 1
    None => false
  }
  standard || config.custom_horizontal_line(boundary_row) is Some(_)
}

///|
fn count_range_boundaries(
  config : SpannedConfig,
  count_rows : Int,
  start : Int,
  end_ : Int,
) -> Int {
  let mut total = 0
  for boundary_row = start + 1
      boundary_row < end_
      boundary_row = boundary_row + 1 {
    if boundary_has_line(config, boundary_row, count_rows) {
      total += 1
    }
  }
  total
}

///|
fn inc_range(values : Array[Int], size : Int, start : Int, end_ : Int) -> Unit {
  let span = end_ - start
  if span <= 0 || size <= 0 {
    return
  }
  let one = size / span
  let rest = size - span * one
  for index = start; index < end_; index = index + 1 {
    if index == start {
      values[index] = values[index] + one + rest
    } else {
      values[index] = values[index] + one
    }
  }
}

///|
fn row_span_anchor(config : SpannedConfig, row : Int, col : Int) -> (Int, Int)? {
  let mut index = config.row_spans.length() - 1
  while index >= 0 {
    let item = config.row_spans[index]
    let colspan = config.col_span_at(item.0, item.1)
    if row > item.0 &&
      row < item.0 + item.2 &&
      col >= item.1 &&
      col < item.1 + colspan {
      return Some((item.0, item.1))
    }
    index = index - 1
  }
  None
}

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

///|
fn row_span_boundary_anchor(
  config : SpannedConfig,
  boundary_row : Int,
  col : Int,
) -> (Int, Int)? {
  match row_span_anchor(config, boundary_row, col) {
    Some(anchor) =>
      if boundary_row > anchor.0 &&
        boundary_row < anchor.0 + config.row_span_at(anchor.0, anchor.1) {
        Some(anchor)
      } else {
        None
      }
    None => None
  }
}

///|
fn display_anchor_for(
  config : SpannedConfig,
  row : Int,
  col : Int,
) -> (Int, Int)? {
  match row_span_anchor(config, row, col) {
    Some(anchor) => if anchor.1 == col { Some(anchor) } else { None }
    None =>
      if config.is_cell_visible(row, col) {
        Some((row, col))
      } else {
        None
      }
  }
}

///|
fn span_source_for_anchor(
  config : SpannedConfig,
  anchor_row : Int,
  anchor_col : Int,
) -> (Int, Int) {
  config.span_source_at(anchor_row, anchor_col)
}

///|
fn row_metrics(
  records : IterRecords,
  count_cols : Int,
  config : SpannedConfig,
) -> (Array[Int], Array[Int], Array[Int]) {
  let count_rows = records.count_rows()
  let content_heights = Array::make(count_rows, 0)
  let top_paddings = Array::make(count_rows, 0)
  let bottom_paddings = Array::make(count_rows, 0)
  for row in 0.. 1 {
        if top > top_paddings[row] {
          top_paddings[row] = top
        }
        let last_row = if row + rowspan < count_rows {
          row + rowspan - 1
        } else {
          count_rows - 1
        }
        if bottom > bottom_paddings[last_row] {
          bottom_paddings[last_row] = bottom
        }
      } else {
        if lines.length() > content_heights[row] {
          content_heights[row] = lines.length()
        }
        if top > top_paddings[row] {
          top_paddings[row] = top
        }
        if bottom > bottom_paddings[row] {
          bottom_paddings[row] = bottom
        }
      }
    }
  }

  let mut changed = true
  while changed {
    changed = false
    for row in 0.. current {
          inc_range(content_heights, needed - current, row, end_)
          changed = true
        }
      }
    }
  }
  (content_heights, top_paddings, bottom_paddings)
}

///|
fn cell_content_for_row(
  records : IterRecords,
  config : SpannedConfig,
  row_heights : Array[Int],
  row : Int,
  col : Int,
  line_index : Int,
) -> String {
  let anchor = match display_anchor_for(config, row, col) {
    Some(anchor) => anchor
    None => return ""
  }
  let source = span_source_for_anchor(config, anchor.0, anchor.1)
  if anchor.0 == row && config.row_span_at(anchor.0, anchor.1) <= 1 {
    let lines = processed_lines(
      records.get_cell(source.0, source.1),
      config.width_mode_at(source.0, source.1),
      config.height_mode_at(source.0, source.1),
    )
    let missing = row_heights[row] - lines.length()
    let valign = config.valign_at(source.0, source.1)
    let top_fill = match valign {
      Top => 0
      Bottom => missing
      Center => missing / 2
    }
    if line_index < top_fill || line_index >= top_fill + lines.length() {
      return ""
    }
    return lines[line_index - top_fill]
  }

  let rowspan = config.row_span_at(anchor.0, anchor.1)
  let end_ = if anchor.0 + rowspan < row_heights.length() {
    anchor.0 + rowspan
  } else {
    row_heights.length()
  }
  let total_height = sum_range(row_heights, anchor.0, end_) +
    count_range_boundaries(config, row_heights.length(), anchor.0, end_)
  let lines = processed_lines(
    records.get_cell(source.0, source.1),
    config.width_mode_at(source.0, source.1),
    config.height_mode_at(source.0, source.1),
  )
  let missing = total_height - lines.length()
  let valign = config.valign_at(source.0, source.1)
  let top_fill = match valign {
    Top => 0
    Bottom => missing
    Center => missing / 2
  }
  let mut offset = 0
  for index = anchor.0; index < row; index = index + 1 {
    offset = offset + row_heights[index]
    if boundary_has_line(config, index + 1, row_heights.length()) {
      offset += 1
    }
  }
  let absolute = offset + line_index
  if absolute < top_fill || absolute >= top_fill + lines.length() {
    ""
  } else {
    lines[absolute - top_fill]
  }
}

///|
fn cell_content_for_boundary(
  records : IterRecords,
  config : SpannedConfig,
  row_heights : Array[Int],
  boundary_row : Int,
  col : Int,
) -> String {
  let anchor = match row_span_boundary_anchor(config, boundary_row, col) {
    Some(anchor) => anchor
    None => return ""
  }
  let source = span_source_for_anchor(config, anchor.0, anchor.1)
  let rowspan = config.row_span_at(anchor.0, anchor.1)
  let end_ = if anchor.0 + rowspan < row_heights.length() {
    anchor.0 + rowspan
  } else {
    row_heights.length()
  }
  let total_height = sum_range(row_heights, anchor.0, end_) +
    count_range_boundaries(config, row_heights.length(), anchor.0, end_)
  let lines = processed_lines(
    records.get_cell(source.0, source.1),
    config.width_mode_at(source.0, source.1),
    config.height_mode_at(source.0, source.1),
  )
  let missing = total_height - lines.length()
  let valign = config.valign_at(source.0, source.1)
  let top_fill = match valign {
    Top => 0
    Bottom => missing
    Center => missing / 2
  }
  let mut absolute = 0
  for row = anchor.0; row < boundary_row; row = row + 1 {
    absolute = absolute + row_heights[row]
    if row + 1 < boundary_row &&
      boundary_has_line(config, row + 1, row_heights.length()) {
      absolute += 1
    }
  }
  if absolute < top_fill || absolute >= top_fill + lines.length() {
    ""
  } else {
    lines[absolute - top_fill]
  }
}

///|
fn render_split_line(
  records : IterRecords,
  borders : Borders,
  widths : Array[Int],
  boundary_row : Int,
  count_cols : Int,
  config : SpannedConfig,
  row_heights : Array[Int],
) -> String {
  let mut has_uncovered = false
  let mut has_content = false
  for col in 0.. ch.to_string()
    None =>
      match first_char([borders.left_intersection, borders.left]) {
        Some(value) => value.to_string()
        None => ""
      }
  }
  let mut col = 0
  while col < count_cols {
    let covered = row_span_crosses_boundary(config, boundary_row, col)
    let fill = match row_span_boundary_anchor(config, boundary_row, col) {
      Some(anchor) =>
        if anchor.1 == col {
          let source = span_source_for_anchor(config, anchor.0, anchor.1)
          let (padding_left, padding_right, _, _) = config.padding_at(
            source.0,
            source.1,
          )
          let align = config.align_at(source.0, source.1)
          let colspan = config.col_span_at(anchor.0, anchor.1)
          col = col + colspan
          render_cell(
            cell_content_for_boundary(
              records,
              config,
              row_heights,
              boundary_row,
              col - colspan,
            ),
            span_width(widths, borders, anchor.1, colspan),
            padding_left,
            padding_right,
            align,
          )
        } else {
          col = col + 1
          continue
        }
      None => {
        col = col + 1
        if covered {
          " ".repeat(widths[col - 1])
        } else {
          match borders.horizontal {
            Some(value) => value.to_string().repeat(widths[col - 1])
            None => " ".repeat(widths[col - 1])
          }
        }
      }
    }
    out = out + fill
    if col < count_cols {
      let current_anchor = row_span_boundary_anchor(
        config,
        boundary_row,
        col - 1,
      )
      let next_anchor = row_span_boundary_anchor(config, boundary_row, col)
      let join = match config.intersection_override_at(boundary_row, col) {
        Some(ch) =>
          if current_anchor == next_anchor && current_anchor is Some(_) {
            ""
          } else {
            ch.to_string()
          }
        None =>
          match current_anchor {
            Some(_) =>
              if current_anchor == next_anchor {
                ""
              } else {
                match
                  first_char([
                    borders.intersection,
                    borders.vertical,
                    borders.horizontal,
                  ]) {
                  Some(value) => value.to_string()
                  None => ""
                }
              }
            None =>
              match
                first_char([
                  borders.intersection,
                  borders.vertical,
                  borders.horizontal,
                ]) {
                Some(value) => value.to_string()
                None => ""
              }
          }
      }
      out = out + join
    }
  }
  out +
  (match config.intersection_override_at(boundary_row, count_cols) {
    Some(ch) => ch.to_string()
    None =>
      match first_char([borders.right_intersection, borders.right]) {
        Some(value) => value.to_string()
        None => ""
      }
  })
}

///|
fn render_row_line(
  borders : Borders,
  widths : Array[Int],
  values : Array[String],
  row_index : Int,
  count_cols : Int,
  config : SpannedConfig,
) -> String {
  let has_overrides = config.has_cell_borders()
  let left_ch = if has_overrides {
    match config.vertical_override_at(row_index, 0) {
      Some(ch) => Some(ch)
      None => borders.left
    }
  } else {
    borders.left
  }
  let mut out = match left_ch {
    Some(value) => value.to_string()
    None => ""
  }

  let mut col = 0
  while col < count_cols {
    let anchor = match display_anchor_for(config, row_index, col) {
      Some(anchor) => anchor
      None => {
        col = col + 1
        continue
      }
    }
    if anchor.1 != col {
      col = col + 1
      continue
    }
    let content = if col < values.length() { values[col] } else { "" }
    let (padding_left, padding_right, _, _) = config.padding_at(
      anchor.0,
      anchor.1,
    )
    let align = config.align_at(anchor.0, anchor.1)
    let colspan = config.col_span_at(anchor.0, anchor.1)
    out = out +
      render_cell(
        content,
        span_width(widths, borders, anchor.1, colspan),
        padding_left,
        padding_right,
        align,
      )
    col = col + colspan
    if col < count_cols {
      let sep_ch = if has_overrides {
        match config.vertical_override_at(row_index, col) {
          Some(ch) => Some(ch)
          None => borders.vertical
        }
      } else {
        borders.vertical
      }
      out = out +
        (match sep_ch {
          Some(value) => value.to_string()
          None => ""
        })
    }
  }

  let right_ch = if has_overrides {
    match config.vertical_override_at(row_index, count_cols) {
      Some(ch) => Some(ch)
      None => borders.right
    }
  } else {
    borders.right
  }
  out +
  (match right_ch {
    Some(value) => value.to_string()
    None => ""
  })
}

///|
fn render_row(
  records : IterRecords,
  borders : Borders,
  widths : Array[Int],
  row_index : Int,
  count_cols : Int,
  config : SpannedConfig,
  row_heights : Array[Int],
  row_top_paddings : Array[Int],
  row_bottom_paddings : Array[Int],
) -> Array[String] {
  let lines = []
  for i in 0.. String {
  let left = match config.intersection_override_at(row_boundary, 0) {
    Some(ch) => Some(ch)
    None => default_left
  }
  let mut out = match left {
    Some(value) => value.to_string()
    None => ""
  }
  for col in 0.. Some(ch)
      None => default_fill
    }
    let w = widths[col]
    out = out +
      (match fill {
        Some(value) => value.to_string().repeat(w)
        None => "".repeat(w)
      })
    if col + 1 < count_cols {
      let ix = match config.intersection_override_at(row_boundary, col + 1) {
        Some(ch) => Some(ch)
        None => default_intersection
      }
      out = out +
        (match ix {
          Some(value) => value.to_string()
          None => ""
        })
    }
  }
  let right = match config.intersection_override_at(row_boundary, count_cols) {
    Some(ch) => Some(ch)
    None => default_right
  }
  out +
  (match right {
    Some(value) => value.to_string()
    None => ""
  })
}

///|
pub fn IterGrid::to_string(self : IterGrid) -> String {
  let widths = self.dimension.widths()
  let borders = self.config.borders()
  if widths.is_empty() {
    return ""
  }

  let lines : Array[String] = []
  let has_overrides = self.config.has_cell_borders()
  let count_cols = self.records.count_cols()
  let top_left = match borders.top {
    Some(_) => first_char([borders.top_left, borders.left])
    None => None
  }
  let top_intersection = match borders.top {
    Some(_) => first_char([borders.top_intersection, borders.top])
    None => None
  }
  let top_right = match borders.top {
    Some(_) => first_char([borders.top_right, borders.right])
    None => None
  }
  let top = if has_overrides {
    build_line_with_overrides(
      self.config,
      widths,
      top_left,
      borders.top,
      top_intersection,
      top_right,
      0,
      count_cols,
    )
  } else {
    borders.build_line(
      top_left,
      borders.top,
      top_intersection,
      top_right,
      widths,
    )
  }
  if top != "" {
    lines.push(top)
  }

  let rows = self.records.rows()
  let (row_heights, row_top_paddings, row_bottom_paddings) = row_metrics(
    self.records,
    count_cols,
    self.config,
  )
  let horizontal_lines = self.config.horizontal_lines()
  for row_index in 0.. lines.push(line))
    let show_middle = match horizontal_lines {
      All => row_index + 1 < rows.length()
      HeaderOnly => row_index == 0 && row_index + 1 < rows.length()
      None => false
    }
    let custom_middle = self.config.custom_horizontal_line(row_index + 1)
    if show_middle || custom_middle is Some(_) {
      let boundary = row_index + 1
      let middle = match custom_middle {
        Some(line) =>
          if self.config.has_row_spans() {
            render_split_line(
              self.records,
              {
                ..borders,
                horizontal: line.main,
                left_intersection: first_char([
                  line.left,
                  borders.left_intersection,
                  borders.left,
                ]),
                intersection: first_char([
                  line.intersection,
                  borders.intersection,
                  borders.vertical,
                  line.main,
                ]),
                right_intersection: first_char([
                  line.right,
                  borders.right_intersection,
                  borders.right,
                ]),
              },
              widths,
              boundary,
              count_cols,
              self.config,
              row_heights,
            )
          } else if has_overrides {
            build_line_with_overrides(
              self.config,
              widths,
              first_char([line.left, borders.left_intersection, borders.left]),
              line.main,
              first_char([
                line.intersection,
                borders.intersection,
                borders.vertical,
                line.main,
              ]),
              first_char([line.right, borders.right_intersection, borders.right]),
              boundary,
              count_cols,
            )
          } else {
            borders.build_line(
              first_char([line.left, borders.left_intersection, borders.left]),
              line.main,
              first_char([
                line.intersection,
                borders.intersection,
                borders.vertical,
                line.main,
              ]),
              first_char([line.right, borders.right_intersection, borders.right]),
              widths,
            )
          }
        None =>
          if self.config.has_row_spans() {
            render_split_line(
              self.records,
              borders,
              widths,
              boundary,
              count_cols,
              self.config,
              row_heights,
            )
          } else if has_overrides {
            build_line_with_overrides(
              self.config,
              widths,
              first_char([borders.left_intersection, borders.left]),
              borders.horizontal,
              first_char([
                borders.intersection,
                borders.vertical,
                borders.horizontal,
              ]),
              first_char([borders.right_intersection, borders.right]),
              boundary,
              count_cols,
            )
          } else {
            borders.build_line(
              first_char([borders.left_intersection, borders.left]),
              borders.horizontal,
              first_char([
                borders.intersection,
                borders.vertical,
                borders.horizontal,
              ]),
              first_char([borders.right_intersection, borders.right]),
              widths,
            )
          }
      }
      if middle != "" {
        lines.push(middle)
      }
    }
  }

  let bottom_left = match borders.bottom {
    Some(_) => first_char([borders.bottom_left, borders.left])
    None => None
  }
  let bottom_intersection = match borders.bottom {
    Some(_) => first_char([borders.bottom_intersection, borders.bottom])
    None => None
  }
  let bottom_right = match borders.bottom {
    Some(_) => first_char([borders.bottom_right, borders.right])
    None => None
  }
  let bottom = if has_overrides {
    build_line_with_overrides(
      self.config,
      widths,
      bottom_left,
      borders.bottom,
      bottom_intersection,
      bottom_right,
      rows.length(),
      count_cols,
    )
  } else {
    borders.build_line(
      bottom_left,
      borders.bottom,
      bottom_intersection,
      bottom_right,
      widths,
    )
  }
  if bottom != "" {
    lines.push(bottom)
  }

  // Apply margin if configured
  if self.config.has_margin() {
    let total_width = if lines.is_empty() { 0 } else { lines[0].length() }
    let full_width = total_width +
      self.config.margin_left +
      self.config.margin_right
    let table_height = lines.length()
    // Add left/right margins to each line
    if self.config.margin_left > 0 || self.config.margin_right > 0 {
      for i in 0.. 0 {
      let top_lines = margin_horizontal_lines(
        self.config.margin_fill_top,
        self.config.margin_top,
        self.config.margin_offset_top,
        full_width,
      )
      for i = top_lines.length() - 1; i >= 0; i = i - 1 {
        lines.insert(0, top_lines[i])
      }
    }
    // Append bottom margin lines
    if self.config.margin_bottom > 0 {
      let bottom_lines = margin_horizontal_lines(
        self.config.margin_fill_bottom,
        self.config.margin_bottom,
        self.config.margin_offset_bottom,
        full_width,
      )
      for i in 0.. String {
  if size == 0 {
    return ""
  }
  let offset = if offset > height { height } else { offset }
  if offset >= 0 {
    // Start offset: line < offset → spaces, line >= offset → fill
    if line >= offset {
      fill.to_string().repeat(size)
    } else {
      " ".repeat(size)
    }
  } else {
    // End offset: from end
    let abs_off = -offset
    let abs_off = if abs_off > height { height } else { abs_off }
    let pos = height - abs_off
    if line >= pos {
      " ".repeat(size)
    } else {
      fill.to_string().repeat(size)
    }
  }
}

///|
/// Build horizontal margin lines (top or bottom).
/// `offset` uses Start semantics: cols before offset get spaces, rest get fill.
fn margin_horizontal_lines(
  fill : Char,
  size : Int,
  offset : Int,
  width : Int,
) -> Array[String] {
  if size == 0 {
    return []
  }
  let start_offset = if offset >= 0 {
    if offset > width {
      width
    } else {
      offset
    }
  } else {
    0
  }
  let end_offset = if offset < 0 {
    let abs_off = -offset
    if abs_off > width {
      width
    } else {
      abs_off
    }
  } else {
    0
  }
  let indent_size = width - start_offset - end_offset
  let result = []
  for _ in 0..