///|
priv struct RowValues {
  row : Int
  values : Array[String]
}

///|
struct RowCells {
  row : Int
  cells : Array[Cell]
  max_col : Int
}

///|
pub struct RowOpts {
  height : Double
  hidden : Bool
  style_id : Int
  outline_level : Int
} derive(Debug)

///|
pub fn RowOpts::with_values(
  height? : Double = 0.0,
  hidden? : Bool = false,
  style_id? : Int = 0,
  outline_level? : Int = 0,
) -> RowOpts {
  { height, hidden, style_id, outline_level }
}

///|
pub struct Rows {
  sheet : Worksheet
  entries : Array[RowCells]
  mut index : Int
  styles : Array[Style]?
  options : Options
  use_1904_format : Bool
}

///|
fn collect_row_cells(sheet : Worksheet) -> Array[RowCells] {
  let cells = sheet.sorted_cells()
  let row_cells : Map[Int, Array[Cell]] = Map([])
  for cell in cells {
    let entries = row_cells.get(cell.row).unwrap_or([])
    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)
  let entries : Array[RowCells] = []
  for row in row_keys {
    let row_entries = row_cells.get(row).unwrap_or([])
    if row_entries.is_empty() {
      entries.push({ row, cells: [], max_col: 0 })
      continue
    }
    let mut max_col = 0
    for cell in row_entries {
      if cell.col > max_col {
        max_col = cell.col
      }
    }
    entries.push({ row, cells: row_entries, max_col })
  }
  entries
}

///|
fn Rows::new(sheet : Worksheet) -> Rows {
  Rows::new_with_options(sheet, None, Options::new())
}

///|
fn Rows::new_with_options(
  sheet : Worksheet,
  styles : Array[Style]?,
  options : Options,
  use_1904_format? : Bool = false,
) -> Rows {
  {
    sheet,
    entries: collect_row_cells(sheet),
    index: 0,
    styles,
    options,
    use_1904_format,
  }
}

///|
pub fn Rows::next(self : Rows) -> Bool {
  if self.index < self.entries.length() {
    self.index = self.index + 1
    true
  } else {
    false
  }
}

///|
pub fn Rows::columns(
  self : Rows,
  options? : Options,
) -> Array[String] raise XlsxError {
  if self.index <= 0 || self.index > self.entries.length() {
    return []
  }
  let entry = self.entries[self.index - 1]
  if entry.max_col == 0 {
    return []
  }
  let resolved_options = options.unwrap_or(self.options)
  let values : Array[String] = Array::make(entry.max_col, "")
  match self.styles {
    Some(styles) =>
      for cell in entry.cells {
        let formatted = format_cell_value_for_workbook(
          styles,
          cell,
          false,
          resolved_options,
          use_1904_format=self.use_1904_format,
        )
        values[cell.col - 1] = formatted
      }
    None =>
      for cell in entry.cells {
        values[cell.col - 1] = cell.value
      }
  }
  values
}

///|
pub fn Rows::row_index(self : Rows) -> Int? {
  if self.index <= 0 || self.index > self.entries.length() {
    return None
  }
  Some(self.entries[self.index - 1].row)
}

///|
fn default_row_height(sheet : Worksheet) -> Double {
  let props = sheet.sheet_props()
  props.default_row_height.unwrap_or(15.0)
}

///|
fn row_opts_for_row(sheet : Worksheet, row : Int) -> RowOpts {
  let dim = sheet.row_dimension_value(row)
  let height = match dim.height {
    Some(value) => value
    None => default_row_height(sheet)
  }
  let style_id = dim.style_id.unwrap_or(0)
  { height, hidden: dim.hidden, style_id, outline_level: dim.outline_level }
}

///|
pub fn Rows::get_row_opts(self : Rows) -> RowOpts {
  match self.row_index() {
    Some(row) => row_opts_for_row(self.sheet, row)
    None => RowOpts::with_values(height=default_row_height(self.sheet))
  }
}

///|
pub fn Rows::error(_self : Rows) -> XlsxError? {
  None
}

///|
pub fn Rows::close(_self : Rows) -> Unit {
  ()
}

