///|
// Strategy: grep-summary
fn Matcher::may_match_block(
  self : Matcher,
  data : BytesView,
  ignore_case : Bool,
) -> Bool {
  match self {
    ManyFixed(set) => ignore_case || set.matches(data)
    Fixed(patterns, _, _) =>
      ignore_case ||
      patterns.iter().any(pattern => data.find(pattern) is Some(_))
    Regular(patterns, _, _) =>
      patterns
      .iter()
      .any(pattern => {
        let required = pattern.required_literal()
        required.is_empty() || data.find(required) is Some(_)
      })
  }
}

///|
/// Only skip complete records already inspected for binary input by the refill
/// path. Leave the final fragment for cross-block assembly and error handling.
fn GrepInput::skip_nonmatching(
  self : GrepInput,
  matcher : Matcher,
  ignore_case : Bool,
) -> Unit {
  guard !self.prefiltered else { return }
  self.prefiltered = true
  let remaining = self.bytes[self.position:]
  guard remaining.rev_find(self.delimiter) is Some(last) else { return }
  let complete = remaining[:last + 1]
  if !matcher.may_match_block(complete, ignore_case) {
    self.consume_records(
      self.position + complete.length(),
      @stream.count_byte(complete, self.delimiter[0]).to_int64(),
    )
  }
}

///|
/// Counting needs neither Record objects nor an async continuation per record.
/// Retain incomplete tails for the same refill/error path as emitted records.
fn GrepInput::count_buffered(
  self : GrepInput,
  matcher : Matcher,
  ignore_case : Bool,
  invert : Bool,
) -> UInt64 {
  if matcher is Regular([pattern], false, false) {
    let remaining = self.bytes[self.position:]
    if remaining.rev_find(self.delimiter) is Some(last) {
      let complete = remaining[:last + 1]
      if pattern.count_records(complete, self.delimiter[0])
        is Some((matched, records)) {
        self.consume_records(
          self.position + complete.length(),
          records.to_int64(),
        )
        return (if invert { records - matched } else { matched }).to_uint64()
      }
    }
  }
  if !ignore_case &&
    matcher is ManyFixed(set) &&
    !set.contains_byte(self.delimiter[0]) {
    return self.count_literals(set, invert)
  }
  if !ignore_case &&
    matcher is Fixed([literal], false, false) &&
    literal.find(self.delimiter) is None {
    return self.count_literal(literal, invert)
  }
  let mut position = self.position
  let mut records = 0L
  let mut count = 0UL
  while self.bytes[position:].find(self.delimiter) is Some(relative) {
    let end = position + relative
    let matched = matcher.matches_record(self.bytes[position:end], ignore_case)
    if matched != invert {
      count += 1UL
    }
    position = end + 1
    records += 1L
  }
  self.consume_records(position, records)
  count
}

///|
/// A fixed hit identifies a record directly. Skip the rest of that record so
/// repeated hits count once; a literal containing the delimiter uses the normal
/// record path instead. The incomplete tail stays with the refill assembler.
fn GrepInput::count_literal(
  self : GrepInput,
  literal : BytesView,
  invert : Bool,
) -> UInt64 {
  let remaining = self.bytes[self.position:]
  guard remaining.rev_find(self.delimiter) is Some(last) else { return 0UL }
  let complete = remaining[:last + 1]
  let records = @stream.count_byte(complete, self.delimiter[0])
  let mut matched = 0
  if literal.is_empty() {
    matched = records
  } else {
    let mut position = 0
    while position < complete.length() {
      guard complete[position:].find(literal) is Some(relative) else { break }
      let after = position + relative + literal.length()
      let end = after + complete[after:].find(self.delimiter).unwrap()
      matched += 1
      position = end + 1
    }
  }
  self.consume_records(self.position + complete.length(), records.to_int64())
  (if invert { records - matched } else { matched }).to_uint64()
}

///|
fn GrepInput::count_literals(
  self : GrepInput,
  set : @bytere.LiteralSet,
  invert : Bool,
) -> UInt64 {
  let remaining = self.bytes[self.position:]
  guard remaining.rev_find(self.delimiter) is Some(last) else { return 0UL }
  let complete = remaining[:last + 1]
  let records = @stream.count_byte(complete, self.delimiter[0])
  let mut matched = 0
  let mut position = 0
  while position < complete.length() {
    guard set.find_any(complete, start=position) is Some((_, end)) else {
      break
    }
    position = end + complete[end:].find(self.delimiter).unwrap() + 1
    matched += 1
  }
  self.consume_records(self.position + complete.length(), records.to_int64())
  (if invert { records - matched } else { matched }).to_uint64()
}