Implementation
PrereqGapParseResult parsePrereqGaps(String text) {
final matches = _kPrereqGapPattern.allMatches(text).toList();
if (matches.isEmpty) {
return PrereqGapParseResult(cleanedText: text, concepts: const []);
}
// Order-preserving dedupe (case-insensitive comparison).
final seen = <String>{};
final concepts = <String>[];
for (final m in matches) {
final raw = m.group(1)?.trim();
if (raw == null || raw.isEmpty) continue;
final key = raw.toLowerCase();
if (seen.add(key)) concepts.add(raw);
}
// Strip every tag from the text. Then collapse runs of whitespace that
// the removal may have left behind (e.g. "foo [tag] bar" → "foo bar").
var cleaned = text.replaceAll(_kPrereqGapPattern, '');
cleaned = cleaned.replaceAll(RegExp(r'[ \t]{2,}'), ' ');
// Trim trailing spaces on each line; collapse 3+ blank lines to 2.
cleaned = cleaned
.split('\n')
.map((line) => line.replaceAll(RegExp(r'[ \t]+$'), ''))
.join('\n');
cleaned = cleaned.replaceAll(RegExp(r'\n{3,}'), '\n\n').trim();
return PrereqGapParseResult(cleanedText: cleaned, concepts: concepts);
}