import 'dart:convert'; import 'dart:math'; import 'package:shared_preferences/shared_preferences.dart'; import '../data/words_data.dart'; import '../models/history_entry_model.dart'; import '../models/word_model.dart'; class WordsRepository { static final WordsRepository _instance = WordsRepository._internal(); factory WordsRepository() => _instance; WordsRepository._internal(); final List _masterWords = List.from(kRussianTop1000Words); List _sessionWords = []; final List _history = []; bool _isInitialized = false; List get allWords => List.unmodifiable(_sessionWords); int get totalWords => _sessionWords.length; List get history => List.unmodifiable(_history); Future init() async { if (_isInitialized) return; // Always reshuffle session numbers on startup for true synchronicity & randomization reshuffleSessionNumbers(); // Load history from persistence try { final prefs = await SharedPreferences.getInstance(); final historyJsonList = prefs.getStringList('oracle_history_entries') ?? []; _history.clear(); for (final str in historyJsonList) { try { final map = jsonDecode(str) as Map; _history.add(HistoryEntry.fromMap(map)); } catch (_) {} } } catch (_) {} _isInitialized = true; } /// Cryptographic/pseudo-random Fisher-Yates shuffle to map 000..999 to words for the current session void reshuffleSessionNumbers() { final random = Random(); final indices = List.generate(_masterWords.length, (i) => i); indices.shuffle(random); final mappedList = []; for (int i = 0; i < _masterWords.length; i++) { final baseWord = _masterWords[indices[i]]; // i is the dynamic sessionNumber (0..999) final sessionItem = baseWord.copyWith(sessionNumber: i); mappedList.add(sessionItem); } _sessionWords = mappedList; } WordItem? getWordBySessionNumber(int sessionNum) { if (sessionNum < 0 || sessionNum >= _sessionWords.length) return null; return _sessionWords[sessionNum]; } WordItem? getWordById(int id) { return getWordBySessionNumber(id); } WordItem getRandomWord() { if (_sessionWords.isEmpty) { return _masterWords.first; } final random = Random(); final index = random.nextInt(_sessionWords.length); return _sessionWords[index]; } WordItem getNextWord(int currentSessionNum) { if (_sessionWords.isEmpty) return getRandomWord(); final nextId = (currentSessionNum + 1) % _sessionWords.length; return _sessionWords[nextId]; } WordItem getPreviousWord(int currentSessionNum) { if (_sessionWords.isEmpty) return getRandomWord(); final prevId = (currentSessionNum - 1 + _sessionWords.length) % _sessionWords.length; return _sessionWords[prevId]; } List search(String rawQuery) { final query = rawQuery.trim().toLowerCase(); if (query.isEmpty) return _sessionWords; // Check if query is a number (e.g. 0, 00, 000, 42, 042, 999) final number = int.tryParse(query); if (number != null && number >= 0 && number < _sessionWords.length) { final exact = _sessionWords[number]; final matches = _sessionWords.where((w) { if (w.sessionNumber == number) return false; final idStr = w.sessionNumber.toString(); final paddedStr = w.formattedSessionNumber; return idStr.startsWith(query) || paddedStr.startsWith(query); }).take(30).toList(); return [exact, ...matches]; } // Partial numeric match if (RegExp(r'^\d+$').hasMatch(query)) { return _sessionWords.where((w) { final idStr = w.sessionNumber.toString(); final paddedStr = w.formattedSessionNumber; return idStr.startsWith(query) || paddedStr.startsWith(query); }).take(30).toList(); } // Text search in word, description, category, and part of speech final exact = []; final startsWith = []; final contains = []; for (final w in _sessionWords) { final lower = w.word.toLowerCase(); if (lower == query) { exact.add(w); } else if (lower.startsWith(query)) { startsWith.add(w); } else if (lower.contains(query) || w.description.toLowerCase().contains(query) || w.category.toLowerCase().contains(query) || w.partOfSpeech.toLowerCase().contains(query)) { contains.add(w); } } return [...exact, ...startsWith, ...contains]; } List getCategories() { final set = {'Все'}; for (final w in _sessionWords) { if (w.category.isNotEmpty) { set.add(w.category); } } return set.toList(); } List getPartsOfSpeech() { final set = {'Все'}; for (final w in _sessionWords) { if (w.partOfSpeech.isNotEmpty) { set.add(w.partOfSpeech); } } return set.toList(); } // --- History Management --- Future addHistoryEntry(HistoryEntry entry) async { _history.insert(0, entry); if (_history.length > 500) { _history.removeRange(500, _history.length); } await _saveHistoryToPrefs(); } Future deleteHistoryEntry(String id) async { _history.removeWhere((e) => e.id == id); await _saveHistoryToPrefs(); } Future clearHistory() async { _history.clear(); await _saveHistoryToPrefs(); } Future _saveHistoryToPrefs() async { try { final prefs = await SharedPreferences.getInstance(); final list = _history.map((e) => jsonEncode(e.toMap())).toList(); await prefs.setStringList('oracle_history_entries', list); } catch (_) {} } }