///|
priv suberror TabError {
  TabError(String)
}

///|
priv struct TabStops {
  columns : Array[Int64]
  interval : Int64
  relative : Bool
}

///|
fn parse_tabs(values : Array[String]) -> TabStops raise TabError {
  let columns : Array[Int64] = []
  let mut absolute_interval = 0L
  let mut increment = 0L
  for text in values {
    let mut offset = 0
    let mut mode = 0
    while offset < text.length() {
      if text[offset] is (',' | ' ' | '\t') {
        offset += 1
        continue
      }
      if text[offset] is ('/' | '+') {
        mode = if text[offset] == '/' { 1 } else { 2 }
        offset += 1
        continue
      }
      let start = offset
      while offset < text.length() && text[offset] >= '0' && text[offset] <= '9' {
        offset += 1
      }
      if offset == start {
        raise TabError(
          "tab size contains invalid character(s): '\{text[offset:]}'",
        )
      }
      let number = text[start:offset]
      let value = @string.parse_int64(number) catch {
        _ => raise TabError("tab stop is too large '\{number}'")
      }
      if offset < text.length() && !(text[offset] is (',' | ' ' | '\t')) {
        if text[offset] is ('/' | '+') {
          raise TabError(
            "'\{text[offset:offset + 1]}' specifier not at start of number: '\{text[offset:]}'",
          )
        }
        raise TabError(
          "tab size contains invalid character(s): '\{text[offset:]}'",
        )
      }
      match mode {
        1 => {
          if absolute_interval != 0 {
            raise TabError("'/' specifier only allowed with the last value")
          }
          absolute_interval = value
        }
        2 => {
          if increment != 0 {
            raise TabError("'+' specifier only allowed with the last value")
          }
          increment = value
        }
        _ => columns.push(value)
      }
    }
  }
  let mut previous = 0L
  for stop in columns {
    if stop == 0 {
      raise TabError("tab size cannot be 0")
    }
    if stop <= previous {
      raise TabError("tab sizes must be ascending")
    }
    previous = stop
  }
  if absolute_interval != 0 && increment != 0 {
    raise TabError("'/' specifier is mutually exclusive with '+'")
  }
  let interval = if absolute_interval != 0 {
    absolute_interval
  } else {
    increment
  }
  if columns.is_empty() {
    {
      columns,
      interval: if interval == 0 {
        8L
      } else {
        interval
      },
      relative: false,
    }
  } else if columns.length() == 1 && interval == 0 {
    { columns: [], interval: columns[0], relative: false, }
  } else {
    { columns, interval, relative: increment != 0, }
  }
}

///|
fn next_tab(column : Int64, stops : TabStops) -> Int64? raise TabError {
  for stop in stops.columns {
    if stop > column {
      return Some(stop)
    }
  }
  if stops.interval == 0 {
    if column == 9223372036854775807L {
      raise TabError("input line is too long")
    }
    return None
  }
  let origin = if stops.relative {
    stops.columns.last().unwrap_or(0L)
  } else {
    0L
  }
  let distance = stops.interval - (column - origin) % stops.interval
  if column > 9223372036854775807L - distance {
    raise TabError("input line is too long")
  }
  Some(column + distance)
}