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 'gradient_star_badge.dart'; class WordCardWidget extends StatefulWidget { final WordItem word; final VoidCallback? onNext; final VoidCallback? onPrevious; final VoidCallback? onRandom; final String? positionTitle; final String? question; final String? initialAiInterpretation; final String? historyEntryId; const WordCardWidget({ super.key, required this.word, this.onNext, this.onPrevious, this.onRandom, this.positionTitle, this.question, this.initialAiInterpretation, this.historyEntryId, }); @override State createState() => _WordCardWidgetState(); } class _WordCardWidgetState extends State { final _repo = WordsRepository(); final _aiService = AiOracleService(); bool _savedToHistory = false; String? _currentHistoryId; String? _aiInterpretation; bool _isLoadingAi = false; @override void initState() { super.initState(); _currentHistoryId = widget.historyEntryId; _aiInterpretation = widget.initialAiInterpretation; _savedToHistory = widget.historyEntryId != null; } @override void didUpdateWidget(covariant WordCardWidget oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.word.id != widget.word.id || oldWidget.word.sessionNumber != widget.word.sessionNumber) { _savedToHistory = widget.historyEntryId != null; _currentHistoryId = widget.historyEntryId; _aiInterpretation = widget.initialAiInterpretation; _isLoadingAi = false; } } Color _getPosColor(String pos) { switch (pos.toLowerCase()) { case 'существительное': return const Color(0xFF64B5F6); case 'глагол': return const Color(0xFF81C784); case 'прилагательное': return const Color(0xFFFFD54F); case 'наречие': case 'наречие-местоимение': return const Color(0xFFBA68C8); case 'местоимение': case 'местоименное прил.': return const Color(0xFF4DD0E1); case 'предлог': case 'союз': case 'частица': return const Color(0xFFFF8A65); default: return const Color(0xFF90A4AE); } } Future _recordToHistory() async { if (_savedToHistory) return; final newId = DateTime.now().millisecondsSinceEpoch.toString(); final sessionWord = SessionWord.fromWordItem( widget.word, positionTitle: widget.positionTitle, ); final session = OracleSession( id: newId, timestamp: DateTime.now(), wordsCount: 1, words: [sessionWord], question: widget.question, aiInterpretation: _aiInterpretation, ); await _repo.saveSession(session); if (!mounted) return; setState(() { _savedToHistory = true; _currentHistoryId = newId; }); HapticFeedback.mediumImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const GradientStar(size: 18), const SizedBox(width: 10), Expanded( child: Text('Символ «${widget.word.word}» сохранён в историю'), ), ], ), duration: const Duration(seconds: 2), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); } Future _fetchAiInterpretation() async { setState(() => _isLoadingAi = true); HapticFeedback.mediumImpact(); try { final result = await _aiService.interpret( word: widget.word, question: widget.question ?? '', positionTitle: widget.positionTitle, ); if (mounted) { setState(() { _aiInterpretation = result; _isLoadingAi = false; }); // Update history if already saved, or save now if (_currentHistoryId != null) { await _repo.updateHistoryEntry( _currentHistoryId!, aiInterpretation: result, question: widget.question, ); } else { await _recordToHistory(); } } } catch (e) { if (mounted) { setState(() => _isLoadingAi = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Ошибка генерации: $e'), behavior: SnackBarBehavior.floating, ), ); } } } void _copyToClipboard(BuildContext context) { final now = DateTime.now(); final day = now.day.toString().padLeft(2, '0'); final month = now.month.toString().padLeft(2, '0'); final year = now.year.toString(); final hour = now.hour.toString().padLeft(2, '0'); final min = now.minute.toString().padLeft(2, '0'); var text = '✨ Оракул 999 — Толкование символа ✨\n' 'Дата и время: $day.$month.$year $hour:$min\n' '${widget.question != null && widget.question!.isNotEmpty ? "Вопрос: «${widget.question}»\n" : ""}' 'Знак №${widget.word.formattedSessionNumber}: ${widget.word.word.toUpperCase()}\n' 'Аспект: ${widget.word.partOfSpeech} • ${widget.word.category}\n\n' '🔮 Символическое значение:\n${widget.word.description}'; if (_aiInterpretation != null) { text += '\n\n🤖 AI Трактовка:\n$_aiInterpretation'; } Clipboard.setData(ClipboardData(text: text)); HapticFeedback.lightImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const GradientStar(size: 18), const SizedBox(width: 10), Expanded(child: Text('Толкование «${widget.word.word}» скопировано')), ], ), 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; final posColor = _getPosColor(widget.word.partOfSpeech); return Card( elevation: 8, shadowColor: const Color(0xFFFFD54F).withValues(alpha: 0.18), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), side: BorderSide( color: const Color(0xFFFFD54F).withValues(alpha: 0.25), width: 1.2, ), ), clipBehavior: Clip.antiAlias, child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isDark ? [ const Color(0xFF1E2138), const Color(0xFF121424), ] : [ const Color(0xFFF9F9FF), const Color(0xFFEFF1F9), ], ), ), padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Question context banner if present if (widget.question != null && widget.question!.trim().isNotEmpty) ...[ Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: const Color(0xFF2A2D4A).withValues(alpha: 0.7), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFFFFD54F).withValues(alpha: 0.35), ), ), child: Row( children: [ const Icon( Icons.help_outline_rounded, size: 16, color: Color(0xFFFFD54F), ), const SizedBox(width: 8), Expanded( child: Text( 'Вопрос: «${widget.question}»', style: const TextStyle( fontSize: 12.5, fontWeight: FontWeight.w600, color: Color(0xFFFFF9C4), fontStyle: FontStyle.italic, ), ), ), ], ), ), ], // Optional spread position banner (e.g. "Исток / Прошлое") if (widget.positionTitle != null) ...[ Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: const Color(0xFFFFD54F).withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), border: Border.all( color: const Color(0xFFFFD54F).withValues(alpha: 0.4), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const GradientStar(size: 14), const SizedBox(width: 6), Text( widget.positionTitle!, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Color(0xFFFFD54F), letterSpacing: 0.5, ), ), ], ), ), ], // Top Row: Gradient Star Number Badge and Action Buttons Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ const GradientStar(size: 15), const SizedBox(width: 6), Text( 'ЗНАК #${widget.word.formattedSessionNumber}', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w900, letterSpacing: 1.0, color: Color(0xFFFFD54F), ), ), ], ), Row( children: [ IconButton( tooltip: 'Скопировать толкование', icon: const Icon(Icons.copy_rounded, size: 20), onPressed: () => _copyToClipboard(context), ), IconButton( tooltip: _savedToHistory ? 'Сохранено в историю' : 'Сохранить в историю', icon: Icon( _savedToHistory ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined, color: _savedToHistory ? const Color(0xFFFFD54F) : null, size: 22, ), onPressed: _recordToHistory, ), ], ), ], ), const SizedBox(height: 12), // Big Word Title Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text( widget.word.word.toUpperCase(), textAlign: TextAlign.center, style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.w900, letterSpacing: 1.5, color: isDark ? const Color(0xFFFFF9C4) : const Color(0xFF1B1D30), ), ), ), ), // Part of Speech & Category Badges Center( child: Wrap( spacing: 8, runSpacing: 6, alignment: WrapAlignment.center, children: [ Chip( avatar: Icon(Icons.auto_awesome, size: 14, color: posColor), label: Text( widget.word.partOfSpeech, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 12, color: posColor, ), ), backgroundColor: posColor.withValues(alpha: 0.12), side: BorderSide(color: posColor.withValues(alpha: 0.3)), padding: const EdgeInsets.symmetric(horizontal: 4), ), if (widget.word.category.isNotEmpty) Chip( label: Text( widget.word.category, style: TextStyle( fontSize: 12, color: isDark ? Colors.white70 : Colors.black87, ), ), backgroundColor: isDark ? Colors.white.withValues(alpha: 0.08) : Colors.black.withValues(alpha: 0.05), side: BorderSide.none, padding: const EdgeInsets.symmetric(horizontal: 2), ), ], ), ), const SizedBox(height: 14), Divider( height: 1, color: isDark ? Colors.white12 : Colors.black12, ), const SizedBox(height: 12), // Base Symbolic Divination Meaning Box Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: isDark ? const Color(0xFF141729) : const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(16), border: Border.all( color: const Color(0xFFFFD54F).withValues(alpha: 0.2), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row( children: [ GradientStar(size: 15), SizedBox(width: 8), Text( 'СИМВОЛИЧЕСКИЙ СМЫСЛ', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 1.0, color: Color(0xFFFFD54F), ), ), ], ), const SizedBox(height: 8), Text( widget.word.description, style: theme.textTheme.bodyMedium?.copyWith( height: 1.45, fontSize: 13.5, color: isDark ? const Color(0xFFE2E4F0) : const Color(0xFF2C3042), ), ), ], ), ), const SizedBox(height: 12), // AI Interpretation Block / Button if (_aiInterpretation != null) ...[ Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ const Color(0xFF2A2145), const Color(0xFF1A1B35), ], ), borderRadius: BorderRadius.circular(16), border: Border.all( color: const Color(0xFFBA68C8).withValues(alpha: 0.6), width: 1.2, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Row( children: [ Icon(Icons.psychology_rounded, size: 18, color: Color(0xFFE1BEE7)), SizedBox(width: 8), Text( 'AI ТРАКТОВКА ОРАКУЛА (G4F)', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 0.8, color: Color(0xFFE1BEE7), ), ), ], ), InkWell( onTap: _fetchAiInterpretation, child: const Padding( padding: EdgeInsets.all(4.0), child: Icon(Icons.refresh_rounded, size: 16, color: Color(0xFFE1BEE7)), ), ), ], ), const SizedBox(height: 8), Text( _aiInterpretation!, style: const TextStyle( fontSize: 13.5, height: 1.5, color: Color(0xFFF3E5F5), ), ), ], ), ), const SizedBox(height: 12), ] else ...[ // Button to generate AI interpretation SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _isLoadingAi ? null : _fetchAiInterpretation, icon: _isLoadingAi ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Color(0xFFBA68C8)), ), ) : const Icon(Icons.auto_awesome, size: 16, color: Color(0xFFBA68C8)), label: Text( _isLoadingAi ? 'Синтез толкования...' : 'Спросить AI Оракула (G4F-трактовка)', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 12.5, color: Color(0xFFE1BEE7), ), ), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 10), side: BorderSide( color: const Color(0xFFBA68C8).withValues(alpha: 0.5), ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), ), ), const SizedBox(height: 12), ], // Navigation Controls Row Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: widget.onPrevious, icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 14), label: const Text('Пред.'), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 11), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), ), ), const SizedBox(width: 8), IconButton.filledTonal( tooltip: 'Случайный жребий', onPressed: widget.onRandom, icon: const GradientStar(size: 20), style: IconButton.styleFrom( padding: const EdgeInsets.all(11), backgroundColor: const Color(0xFF2A2D4A), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), side: BorderSide( color: const Color(0xFFFFD54F).withValues(alpha: 0.3), ), ), ), ), const SizedBox(width: 8), Expanded( child: FilledButton.icon( onPressed: widget.onNext, icon: const Text('След.'), label: const Icon(Icons.arrow_forward_ios_rounded, size: 14), style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 11), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), ), ), ], ), ], ), ), ); } }