///|
pub struct Cols {
  sheet : Worksheet
  columns : Array[Array[Cell]]
  max_row : Int
  mut index : Int
  styles : Array[Style]?
  options : Options
  use_1904_format : Bool
  mut err : XlsxError?
}

///|
fn max_row_for_columns(sheet : Worksheet) -> Int {
  let mut max_row = sheet.max_row()
  for row, _ in sheet.row_dimensions {
    if row > max_row {
      max_row = row
    }
  }
  max_row
}

///|
fn collect_column_cells(sheet : Worksheet, max_col : Int) -> Array[Array[Cell]] {
  let columns : Array[Array[Cell]] = []
  for _ in 0..= 1 && cell.col <= max_col {
      columns[cell.col - 1].push(cell)
    }
  }
  columns
}

///|
fn collect_row_entries_with_options(
  sheet : Worksheet,
  styles : ArrayView[Style],
  options : Options,
  use_1904_format : Bool,
) -> Array[RowValues] raise XlsxError {
  let cells = sheet.sorted_cells()
  let row_cells : Map[Int, Array[Cell]] = Map([])
  for cell in cells {
    let entries = row_cells.get(cell.row).unwrap_or([])
    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)
  let entries : Array[RowValues] = []
  for row in row_keys {
    let row_entries = row_cells.get(row).unwrap_or([])
    if row_entries.is_empty() {
      entries.push({ row, values: [] })
      continue
    }
    let mut max_col = 0
    for cell in row_entries {
      if cell.col > max_col {
        max_col = cell.col
      }
    }
    let values : Array[String] = Array::make(max_col, "")
    for cell in row_entries {
      let formatted = format_cell_value_for_workbook(
        styles,
        cell,
        false,
        options,
        use_1904_format~,
      )
      values[cell.col - 1] = formatted
    }
    entries.push({ row, values })
  }
  entries
}

///|
fn collect_columns_with_options(
  sheet : Worksheet,
  styles : ArrayView[Style],
  options : Options,
  use_1904_format : Bool,
) -> Array[Array[String]] raise XlsxError {
  let max_col = sheet.max_col()
  if max_col <= 0 {
    return []
  }
  let max_row = max_row_for_columns(sheet)
  let col_cells : Map[Int, Array[(Int, String)]] = Map([])
  for cell in sheet.cells {
    let formatted = format_cell_value_for_workbook(
      styles,
      cell,
      false,
      options,
      use_1904_format~,
    )
    let entries = col_cells.get(cell.col).unwrap_or([])
    entries.push((cell.row, formatted))
    col_cells[cell.col] = entries
  }
  let columns : Array[Array[String]] = []
  for col in 1..<=max_col {
    let values : Array[String] = Array::make(max_row, "")
    for entry in col_cells.get(col).unwrap_or([]) {
      let (row, value) = entry
      if row >= 1 && row <= max_row {
        values[row - 1] = value
      }
    }
    columns.push(values)
  }
  columns
}

///|
fn Cols::new(sheet : Worksheet) -> Cols {
  Cols::new_with_options(sheet, None, Options::new())
}

///|
fn Cols::new_with_options(
  sheet : Worksheet,
  styles : Array[Style]?,
  options : Options,
  use_1904_format? : Bool = false,
) -> Cols {
  let max_col = sheet.max_col()
  let columns = if max_col <= 0 {
    []
  } else {
    collect_column_cells(sheet, max_col)
  }
  let max_row = if max_col <= 0 { 0 } else { max_row_for_columns(sheet) }
  {
    sheet,
    columns,
    max_row,
    index: 0,
    styles,
    options,
    use_1904_format,
    err: None,
  }
}

///|
pub fn Cols::next(self : Cols) -> Bool {
  if self.index < self.columns.length() {
    self.index = self.index + 1
    true
  } else {
    false
  }
}

