///|
pub using @papergrid {type Offset}

///|
/// Reverse a contiguous range of rows or columns.
pub struct Reverse {
  priv columns : Bool
  priv start : Int
  priv end_ : Offset
}

///|
/// Reverse all rows from `start`, including `start`.
pub fn Reverse::rows(start : Int) -> Reverse {
  { columns: false, start, end_: End(0), }
}

///|
/// Reverse all columns from `start`, including `start`.
pub fn Reverse::cols(start : Int) -> Reverse {
  { columns: true, start, end_: End(0), }
}

///|
/// Reverse exactly `count` items, starting at the configured index.
/// A negative count or a range extending past the table leaves it unchanged.
/// Replaces any previous `take` or `exclude_last` bound.
pub fn Reverse::take(self : Reverse, count : Int) -> Reverse {
  { ..self, end_: Start(count), }
}

///|
/// Exclude `count` trailing rows or columns from the reversed range.
/// Replaces any previous `take` or `exclude_last` bound.
///
/// ```mbt check
/// test {
///   let table = @tabular.Table::from_rows([["a"], ["b"], ["c"], ["d"], ["e"]])
///   table.reverse(@tabular.Reverse::rows(1).exclude_last(1)) |> ignore
///   assert_eq(table.rows, [["a"], ["d"], ["c"], ["b"], ["e"]])
/// }
/// ```
pub fn Reverse::exclude_last(self : Reverse, count : Int) -> Reverse {
  { ..self, end_: End(count), }
}

///|
/// Reverse records without moving coordinate-based formatting.
/// Invalid ranges leave the table unchanged.
///
/// ```mbt check
/// test {
///   let table = @tabular.Table::from_rows([["a"], ["b"], ["c"], ["d"]])
///   table.reverse(@tabular.Reverse::rows(1).take(2)) |> ignore
///   assert_eq(table.rows, [["a"], ["c"], ["b"], ["d"]])
/// }
/// ```
pub fn Table::reverse(self : Table, reverse : Reverse) -> Table {
  let records = @papergrid.IterRecords::new(self.rows)
  let count = if reverse.columns {
    records.count_cols()
  } else {
    records.count_rows()
  }
  let start = reverse.start
  guard start >= 0 && start < count else { return self }
  let end_ = match reverse.end_ {
    Start(length) => {
      guard length >= 0 && length <= count - start else { return self }
      start + length
    }
    End(excluded) => {
      guard excluded >= 0 && excluded <= count else { return self }
      count - excluded
    }
  }
  guard end_ > start else { return self }
  if reverse.columns {
    self.rows = Array::makei(records.count_rows(), row => {
      Array::makei(count, col => {
        let source = if col >= start && col < end_ {
          end_ - 1 - (col - start)
        } else {
          col
        }
        records.get_cell(row, source)
      })
    })
  } else {
    for i in 0..<((end_ - start) / 2) {
      self.rows.swap(start + i, end_ - 1 - i)
    }
  }
  self.finish_legacy_change()
}