class DictationTextAccumulator {
  String _baseText = '';
  String _recognizedText = '';

  String get text =>
      [_baseText, _recognizedText].where((part) => part.isNotEmpty).join(' ');

  void begin(String existingText) {
    _baseText = existingText.trimRight();
    _recognizedText = '';
  }

  String addResult(String words) {
    final nextText = words.trim().replaceAll(RegExp(r'\s+'), ' ');
    if (nextText.isEmpty) return text;

    _recognizedText = _mergeWithoutDeleting(_recognizedText, nextText);
    return text;
  }

  String _mergeWithoutDeleting(String previousText, String nextText) {
    if (previousText.isEmpty) return nextText;

    final previousWords = _words(previousText);
    final nextWords = _words(nextText);

    if (_startsWith(nextWords, previousWords)) return nextText;
    if (_startsWith(previousWords, nextWords)) return previousText;

    final commonPrefixLength = _commonPrefixLength(previousWords, nextWords);
    if (commonPrefixLength > 0) {
      if (nextWords.length > previousWords.length) return nextText;
      if (nextWords.length < previousWords.length) return previousText;
      return nextText.length >= previousText.length ? nextText : previousText;
    }

    final overlapLength = _suffixPrefixOverlap(previousWords, nextWords);
    if (overlapLength > 0) {
      final originalNextWords = nextText.split(' ');
      final remainingWords = originalNextWords.skip(overlapLength).join(' ');
      return remainingWords.isEmpty
          ? previousText
          : '$previousText $remainingWords';
    }

    return '$previousText $nextText';
  }

  List<String> _words(String value) {
    return value
        .toLowerCase()
        .replaceAll(RegExp(r'[.,;:!?¿¡]'), '')
        .split(RegExp(r'\s+'))
        .where((word) => word.isNotEmpty)
        .toList();
  }

  bool _startsWith(List<String> words, List<String> prefix) {
    if (prefix.length > words.length) return false;
    for (var index = 0; index < prefix.length; index++) {
      if (words[index] != prefix[index]) return false;
    }
    return true;
  }

  int _commonPrefixLength(List<String> first, List<String> second) {
    final limit = first.length < second.length ? first.length : second.length;
    var commonWords = 0;
    while (commonWords < limit && first[commonWords] == second[commonWords]) {
      commonWords++;
    }
    return commonWords;
  }

  int _suffixPrefixOverlap(List<String> previous, List<String> next) {
    final limit = previous.length < next.length ? previous.length : next.length;
    for (var overlap = limit; overlap > 0; overlap--) {
      var matches = true;
      for (var index = 0; index < overlap; index++) {
        if (previous[previous.length - overlap + index] != next[index]) {
          matches = false;
          break;
        }
      }
      if (matches) return overlap;
    }
    return 0;
  }
}
