/**
 * Tracks newlines during parsing in order to provide an efficient API for
 * determining the one-indexed `{ line, col }` position for any offset
 * within the input.
 */
export class LineCounter {
  lineStarts: number[] = []

  /**
   * Should be called in ascending order. Otherwise, call
   * `lineCounter.lineStarts.sort()` before calling `linePos()`.
   */
  addNewLine = (offset: number) => this.lineStarts.push(offset)

  /**
   * Performs a binary search and returns the 1-indexed { line, col }
   * position of `offset`. If `line === 0`, `addNewLine` has never been
   * called or `offset` is before the first known newline.
   */
  linePos = (offset: number) => {
    let low = 0
    let high = this.lineStarts.length
    while (low < high) {
      const mid = (low + high) >> 1 // Math.floor((low + high) / 2)
      if (this.lineStarts[mid] < offset) low = mid + 1
      else high = mid
    }
    if (this.lineStarts[low] === offset) return { line: low + 1, col: 1 }
    if (low === 0) return { line: 0, col: offset }
    const start = this.lineStarts[low - 1]
    return { line: low, col: offset - start + 1 }
  }
}