///|
pub fn Cols::rows(self : Cols, options? : Options) -> Array[String] {
  if self.index <= 0 || self.index > self.columns.length() {
    return []
  }
  let resolved_options = options.unwrap_or(self.options)
  if self.max_row <= 0 {
    return []
  }
  let entry = self.columns[self.index - 1]
  let values : Array[String] = Array::make(self.max_row, "")
  match self.styles {
    Some(styles) =>
      for cell in entry {
        if cell.row < 1 || cell.row > self.max_row {
          continue
        }
        let formatted = format_cell_value_for_workbook(
          styles,
          cell,
          false,
          resolved_options,
          use_1904_format=self.use_1904_format,
        ) catch {
          err => {
            if self.err is None {
              self.err = Some(err)
            }
            ""
          }
        }
        if self.err is Some(_) {
          return values
        }
        values[cell.row - 1] = formatted
      }
    None =>
      for cell in entry {
        if cell.row < 1 || cell.row > self.max_row {
          continue
        }
        values[cell.row - 1] = cell.value
      }
  }
  values
}

///|
pub fn Cols::col_index(self : Cols) -> Int? {
  if self.index <= 0 || self.index > self.columns.length() {
    return None
  }
  Some(self.index)
}

///|
pub fn Cols::error(self : Cols) -> XlsxError? {
  self.err
}

///|
pub fn Worksheet::rows(self : Worksheet) -> Rows {
  Rows::new(self)
}

///|
pub fn Worksheet::cols(self : Worksheet) -> Cols {
  Cols::new(self)
}

///|
pub fn Worksheet::get_rows(self : Worksheet) -> Array[Array[String]] {
  let entries = collect_row_cells(self)
  let results : Array[Array[String]] = []
  let mut max_with_values = 0
  for entry in entries {
    let current = entry.row
    let values = if entry.max_col == 0 {
      []
    } else {
      let row_values : Array[String] = Array::make(entry.max_col, "")
      for cell in entry.cells {
        row_values[cell.col - 1] = cell.value
      }
      row_values
    }
    if values.length() > 0 {
      let empty = current - max_with_values - 1
      if empty > 0 {
        for _ in 0.. Array[Array[String]] {
  let cols = Cols::new(self)
  let results : Array[Array[String]] = []
  while cols.next() {
    results.push(cols.rows())
  }
  results
}

///|
pub fn Workbook::rows(
  self : Workbook,
  sheet_name : StringView,
) -> Rows raise XlsxError {
  check_sheet_name(sheet_name)
  let sheet = match self.sheet(sheet_name) {
    Some(value) => value
    None => raise SheetNotFound(name=sheet_name.to_owned())
  }
  Rows::new_with_options(
    sheet,
    Some(self.styles),
    self.options,
    use_1904_format=self.uses_1904_date_system(),
  )
}

///|
pub fn Workbook::cols(
  self : Workbook,
  sheet_name : StringView,
) -> Cols raise XlsxError {
  check_sheet_name(sheet_name)
  let sheet = match self.sheet(sheet_name) {
    Some(value) => value
    None => raise SheetNotFound(name=sheet_name.to_owned())
  }
  Cols::new_with_options(
    sheet,
    Some(self.styles),
    self.options,
    use_1904_format=self.uses_1904_date_system(),
  )
}

///|
pub fn Workbook::get_rows(
  self : Workbook,
  sheet_name : StringView,
  options? : Options,
) -> Array[Array[String]] raise XlsxError {
  check_sheet_name(sheet_name)
  let resolved_options = options.unwrap_or(self.options)
  let sheet = match self.sheet(sheet_name) {
    Some(value) => value
    None => raise SheetNotFound(name=sheet_name.to_owned())
  }
  let entries = collect_row_entries_with_options(
    sheet,
    self.styles,
    resolved_options,
    self.uses_1904_date_system(),
  )
  let results : Array[Array[String]] = []
  let mut max_with_values = 0
  for entry in entries {
    if entry.values.length() > 0 {
      let empty = entry.row - max_with_values - 1
      if empty > 0 {
        for _ in 0.. Array[Array[String]] raise XlsxError {
  check_sheet_name(sheet_name)
  let resolved_options = options.unwrap_or(self.options)
  let sheet = match self.sheet(sheet_name) {
    Some(value) => value
    None => raise SheetNotFound(name=sheet_name.to_owned())
  }
  collect_columns_with_options(
    sheet,
    self.styles,
    resolved_options,
    self.uses_1904_date_system(),
  )
}