import 'dart:convert'; import 'dart:math'; import 'package:flutter/services.dart'; import 'package:hive_flutter/hive_flutter.dart'; // Import Hive import '../services/bible_xml_parser.dart'; // Import the XML parser import 'cycle_entry.dart'; import 'user_profile.dart'; part 'scripture.g.dart'; // Hive generated adapter /// Scripture model for daily verses and devotionals @HiveType(typeId: 10) // Unique typeId for Scripture class Scripture extends HiveObject { @HiveField(0, defaultValue: {}) final Map verses; @HiveField(1) final String reference; @HiveField(2) final String? reflection; @HiveField(3, defaultValue: []) final List applicablePhases; @HiveField(4, defaultValue: []) final List applicableContexts; Scripture({ required this.verses, required this.reference, this.reflection, this.applicablePhases = const [], this.applicableContexts = const [], }); factory Scripture.fromJson(Map json) { return Scripture( verses: (json['verses'] as Map).map((key, value) => MapEntry( BibleTranslation.values.firstWhere((e) => e.name == key), value)), reference: json['reference'], reflection: json['reflection'], applicablePhases: (json['applicablePhases'] as List?) ?.map((e) => e as String) .toList() ?? [], applicableContexts: (json['applicableContexts'] as List?) ?.map((e) => e as String) .toList() ?? [], ); } String getVerse(BibleTranslation translation) { return verses[translation] ?? verses[BibleTranslation.esv] ?? verses.values.first; } @override bool operator ==(Object other) => identical(this, other) || (other is Scripture && runtimeType == other.runtimeType && reference == other.reference && reflection == other.reflection && _listEquals(applicablePhases, other.applicablePhases) && _listEquals(applicableContexts, other.applicableContexts) && _mapEquals(verses, other.verses)); @override int get hashCode => reference.hashCode ^ reflection.hashCode ^ Object.hashAll(applicablePhases) ^ Object.hashAll(applicableContexts) ^ Object.hashAll(verses.entries) ^ reflection.hashCode; // Helper for list equality check static bool _listEquals(List? a, List? b) { if (a == null) return b == null; if (b == null) return false; if (a.length != b.length) return false; for (int i = 0; i < a.length; i++) { if (a[i] != b[i]) return false; } return true; } // Helper for map equality check static bool _mapEquals(Map? a, Map? b) { if (a == null) return b == null; if (b == null) return false; if (a.length != b.length) return false; if (a.keys.length != b.keys.length) return false; // Added length check for (final key in a.keys) { if (!b.containsKey(key) || a[key] != b[key]) return false; // Added containsKey check } return true; } } /// Pre-defined scriptures for the app class ScriptureDatabase { static final ScriptureDatabase _instance = ScriptureDatabase._internal(); factory ScriptureDatabase({BibleXmlParser? bibleXmlParser}) { _instance._bibleXmlParser = bibleXmlParser ?? BibleXmlParser(); return _instance; } ScriptureDatabase._internal(); late BibleXmlParser _bibleXmlParser; late Box _scriptureBox; // Mapping of BibleTranslation to its XML asset path final Map _translationFileMapping = { BibleTranslation.esv: 'bible_xml/ESV.xml', BibleTranslation.niv: 'bible_xml/NIV.xml', BibleTranslation.nkjv: 'bible_xml/NKJV.xml', BibleTranslation.nlt: 'bible_xml/NLT.xml', BibleTranslation.nasb: 'bible_xml/NASB.xml', BibleTranslation.kjv: 'bible_xml/KJV.xml', BibleTranslation.msg: 'bible_xml/MSG.xml', }; List _menstrualScriptures = []; List _follicularScriptures = []; List _ovulationScriptures = []; List _lutealScriptures = []; List _husbandScriptures = []; List _womanhoodScriptures = []; Map> _contextualScriptures = {}; // Hardcoded scriptures to ensure rich Husband experience immediately final List _hardcodedHusbandScriptures = [ Scripture( reference: "Mark 10:45", verses: { BibleTranslation.esv: "For even the Son of Man came not to be served but to serve, and to give his life as a ransom for many.", BibleTranslation.niv: "For even the Son of Man did not come to be served, but to serve, and to give his life as a ransom for many.", }, reflection: "True leadership is servanthood. How can you serve your wife today?", applicablePhases: ['husband'], applicableContexts: ['leadership', 'servant'], ), Scripture( reference: "Philippians 2:3-4", verses: { BibleTranslation.esv: "Do nothing from selfish ambition or conceit, but in humility count others more significant than yourselves. Let each of you look not only to his own interests, but also to the interests of others.", }, reflection: "Humility is the foundation of a happy marriage.", applicablePhases: ['husband'], applicableContexts: ['servant', 'humility'], ), Scripture( reference: "Proverbs 29:18", verses: { BibleTranslation.esv: "Where there is no prophetic vision the people cast off restraint, but blessed is he who keeps the law.", BibleTranslation.kjv: "Where there is no vision, the people perish: but he that keepeth the law, happy is he.", }, reflection: "Lead your family with a clear, Godly vision.", applicablePhases: ['husband'], applicableContexts: ['vision', 'leadership'], ), Scripture( reference: "James 1:5", verses: { BibleTranslation.esv: "If any of you lacks wisdom, let him ask God, who gives generously to all without reproach, and it will be given him.", }, reflection: "Seek God's wisdom in every decision you make for your family.", applicablePhases: ['husband'], applicableContexts: ['wisdom', 'vision'], ), Scripture( reference: "1 Timothy 3:4-5", verses: { BibleTranslation.esv: "He must manage his own household well, with all dignity keeping his children submissive, for if someone does not know how to manage his own household, how will he care for God's church?", }, reflection: "Your first ministry is your home. Manage it with love and dignity.", applicablePhases: ['husband'], applicableContexts: ['leadership'], ), Scripture( reference: "Colossians 3:19", verses: { BibleTranslation.esv: "Husbands, love your wives, and do not be harsh with them.", BibleTranslation.niv: "Husbands, love your wives and do not be harsh with them.", }, reflection: "Gentleness is a sign of strength, not weakness.", applicablePhases: ['husband'], applicableContexts: ['kindness', 'love'], ), Scripture( reference: "1 Corinthians 16:14", verses: { BibleTranslation.esv: "Let all that you do be done in love.", }, reflection: "Let love be the motivation behind every action and word.", applicablePhases: ['husband'], applicableContexts: ['love'], ), ]; Future loadScriptures() async { _scriptureBox = await Hive.openBox('scriptures'); if (_scriptureBox.isEmpty) { print('Hive box is empty. Importing scriptures from optimized JSON data...'); // Load the pre-processed JSON file which already contains all verse text final String response = await rootBundle.loadString('assets/scriptures_optimized.json'); final Map data = json.decode(response); List importedScriptures = []; // Helper function to process ANY list of scriptures void processList(List list, String listName) { for (var jsonEntry in list) { final reference = jsonEntry['reference']; final reflection = jsonEntry['reflection']; // Optional final applicablePhases = (jsonEntry['applicablePhases'] as List?) ?.map((e) => e as String) .toList() ?? []; final applicableContexts = (jsonEntry['applicableContexts'] as List?) ?.map((e) => e as String) .toList() ?? []; // Map string keys (esv, niv) to BibleTranslation enum Map versesMap = {}; if (jsonEntry['verses'] != null) { (jsonEntry['verses'] as Map).forEach((key, value) { // Find enum by name (case-insensitive usually, but here keys are lowercase 'esv') try { final translation = BibleTranslation.values.firstWhere( (e) => e.name.toLowerCase() == key.toLowerCase() ); versesMap[translation] = value.toString(); } catch (e) { print('Warning: Unknown translation key "$key" in optimized JSON'); } }); } if (versesMap.isNotEmpty) { importedScriptures.add(Scripture( verses: versesMap, reference: reference, reflection: reflection, applicablePhases: applicablePhases, applicableContexts: applicableContexts, )); } } } // Process all sections if (data['menstrual'] != null) processList(data['menstrual'], 'menstrual'); if (data['follicular'] != null) processList(data['follicular'], 'follicular'); if (data['ovulation'] != null) processList(data['ovulation'], 'ovulation'); if (data['luteal'] != null) processList(data['luteal'], 'luteal'); if (data['husband'] != null) processList(data['husband'], 'husband'); if (data['womanhood'] != null) processList(data['womanhood'], 'womanhood'); if (data['contextual'] != null) { final contextualMap = data['contextual'] as Map; contextualMap.forEach((key, value) { processList(value as List, 'contextual_$key'); }); } // Store all imported scriptures into Hive for (var scripture in importedScriptures) { await _scriptureBox.put(scripture.reference, scripture); // Using reference as key } } else { print('Hive box is not empty. Loading scriptures from Hive...'); } // Populate internal lists from Hive box values _menstrualScriptures = _scriptureBox.values .where((s) => s.applicablePhases.contains('menstrual')) .toList(); _follicularScriptures = _scriptureBox.values .where((s) => s.applicablePhases.contains('follicular')) .toList(); _ovulationScriptures = _scriptureBox.values .where((s) => s.applicablePhases.contains('ovulation')) .toList(); _lutealScriptures = _scriptureBox.values .where((s) => s.applicablePhases.contains('luteal')) .toList(); _husbandScriptures = [ ..._scriptureBox.values.where((s) => s.applicablePhases.contains('husband')), ..._hardcodedHusbandScriptures, ]; // Remove duplicates based on reference if any final uniqueHusbandIds = {}; _husbandScriptures = _husbandScriptures.where((s) { if (uniqueHusbandIds.contains(s.reference)) return false; uniqueHusbandIds.add(s.reference); return true; }).toList(); _womanhoodScriptures = _scriptureBox.values .where((s) => s.applicableContexts.contains('womanhood')) .toList(); _contextualScriptures = { 'anxiety': _scriptureBox.values.where((s) => s.applicableContexts.contains('anxiety')).toList(), 'pain': _scriptureBox.values.where((s) => s.applicableContexts.contains('pain')).toList(), 'fatigue': _scriptureBox.values.where((s) => s.applicableContexts.contains('fatigue')).toList(), 'joy': _scriptureBox.values.where((s) => s.applicableContexts.contains('joy')).toList(), }; } /// Get the number of scriptures for a given phase int getScriptureCountForPhase(String phase) { switch (phase.toLowerCase()) { case 'menstrual': return _menstrualScriptures.length; case 'follicular': return _follicularScriptures.length; case 'ovulation': return _ovulationScriptures.length; case 'luteal': return _lutealScriptures.length; case 'husband': return _husbandScriptures.length; case 'womanhood': return _womanhoodScriptures.length; case 'anxiety': return _contextualScriptures['anxiety']?.length ?? 0; case 'pain': return _contextualScriptures['pain']?.length ?? 0; case 'fatigue': return _contextualScriptures['fatigue']?.length ?? 0; case 'joy': return _contextualScriptures['joy']?.length ?? 0; default: return 0; } } /// Get recommended scripture based on entry Scripture? getRecommendedScripture(CycleEntry entry) { if (entry.mood == MoodLevel.verySad || entry.mood == MoodLevel.sad || (entry.stressLevel != null && entry.stressLevel! > 3)) { final scriptures = _contextualScriptures['anxiety']; if (scriptures != null && scriptures.isNotEmpty) { return scriptures[DateTime.now().day % scriptures.length]; } } if ((entry.crampIntensity != null && entry.crampIntensity! >= 3) || entry.hasHeadache || entry.hasLowerBackPain) { final scriptures = _contextualScriptures['pain']; if (scriptures != null && scriptures.isNotEmpty) { return scriptures[DateTime.now().day % scriptures.length]; } } if (entry.hasFatigue || entry.hasInsomnia || (entry.energyLevel != null && entry.energyLevel! <= 2)) { final scriptures = _contextualScriptures['fatigue']; if (scriptures != null && scriptures.isNotEmpty) { return scriptures[DateTime.now().day % scriptures.length]; } } if (entry.mood == MoodLevel.veryHappy) { final scriptures = _contextualScriptures['joy']; if (scriptures != null && scriptures.isNotEmpty) { return scriptures[DateTime.now().day % scriptures.length]; } } return null; } /// Get scripture for current phase by index Scripture? getScriptureForPhaseByIndex(String phase, int index) { List scriptures; switch (phase.toLowerCase()) { case 'menstrual': scriptures = _menstrualScriptures; break; case 'follicular': scriptures = _follicularScriptures; break; case 'ovulation': scriptures = _ovulationScriptures; break; case 'luteal': scriptures = _lutealScriptures; break; case 'husband': scriptures = _husbandScriptures; break; case 'womanhood': scriptures = _womanhoodScriptures; break; case 'anxiety': scriptures = _contextualScriptures['anxiety'] ?? []; break; case 'pain': scriptures = _contextualScriptures['pain'] ?? []; break; case 'fatigue': scriptures = _contextualScriptures['fatigue'] ?? []; break; case 'joy': scriptures = _contextualScriptures['joy'] ?? []; break; default: return null; } if (scriptures.isEmpty || index < 0 || index >= scriptures.length) { return null; } return scriptures[index]; } // ... imports // ... inside ScriptureDatabase class /// Get a random scripture for a given phase Scripture? getRandomScriptureForPhase(String phase) { List scriptures; switch (phase.toLowerCase()) { case 'menstrual': scriptures = _menstrualScriptures; break; case 'follicular': scriptures = _follicularScriptures; break; case 'ovulation': scriptures = _ovulationScriptures; break; case 'luteal': scriptures = _lutealScriptures; break; case 'husband': scriptures = _husbandScriptures; break; case 'womanhood': scriptures = _womanhoodScriptures; break; case 'anxiety': scriptures = _contextualScriptures['anxiety'] ?? []; break; case 'pain': scriptures = _contextualScriptures['pain'] ?? []; break; case 'fatigue': scriptures = _contextualScriptures['fatigue'] ?? []; break; case 'joy': scriptures = _contextualScriptures['joy'] ?? []; break; default: return null; } if (scriptures.isEmpty) { return null; } return scriptures[Random().nextInt(scriptures.length)]; } /// Get scripture for current phase (Randomized) Scripture getScriptureForPhase(String phase) { List scriptures; switch (phase.toLowerCase()) { // ... (same switch cases) case 'menstrual': scriptures = _menstrualScriptures; break; case 'follicular': scriptures = _follicularScriptures; break; case 'ovulation': scriptures = _ovulationScriptures; break; case 'luteal': scriptures = _lutealScriptures; break; case 'husband': scriptures = _husbandScriptures; break; case 'womanhood': scriptures = _womanhoodScriptures; break; case 'anxiety': scriptures = _contextualScriptures['anxiety'] ?? []; break; case 'pain': scriptures = _contextualScriptures['pain'] ?? []; break; case 'fatigue': scriptures = _contextualScriptures['fatigue'] ?? []; break; case 'joy': scriptures = _contextualScriptures['joy'] ?? []; break; default: // Fallback scriptures = [ ..._menstrualScriptures, ..._follicularScriptures, ..._ovulationScriptures, ..._lutealScriptures, ..._husbandScriptures, ..._womanhoodScriptures, ...(_contextualScriptures['anxiety'] ?? []), ...(_contextualScriptures['pain'] ?? []), ...(_contextualScriptures['fatigue'] ?? []), ...(_contextualScriptures['joy'] ?? []), ]; if (scriptures.isEmpty) return Scripture(verses: {BibleTranslation.esv: "No scripture found."}, reference: "Unknown", applicablePhases: [], applicableContexts: []); } if (scriptures.isEmpty) return Scripture(verses: {BibleTranslation.esv: "No scripture found."}, reference: "Unknown", applicablePhases: [], applicableContexts: []); return scriptures[Random().nextInt(scriptures.length)]; } /// Get scripture for husband (Randomized) Scripture getHusbandScripture() { final scriptures = _husbandScriptures; if (scriptures.isEmpty) { return Scripture(verses: {BibleTranslation.esv: "No husband scripture found."}, reference: "Unknown", applicablePhases: [], applicableContexts: []); } return scriptures[Random().nextInt(scriptures.length)]; } /// Get all scriptures List getAllScriptures() { return _scriptureBox.values.toList(); } }