import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/history_entry_model.dart'; import '../models/word_model.dart'; import '../services/ai_oracle_service.dart'; import '../services/words_repository.dart'; import '../widgets/d10_dice_roller_widget.dart'; import '../widgets/gradient_star_badge.dart'; class SessionResultScreen extends StatefulWidget { final OracleSession session; const SessionResultScreen({ super.key, required this.session, }); @override State createState() => _SessionResultScreenState(); } class _SessionResultScreenState extends State { final _aiService = AiOracleService(); final _repo = WordsRepository(); final _chatInputController = TextEditingController(); final _scrollController = ScrollController(); late OracleSession _session; bool _isGeneratingInitialAi = false; bool _isChatLoading = false; @override void initState() { super.initState(); _session = widget.session; _repo.saveSession(_session); // If initial interpretation is not yet done, trigger it if (_session.aiInterpretation == null) { _fetchInitialAi(); } } @override void dispose() { _chatInputController.dispose(); _scrollController.dispose(); super.dispose(); } Future _fetchInitialAi() async { setState(() => _isGeneratingInitialAi = true); try { final result = await _aiService.interpretSession( words: _session.words, question: _session.question ?? '', ); if (mounted) { final initialMessage = SessionChatMessage( id: 'msg_0', sender: 'oracle', text: result, timestamp: DateTime.now(), ); final updated = _session.copyWith( aiInterpretation: result, messages: _session.messages.isEmpty ? [initialMessage] : _session.messages, ); setState(() { _session = updated; _isGeneratingInitialAi = false; }); await _repo.saveSession(updated); _scrollToBottom(); } } catch (_) { if (mounted) { setState(() => _isGeneratingInitialAi = false); } } } Future _sendMessage({SessionWord? newlyAddedWord}) async { final text = _chatInputController.text.trim(); if (text.isEmpty && newlyAddedWord == null) return; final userMessageText = text.isNotEmpty ? text : 'Раскрой значение добавленного знака #${newlyAddedWord?.formattedSessionNumber} «${newlyAddedWord?.word.toUpperCase()}».'; _chatInputController.clear(); final userMsg = SessionChatMessage( id: 'msg_${DateTime.now().millisecondsSinceEpoch}', sender: 'user', text: userMessageText, timestamp: DateTime.now(), attachedWord: newlyAddedWord, ); final updatedWithUser = _session.copyWith( messages: [..._session.messages, userMsg], ); setState(() { _session = updatedWithUser; _isChatLoading = true; }); _scrollToBottom(); try { final reply = await _aiService.chatWithOracle( session: updatedWithUser, userMessage: userMessageText, newlyAddedWord: newlyAddedWord, ); if (mounted) { final oracleMsg = SessionChatMessage( id: 'msg_${DateTime.now().millisecondsSinceEpoch + 1}', sender: 'oracle', text: reply, timestamp: DateTime.now(), ); final finalSession = updatedWithUser.copyWith( messages: [...updatedWithUser.messages, oracleMsg], ); setState(() { _session = finalSession; _isChatLoading = false; }); await _repo.saveSession(finalSession); _scrollToBottom(); } } catch (e) { if (mounted) { setState(() => _isChatLoading = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ошибка отправки: $e')), ); } } } void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent + 100, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); } void _showAddWordModal() { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) => _AddWordBottomSheet( onWordChosen: (WordItem word) async { Navigator.pop(ctx); final sessionWord = SessionWord.fromWordItem( word, positionTitle: 'Уточняющий знак #${_session.words.length + 1}', ); final updated = _session.copyWith( words: [..._session.words, sessionWord], wordsCount: _session.words.length + 1, ); setState(() { _session = updated; }); await _repo.saveSession(updated); // Trigger chat refinement with this new word await _sendMessage(newlyAddedWord: sessionWord); }, ), ); } void _copySessionToClipboard() { final buf = StringBuffer(); buf.writeln('✨ Оракул 999 — Сессия Клеромантии ✨'); buf.writeln('Дата: ${_session.formattedDate}'); if (_session.question != null && _session.question!.isNotEmpty) { buf.writeln('Вопрос: «${_session.question}»'); } buf.writeln('Знаков в цепочке: ${_session.words.length}\n'); for (int i = 0; i < _session.words.length; i++) { final w = _session.words[i]; final pos = w.positionTitle != null ? ' (${w.positionTitle})' : ''; buf.writeln('🔮 Знак #${w.formattedSessionNumber}: ${w.word.toUpperCase()}$pos'); buf.writeln(' ${w.symbolicMeaning}\n'); } if (_session.aiInterpretation != null) { buf.writeln('🤖 Толкование Оракула (G4F):'); buf.writeln(_session.aiInterpretation); } Clipboard.setData(ClipboardData(text: buf.toString())); HapticFeedback.lightImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Row( children: [ GradientStar(size: 18), SizedBox(width: 10), Expanded(child: Text('Сессия скопирована в буфер обмена')), ], ), duration: const Duration(seconds: 2), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; return Scaffold( appBar: AppBar( title: Row( children: [ const GradientStar(size: 22), const SizedBox(width: 8), Text( 'Сессия (${_session.words.length} ${_session.words.length == 1 ? "знак" : (_session.words.length < 5 ? "знака" : "знаков")})', style: const TextStyle(fontWeight: FontWeight.w900), ), ], ), actions: [ IconButton( tooltip: 'Докинуть уточняющий знак', icon: const Icon(Icons.add_circle_outline_rounded, color: Color(0xFFFFD54F)), onPressed: _showAddWordModal, ), IconButton( tooltip: 'Скопировать сессию', icon: const Icon(Icons.copy_rounded), onPressed: _copySessionToClipboard, ), ], ), body: SafeArea( child: Column( children: [ // Top Collapsible / Horizontal Words Strip Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: isDark ? const Color(0xFF141728) : theme.colorScheme.surfaceContainerLow, border: const Border( bottom: BorderSide(color: Colors.white12, width: 1), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Question line if present if (_session.question != null && _session.question!.isNotEmpty) ...[ Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( children: [ const Icon(Icons.help_outline_rounded, size: 14, color: Color(0xFFFFD54F)), const SizedBox(width: 6), Expanded( child: Text( 'Вопрос: «${_session.question}»', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFFFFF9C4), fontStyle: FontStyle.italic, ), ), ), ], ), ), ], // Words Horizontal Badges List + Add Button SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ ..._session.words.asMap().entries.map((e) { final idx = e.key; final w = e.value; return Padding( padding: const EdgeInsets.only(right: 6), child: InkWell( onTap: () => _showWordDetails(w), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: const Color(0xFF232742), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFFFFD54F).withValues(alpha: 0.4), ), ), child: Row( children: [ Text( '${idx + 1}. ', style: TextStyle( fontSize: 11, color: theme.colorScheme.outline, fontWeight: FontWeight.bold, ), ), Text( '#${w.formattedSessionNumber} ${w.word.toUpperCase()}', style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w900, color: Color(0xFFFFD54F), ), ), ], ), ), ), ); }), // Button: Add word OutlinedButton.icon( onPressed: _showAddWordModal, icon: const Icon(Icons.casino_rounded, size: 14, color: Color(0xFFFFD54F)), label: const Text( '+ Докинуть знак', style: TextStyle( fontSize: 11.5, fontWeight: FontWeight.bold, color: Color(0xFFFFD54F), ), ), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), side: const BorderSide(color: Color(0xFFFFD54F)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ], ), ), ], ), ), // Live Chat Area Expanded( child: _session.messages.isEmpty && _isGeneratingInitialAi ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Color(0xFFFFD54F)), ), SizedBox(height: 16), Text( 'Оракул синтезирует символы сессии...', style: TextStyle(fontStyle: FontStyle.italic), ), ], ), ) : ListView.builder( controller: _scrollController, padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), itemCount: _session.messages.length + (_isChatLoading ? 1 : 0), itemBuilder: (context, idx) { if (idx == _session.messages.length) { return _buildTypingIndicator(); } final msg = _session.messages[idx]; final isUser = msg.sender == 'user'; return _buildChatBubble(msg, isUser, isDark, theme); }, ), ), // Bottom Chat Input Bar Container( padding: const EdgeInsets.fromLTRB(10, 8, 10, 10), decoration: BoxDecoration( color: isDark ? const Color(0xFF141728) : theme.colorScheme.surfaceContainerHigh, border: const Border( top: BorderSide(color: Colors.white12, width: 1), ), ), child: Row( children: [ IconButton( tooltip: 'Докинуть уточняющий знак d10', icon: const Icon(Icons.casino_rounded, color: Color(0xFFFFD54F), size: 24), onPressed: _showAddWordModal, ), const SizedBox(width: 4), Expanded( child: TextField( controller: _chatInputController, textInputAction: TextInputAction.send, onSubmitted: (_) => _sendMessage(), decoration: InputDecoration( hintText: 'Задайте уточняющий вопрос оракулу...', hintStyle: const TextStyle(fontSize: 13), filled: true, fillColor: isDark ? const Color(0xFF1F223B) : theme.colorScheme.surface, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), borderSide: BorderSide.none, ), ), ), ), const SizedBox(width: 6), IconButton.filled( onPressed: _isChatLoading ? null : () => _sendMessage(), icon: const Icon(Icons.arrow_upward_rounded, size: 20), style: IconButton.styleFrom( backgroundColor: const Color(0xFF2A2D4A), foregroundColor: const Color(0xFFFFD54F), side: const BorderSide(color: Color(0xFFFFD54F), width: 1.2), ), ), ], ), ), ], ), ), ); } Widget _buildChatBubble( SessionChatMessage msg, bool isUser, bool isDark, ThemeData theme) { return Align( alignment: isUser ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(bottom: 12), constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.85, ), padding: const EdgeInsets.all(14), decoration: BoxDecoration( gradient: isUser ? const LinearGradient( colors: [Color(0xFF2A2D4A), Color(0xFF1E2138)], ) : const LinearGradient( colors: [Color(0xFF2B1F4A), Color(0xFF1A1B35)], ), borderRadius: BorderRadius.only( topLeft: const Radius.circular(18), topRight: const Radius.circular(18), bottomLeft: Radius.circular(isUser ? 18 : 4), bottomRight: Radius.circular(isUser ? 4 : 18), ), border: Border.all( color: isUser ? const Color(0xFFFFD54F).withValues(alpha: 0.3) : const Color(0xFFBA68C8).withValues(alpha: 0.45), width: 1.2, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: Sender icon & name Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isUser ? Icons.person_outline_rounded : Icons.psychology_rounded, size: 14, color: isUser ? const Color(0xFFFFD54F) : const Color(0xFFE1BEE7), ), const SizedBox(width: 5), Text( isUser ? 'Вы' : 'Оракул 999', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w900, color: isUser ? const Color(0xFFFFD54F) : const Color(0xFFE1BEE7), ), ), ], ), // Attached new word pill if present if (msg.attachedWord != null) ...[ const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: const Color(0xFF3B2B5E), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.4)), ), child: Text( '🎲 Добавлен знак: #${msg.attachedWord!.formattedSessionNumber} «${msg.attachedWord!.word.toUpperCase()}»', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: Color(0xFFFFF9C4), ), ), ), ], const SizedBox(height: 6), // Message text Text( msg.text, style: TextStyle( fontSize: 13.5, height: 1.45, color: isUser ? Colors.white : const Color(0xFFF3E5F5), ), ), ], ), ), ); } Widget _buildTypingIndicator() { return Align( alignment: Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: const Color(0xFF241B3B), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFBA68C8).withValues(alpha: 0.3)), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 1.8, valueColor: AlwaysStoppedAnimation(Color(0xFFE1BEE7)), ), ), SizedBox(width: 8), Text( 'Оракул внимает и толкует знаки...', style: TextStyle(fontSize: 12, color: Color(0xFFE1BEE7), fontStyle: FontStyle.italic), ), ], ), ), ); } void _showWordDetails(SessionWord word) { showDialog( context: context, builder: (ctx) => AlertDialog( title: Row( children: [ const GradientStar(size: 20), const SizedBox(width: 8), Text( '#${word.formattedSessionNumber} ${word.word.toUpperCase()}', style: const TextStyle(fontWeight: FontWeight.bold), ), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${word.partOfSpeech} • ${word.category}', style: TextStyle(color: Theme.of(ctx).colorScheme.outline), ), const SizedBox(height: 12), Text( word.symbolicMeaning, style: const TextStyle(fontSize: 14, height: 1.45), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Закрыть'), ), ], ), ); } } /// Modal Bottom Sheet for rolling or typing an additional d10 word class _AddWordBottomSheet extends StatefulWidget { final ValueChanged onWordChosen; const _AddWordBottomSheet({required this.onWordChosen}); @override State<_AddWordBottomSheet> createState() => _AddWordBottomSheetState(); } class _AddWordBottomSheetState extends State<_AddWordBottomSheet> { final _repo = WordsRepository(); String _digits = ''; void _onRollCompleted(int finalNumber) { final word = _repo.getWordBySessionNumber(finalNumber); if (word != null) { widget.onWordChosen(word); } } void _onKeypadTap(String val) { HapticFeedback.lightImpact(); if (val == 'C') { setState(() => _digits = ''); return; } if (val == '⌫') { if (_digits.isNotEmpty) { setState(() => _digits = _digits.substring(0, _digits.length - 1)); } return; } String newDigits = _digits.length >= 3 ? val : _digits + val; setState(() => _digits = newDigits); if (newDigits.length == 3) { final num = int.tryParse(newDigits); if (num != null && num <= 999) { final word = _repo.getWordBySessionNumber(num); if (word != null) { widget.onWordChosen(word); } } } } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Container( padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), decoration: BoxDecoration( color: isDark ? const Color(0xFF141728) : Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(28)), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.35)), ), child: SafeArea( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 4, decoration: BoxDecoration( color: const Color(0xFFFFD54F).withValues(alpha: 0.5), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 12), const Row( children: [ GradientStar(size: 20), SizedBox(width: 8), Text( 'Докинуть уточняющий знак (3× d10)', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900), ), ], ), const SizedBox(height: 12), D10DiceRollerWidget( digits: _digits, isRolling: false, onRollPressed: () {}, onRollCompleted: _onRollCompleted, ), const SizedBox(height: 12), // Keypad for 3 digits _buildKeypadRow(['1', '2', '3']), const SizedBox(height: 6), _buildKeypadRow(['4', '5', '6']), const SizedBox(height: 6), _buildKeypadRow(['7', '8', '9']), const SizedBox(height: 6), _buildKeypadRow(['C', '0', '⌫']), ], ), ), ), ); } Widget _buildKeypadRow(List keys) { final isDark = Theme.of(context).brightness == Brightness.dark; return Row( children: keys.map((key) { final isSpecial = key == 'C' || key == '⌫'; return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Material( color: isSpecial ? const Color(0xFF2A2D4A) : (isDark ? const Color(0xFF20233A) : Colors.black12), borderRadius: BorderRadius.circular(12), child: InkWell( onTap: () => _onKeypadTap(key), borderRadius: BorderRadius.circular(12), child: Container( height: 42, alignment: Alignment.center, child: Text( key, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: isSpecial ? const Color(0xFFFFF9C4) : Colors.white, ), ), ), ), ), ), ); }).toList(), ); } }