///|
pub(all) struct StreamScanner {
  scanner : Scanner
  overlap : Int
  mut pending : String
  mut absolute_offset : Int
  mut finished : Bool
}

///|
pub fn StreamScanner::new(
  scanner? : Scanner = Scanner::new(),
  overlap? : Int = 256,
) -> StreamScanner {
  {
    scanner,
    overlap: if overlap < 32 {
      32
    } else {
      overlap
    },
    pending: "",
    absolute_offset: 0,
    finished: false,
  }
}

///|
fn offset_finding(finding : Finding, offset : Int) -> Finding {
  Finding::new(
    finding.detector_id,
    finding.kind,
    finding.severity,
    finding.span.start + offset,
    finding.span.end + offset,
    finding.confidence,
    finding.preview,
    finding.message,
  )
}

///|
pub fn StreamScanner::push(
  self : StreamScanner,
  chunk : String,
) -> Array[Finding] raise {
  if self.finished {
    fail("stream already finished")
  }
  let combined = self.pending + chunk
  let chars = combined.to_array()
  let stable_end = if chars.length() > self.overlap {
    chars.length() - self.overlap
  } else {
    0
  }
  let emitted : Array[Finding] = []
  for finding in self.scanner.find(combined) {
    if finding.span.end <= stable_end {
      emitted.push(offset_finding(finding, self.absolute_offset))
    }
  }
  self.absolute_offset = self.absolute_offset + stable_end
  self.pending = slice_chars(chars, stable_end, chars.length())
  emitted
}

///|
pub fn StreamScanner::finish(self : StreamScanner) -> Array[Finding] raise {
  if self.finished {
    fail("stream already finished")
  }
  self.finished = true
  let out : Array[Finding] = []
  for finding in self.scanner.find(self.pending) {
    out.push(offset_finding(finding, self.absolute_offset))
  }
  self.pending = ""
  out
}

///|
pub fn StreamScanner::buffered_bytes(self : StreamScanner) -> Int {
  self.pending.to_array().length()
}