// lib/editor/search/search_snippet.dart
//
// Pure snippet extraction for library-wide full-text search (F8 — the user's #1
// named differentiator). Given a source string (a PDF text page, a typed text
// box, or OCR'd handwriting) and a query, produce a windowed excerpt centered on
// the first match with the match offset preserved, so the results list can show
// "…context **match** context…" and jump to the hit.
//
// The FTS index / ranking lives in the DB (search_indexer); THIS is the pure,
// storage-free excerpt math, fully unit-tested.

/// A windowed excerpt around a query match.
class Snippet {
  const Snippet({
    required this.text,
    required this.matchStart,
    required this.matchLength,
    required this.truncatedStart,
    required this.truncatedEnd,
  });

  /// The excerpt (a substring of the source).
  final String text;

  /// Offset of the match WITHIN [text].
  final int matchStart;

  /// Length of the matched run.
  final int matchLength;

  /// True when [text] begins before the source start was reached (show a
  /// leading ellipsis).
  final bool truncatedStart;

  /// True when [text] ends before the source end (show a trailing ellipsis).
  final bool truncatedEnd;

  /// Convenience: the matched substring.
  String get match => text.substring(matchStart, matchStart + matchLength);

  @override
  String toString() =>
      '${truncatedStart ? '…' : ''}$text${truncatedEnd ? '…' : ''}'
      ' [match @$matchStart+$matchLength]';
}

/// First case-insensitive match of [query] in [source], as a snippet of up to
/// roughly [window] characters centered on the match. Returns null when [query]
/// is empty or absent. The full match is always included even if longer than
/// [window].
Snippet? snippetFor(String source, String query, {int window = 80}) {
  if (query.isEmpty || source.isEmpty) return null;
  assert(window >= 0);

  final matchIndex = source.toLowerCase().indexOf(query.toLowerCase());
  if (matchIndex < 0) return null;

  final matchLen = query.length;
  final contextEach = ((window - matchLen) ~/ 2).clamp(0, window);

  var start = matchIndex - contextEach;
  if (start < 0) start = 0;
  var end = matchIndex + matchLen + contextEach;
  if (end > source.length) end = source.length;

  return Snippet(
    text: source.substring(start, end),
    matchStart: matchIndex - start,
    matchLength: matchLen,
    truncatedStart: start > 0,
    truncatedEnd: end < source.length,
  );
}
