import 'manual_dive_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/word_model.dart'; import '../models/culture_branch.dart'; import '../services/ai_oracle_service.dart'; import '../services/words_repository.dart'; import '../widgets/gradient_star_badge.dart'; import '../services/oracle_master_prompt_generator.dart'; class EditSymbolScreen extends StatefulWidget { final WordItem word; const EditSymbolScreen({super.key, required this.word}); @override State createState() => _EditSymbolScreenState(); } class _EditSymbolScreenState extends State { final _formKey = GlobalKey(); final _repo = WordsRepository(); final _aiService = AiOracleService(); late final TextEditingController _coreController; late final TextEditingController _oracleController; late final TextEditingController _shadowController; late final TextEditingController _asksController; late final TextEditingController _stepController; late final TextEditingController _culturesController; bool _isSaving = false; bool _isGeneratingAi = false; bool _isDistributing = false; @override void initState() { super.initState(); final w = widget.word; _coreController = TextEditingController(text: w.core); _oracleController = TextEditingController( text: w.oracle.isNotEmpty ? w.oracle : w.description, ); _shadowController = TextEditingController(text: w.shadow); _asksController = TextEditingController(text: w.asks); _stepController = TextEditingController(text: w.step); _culturesController = TextEditingController(text: formatCulturesBlock(w.cultures)); _coreController.addListener(() => _checkAndDistributeMultiBlockPaste(_coreController)); _oracleController.addListener(() => _checkAndDistributeMultiBlockPaste(_oracleController)); } void _checkAndDistributeMultiBlockPaste(TextEditingController controller) { if (_isDistributing) return; final val = controller.text; if (val.length > 80 && (val.contains('##') || (val.toUpperCase().contains('ОРАКУЛ') && (val.toUpperCase().contains('ТЕНЬ') || val.toUpperCase().contains('ЯДРО'))))) { final blocks = _parseRevisionBlocks(val); if (blocks.length >= 2) { _isDistributing = true; setState(() { if (blocks.containsKey('ЯДРО')) _coreController.text = blocks['ЯДРО']!; if (blocks.containsKey('ОРАКУЛ')) _oracleController.text = blocks['ОРАКУЛ']!; if (blocks.containsKey('ТЕНЬ')) _shadowController.text = blocks['ТЕНЬ']!; if (blocks.containsKey('ВОПРОС')) _asksController.text = blocks['ВОПРОС']!; if (blocks.containsKey('ЖЕСТ')) _stepController.text = blocks['ЖЕСТ']!; if (blocks.containsKey('КУЛЬТУРЫ')) _culturesController.text = formatCulturesBlock(parseCulturesBlock(blocks['КУЛЬТУРЫ']!)); }); _isDistributing = false; HapticFeedback.mediumImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.auto_fix_high_rounded, color: Color(0xFF81C784), size: 20), const SizedBox(width: 10), Expanded( child: Text('Умное распределение: распознано ${blocks.length} секций и заполнено по всем 5 полям!'), ), ], ), backgroundColor: const Color(0xFF1B3D2F), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); } } } @override void dispose() { _coreController.dispose(); _oracleController.dispose(); _shadowController.dispose(); _asksController.dispose(); _stepController.dispose(); _culturesController.dispose(); super.dispose(); } /// Builds the Master Prompt for deep culturological and mythological enrichment of the Archetype. String _buildPrePrompt() { return OracleMasterPromptGenerator.generateArchetypeEnrichmentPrompt( widget.word, customCore: _coreController.text, customOracle: _oracleController.text, customShadow: _shadowController.text, customAsks: _asksController.text, customStep: _stepController.text, ); } void _copyPrePrompt() { final prompt = _buildPrePrompt(); Clipboard.setData(ClipboardData(text: prompt)); HapticFeedback.mediumImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Row( children: [ GradientStar(size: 18), SizedBox(width: 10), Expanded( child: Text( 'Предпромпт со всей текущей информацией скопирован! Вставьте его в любую нейросеть.', style: TextStyle(fontSize: 13), ), ), ], ), duration: const Duration(seconds: 3), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); } void _showPrePromptViewer() { final prompt = _buildPrePrompt(); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF151829), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (ctx) => DraggableScrollableSheet( initialChildSize: 0.75, minChildSize: 0.4, maxChildSize: 0.95, expand: false, builder: (_, scrollController) => Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: const Color(0xFF3949AB), borderRadius: BorderRadius.circular(2), ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Row( children: [ GradientStar(size: 20), SizedBox(width: 8), Text( 'Полный Предпромпт для ИИ', style: TextStyle( color: Color(0xFFFFF9C4), fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), IconButton( icon: const Icon(Icons.close_rounded, color: Color(0xFF8C93B8)), onPressed: () => Navigator.pop(ctx), ), ], ), const SizedBox(height: 8), const Text( 'Включает все текущие данные знака, чтобы не повторяться и получить глубокую редакцию:', style: TextStyle(color: Color(0xFF8C93B8), fontSize: 12), ), const SizedBox(height: 12), Expanded( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFF0C0E1A), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFF2A2D4A)), ), child: SingleChildScrollView( controller: scrollController, child: SelectableText( prompt, style: const TextStyle( color: Color(0xFFE0E2EC), fontSize: 12.5, height: 1.45, fontFamily: 'monospace', ), ), ), ), ), const SizedBox(height: 12), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFFD54F), foregroundColor: const Color(0xFF0C0E1A), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), icon: const Icon(Icons.copy_rounded, size: 18), label: const Text( 'Скопировать предпромпт в буфер обмена', style: TextStyle(fontWeight: FontWeight.w900, fontSize: 13), ), onPressed: () { Navigator.pop(ctx); _copyPrePrompt(); }, ), ], ), ), ), ); } Map _parseRevisionBlocks(String text) { final blocks = {}; if (text.trim().isEmpty) return blocks; // 1. Clean markdown code fences if wrapped in ```markdown ... ``` var src = text.trim(); if (src.startsWith('```')) { src = src.replaceFirst(RegExp(r'^```[a-zA-Z]*\n?'), ''); if (src.endsWith('```')) { src = src.substring(0, src.length - 3).trim(); } } final lines = src.replaceAll('\r\n', '\n').split('\n'); String? detectSlot(String line) { final stripped = line.trim(); if (stripped.isEmpty || stripped.length > 95) return null; final isHeaderFormat = stripped.startsWith('#') || stripped.startsWith('*') || stripped.startsWith('[') || RegExp(r'^(?:\d+[\.\)]|[IVX]+[\.\)])').hasMatch(stripped) || (stripped.length < 70 && (stripped.endsWith(':') || stripped == stripped.toUpperCase())) || RegExp(r'(?:ЯДРО|ОРАКУЛ|ТЕНЬ|ВОПРОС|ЖЕСТ|КОАН|КУЛЬТУР)', caseSensitive: false).hasMatch(stripped); if (!isHeaderFormat) return null; // 0. КУЛЬТУРЫ — сравнительный атлас традиций (подзаголовки ### внутри остаются в блоке) if (RegExp(r'^[#*\s\[\d.)]*(?:🌍\s*)?КУЛЬТУР[А-Яа-я]*(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) { return 'КУЛЬТУРЫ'; } // 1. ЯДРО / ФОРМУЛА if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ЯДР[ОАЕУ]|ФОРМУЛ[А-Яа-я]*|ОНТОЛОГИ[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) { return 'ЯДРО'; } // 2. ВОПРОС / КОАН (checked before ТЕНЬ to prevent 'экзистенциальный' substring collision) if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ВОПРОС[А-Яа-я]*|КОАН[А-Яа-я]*|РЕФЛЕКСИ[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) { return 'ВОПРОС'; } // 3. ТЕНЬ / ЛОВУШКА if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ТЕН[ЬЯЕИЮ]|ТЕНЕВ[А-Яа-я]*|ЛОВУШК[А-Яа-я]*|ИСКАЖЕН[А-Яа-я]*|СЛЕП[А-Яа-я]*\s+ЗОН[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) { return 'ТЕНЬ'; } // 4. ЖЕСТ / МИКРОДЕЙСТВИЕ if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ЖЕСТ[А-Яа-я]*|МИКРОДЕЙСТВИ[А-Яа-я]*|ДЕЙСТВИ[ЕЯ]|ПРАКТИК[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) { return 'ЖЕСТ'; } // 5. ОРАКУЛ / СТАТЬЯ / ЭССЕ if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ОРАКУЛ[А-Яа-я]*|СТАТЬ[ЯЕИЮ]|ЭССЕ|ТОЛКОВАНИ[А-Яа-я]*|ОБОГАЩЕНИ[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) { return 'ОРАКУЛ'; } return null; } final headers = <({int lineIdx, String slot})>[]; for (int i = 0; i < lines.length; i++) { final slot = detectSlot(lines[i]); if (slot != null) { headers.add((lineIdx: i, slot: slot)); } } for (int i = 0; i < headers.length; i++) { final startLineIdx = headers[i].lineIdx + 1; final slot = headers[i].slot; final endLineIdx = (i + 1 < headers.length) ? headers[i + 1].lineIdx : lines.length; var content = lines.sublist(startLineIdx, endLineIdx).join('\n').trim(); // Strip prompt instructions in parentheses like (1–2 чеканных...) at the top content = content.replaceFirst( RegExp(r'^\s*\([^)]*(?:предложен|абзац|коан|действи|формул|ловушк|стать)[^)]*\)\s*\n?', caseSensitive: false), '', ).trim(); // Strip trailing metric block like "Метрика: 4850 символов" content = content.replaceAll( RegExp(r'(?:\n|\r\n)\s*(?:Метрика|Количество символов|Символов|Длина текста)[:—–\s][\s\S]*$', caseSensitive: false), '', ).trim(); if (content.isNotEmpty) { if (!blocks.containsKey(slot) || content.length > blocks[slot]!.length) { blocks[slot] = content; } } } return blocks; } Future _pasteFromMarkdownClipboard() async { HapticFeedback.lightImpact(); final data = await Clipboard.getData(Clipboard.kTextPlain); final text = data?.text?.trim() ?? ''; if (text.isEmpty) { _showManualMarkdownInputDialog(); return; } final blocks = _parseRevisionBlocks(text); if (blocks.isEmpty) { _showManualMarkdownInputDialog(initialText: text); return; } _showImportPreviewModal(blocks, rawText: text, title: 'Умная вставка из Markdown'); } void _showManualMarkdownInputDialog({String? initialText}) { final controller = TextEditingController(text: initialText ?? ''); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF151829), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (ctx) => Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(ctx).viewInsets.bottom + 20, left: 16, right: 16, top: 16, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ const Icon(Icons.paste_rounded, color: Color(0xFFA5D6A7), size: 20), const SizedBox(width: 8), const Expanded( child: Text( 'Вставка Markdown-статьи', style: TextStyle(color: Color(0xFFFFF9C4), fontSize: 16, fontWeight: FontWeight.bold), ), ), IconButton( icon: const Icon(Icons.close_rounded, color: Color(0xFF8C93B8)), onPressed: () => Navigator.pop(ctx), ), ], ), const SizedBox(height: 8), const Text( 'Вставьте сюда текст от нейросети с заголовками ## ЯДРО, ## ОРАКУЛ, ## ТЕНЬ, ## ВОПРОС, ## ЖЕСТ:', style: TextStyle(color: Color(0xFFECEFF1), fontSize: 12), ), const SizedBox(height: 12), TextField( controller: controller, maxLines: 8, style: const TextStyle(color: Color(0xFFFFF9C4), fontSize: 13, height: 1.4), decoration: InputDecoration( filled: true, fillColor: const Color(0xFF1E2238), hintText: '## ЯДРО\nФормула...\n\n## ОРАКУЛ\nСтатья...', hintStyle: TextStyle(color: Colors.white.withOpacity(0.3)), border: OutlineInputBorder(borderRadius: BorderRadius.circular(14), borderSide: BorderSide.none), ), ), const SizedBox(height: 14), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFA5D6A7), foregroundColor: const Color(0xFF0C0E1A), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), icon: const Icon(Icons.auto_fix_high_rounded, size: 18), label: const Text( 'Распознать и применить к полям', style: TextStyle(fontWeight: FontWeight.w900, fontSize: 13), ), onPressed: () { final input = controller.text.trim(); Navigator.pop(ctx); if (input.isEmpty) return; final blocks = _parseRevisionBlocks(input); if (blocks.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Не удалось найти размеченные блоки (## ЯДРО, ## ОРАКУЛ, и т.д.)')), ); } else { _showImportPreviewModal(blocks, rawText: input, title: 'Распознанные блоки статьи'); } }, ), ], ), ), ); } void _showImportPreviewModal(Map blocks, {String? rawText, String title = 'Импорт из Markdown'}) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF151829), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (ctx) => DraggableScrollableSheet( initialChildSize: 0.82, minChildSize: 0.5, maxChildSize: 0.95, expand: false, builder: (_, scrollController) => Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: const Color(0xFF3949AB), borderRadius: BorderRadius.circular(2), ), ), ), Row( children: [ const GradientStar(size: 20), const SizedBox(width: 8), Expanded( child: Text( title, style: const TextStyle( color: Color(0xFFFFF9C4), fontSize: 16, fontWeight: FontWeight.bold, ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: const Color(0xFF2E7D32).withOpacity(0.3), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFF81C784)), ), child: Text( '${blocks.length}/5 блоков', style: const TextStyle(color: Color(0xFFA5D6A7), fontSize: 11, fontWeight: FontWeight.bold), ), ), const SizedBox(width: 4), IconButton( icon: const Icon(Icons.close_rounded, color: Color(0xFF8C93B8)), onPressed: () => Navigator.pop(ctx), ), ], ), const SizedBox(height: 8), const Text( 'Проверьте распознанные секции. При нажатии «Применить» они мгновенно заполнят форму знака.', style: TextStyle(color: Color(0xFFECEFF1), fontSize: 12, height: 1.35), ), const SizedBox(height: 12), Expanded( child: ListView( controller: scrollController, children: [ if (blocks.containsKey('ЯДРО')) _buildAiBlockPreview('✨ ЯДРО', blocks['ЯДРО']!, const Color(0xFFFFD54F)), if (blocks.containsKey('ОРАКУЛ')) _buildAiBlockPreview('📖 ОРАКУЛ / СТАТЬЯ', blocks['ОРАКУЛ']!, const Color(0xFF64B5F6)), if (blocks.containsKey('ТЕНЬ')) _buildAiBlockPreview('🌑 ТЕНЬ', blocks['ТЕНЬ']!, const Color(0xFFE57373)), if (blocks.containsKey('ВОПРОС')) _buildAiBlockPreview('❓ ВОПРОС', blocks['ВОПРОС']!, const Color(0xFFBA68C8)), if (blocks.containsKey('ЖЕСТ')) _buildAiBlockPreview('⚡ ЖЕСТ', blocks['ЖЕСТ']!, const Color(0xFF81C784)), if (blocks.containsKey('КУЛЬТУРЫ')) _buildAiBlockPreview( '🌍 КУЛЬТУРЫ (${parseCulturesBlock(blocks['КУЛЬТУРЫ']!).length} ветвей)', blocks['КУЛЬТУРЫ']!, const Color(0xFFFFB74D), ), ], ), ), const SizedBox(height: 12), Row( children: [ if (rawText != null) Expanded( flex: 2, child: OutlinedButton( style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF80DEEA), side: const BorderSide(color: Color(0xFF80DEEA)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 12), ), onPressed: () { Navigator.pop(ctx); _showManualMarkdownInputDialog(initialText: rawText); }, child: const Text('Сырой MD', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12)), ), ), if (rawText != null) const SizedBox(width: 8), Expanded( flex: 3, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFFD54F), foregroundColor: const Color(0xFF0C0E1A), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), icon: const Icon(Icons.check_circle_rounded, size: 18), label: const Text( 'Применить к полям', style: TextStyle(fontWeight: FontWeight.w900, fontSize: 13), ), onPressed: () { setState(() { if (blocks.containsKey('ЯДРО')) _coreController.text = blocks['ЯДРО']!; if (blocks.containsKey('ОРАКУЛ')) _oracleController.text = blocks['ОРАКУЛ']!; if (blocks.containsKey('ТЕНЬ')) _shadowController.text = blocks['ТЕНЬ']!; if (blocks.containsKey('ВОПРОС')) _asksController.text = blocks['ВОПРОС']!; if (blocks.containsKey('ЖЕСТ')) _stepController.text = blocks['ЖЕСТ']!; if (blocks.containsKey('КУЛЬТУРЫ')) _culturesController.text = formatCulturesBlock(parseCulturesBlock(blocks['КУЛЬТУРЫ']!)); }); Navigator.pop(ctx); HapticFeedback.mediumImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle_rounded, color: Color(0xFF81C784), size: 20), const SizedBox(width: 10), Expanded(child: Text('Применено ${blocks.length} блоков статьи к знаку!')), ], ), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); }, ), ), ], ), ], ), ), ), ); } Future _generateAiRevision() async { setState(() => _isGeneratingAi = true); HapticFeedback.mediumImpact(); try { final prompt = _buildPrePrompt(); final response = await _aiService.generateSymbolRevision(prePrompt: prompt); if (!mounted) return; setState(() => _isGeneratingAi = false); if (response.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Не удалось получить ответ от ИИ. Попробуйте скопировать предпромпт вручную.')), ); return; } final blocks = _parseRevisionBlocks(response); _showImportPreviewModal(blocks, rawText: response, title: 'Предложенное ИИ-обогащение'); } catch (e) { if (mounted) { setState(() => _isGeneratingAi = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ошибка ИИ: $e')), ); } } } Widget _buildAiBlockPreview(String title, String content, Color accent) { return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFF1E2238), borderRadius: BorderRadius.circular(12), border: Border.all(color: accent.withValues(alpha: 0.5)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration(color: accent, shape: BoxShape.circle), ), const SizedBox(width: 6), Text( title, style: TextStyle(color: accent, fontSize: 12, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 6), Text( content, maxLines: 6, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Color(0xFFE0E2EC), fontSize: 13, height: 1.45), ), ], ), ); } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _isSaving = true); HapticFeedback.mediumImpact(); await _repo.saveCustomEdit( widget.word.word, core: _coreController.text, oracle: _oracleController.text, shadow: _shadowController.text, asks: _asksController.text, step: _stepController.text, cultures: parseCulturesBlock(_culturesController.text), ); if (!mounted) return; setState(() => _isSaving = false); Navigator.pop(context, true); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const GradientStar(size: 18), const SizedBox(width: 10), Expanded(child: Text('Канон «${widget.word.word}» успешно обновлен!')), ], ), backgroundColor: const Color(0xFF2E7D32), behavior: SnackBarBehavior.floating, ), ); } Future _confirmReset() async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: const Color(0xFF1E2238), title: const Text('Сбросить к канону релиза?', style: TextStyle(color: Color(0xFFFFF9C4))), content: const Text( 'Ваша персональная редакция этого знака будет удалена, вернётся оригинальный текст из базы.', style: TextStyle(color: Color(0xFFB0BEC5)), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('Отмена', style: TextStyle(color: Color(0xFF9FA8DA))), ), FilledButton( style: FilledButton.styleFrom(backgroundColor: const Color(0xFFE57373)), onPressed: () => Navigator.pop(ctx, true), child: const Text('Сбросить'), ), ], ), ); if (confirmed == true) { setState(() => _isSaving = true); await _repo.resetCustomEdit(widget.word.word); if (!mounted) return; setState(() => _isSaving = false); Navigator.pop(context, true); } } @override Widget build(BuildContext context) { final w = widget.word; return Scaffold( backgroundColor: const Color(0xFF0C0E1A), appBar: AppBar( title: Text( 'Редактор: ${w.word.toUpperCase()}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), actions: [ IconButton( tooltip: '78-полюсный паспорт (Ручной обогатитель)', icon: const Icon(Icons.badge_outlined, color: Color(0xFFFFD54F)), onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (_) => ManualDiveScreen(initialWord: widget.word)), ); }, ), IconButton( tooltip: 'Скопировать промпт на обогащение', icon: const Icon(Icons.copy_all_rounded, color: Color(0xFFFFD54F)), onPressed: _copyPrePrompt, ), IconButton( tooltip: 'Вставить из Markdown', icon: const Icon(Icons.paste_rounded, color: Color(0xFFA5D6A7)), onPressed: _pasteFromMarkdownClipboard, ), IconButton( tooltip: 'Сгенерировать через ИИ', icon: const Icon(Icons.auto_awesome_rounded, color: Color(0xFF80DEEA)), onPressed: _isGeneratingAi ? null : _generateAiRevision, ), if (w.isUserEdited) IconButton( tooltip: 'Сбросить к заводскому канону', icon: const Icon(Icons.restore_rounded, color: Color(0xFFE57373)), onPressed: _isSaving ? null : _confirmReset, ), IconButton( tooltip: 'Сохранить', icon: const Icon(Icons.check_rounded, color: Color(0xFF81C784), size: 26), onPressed: _isSaving ? null : _save, ), ], ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), children: [ // Prominent Pre-Prompt Context Banner Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF281E44), Color(0xFF14172B)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(18), border: Border.all(color: const Color(0xFFFFD54F).withOpacity(0.5)), boxShadow: [ BoxShadow( color: const Color(0xFFFFD54F).withOpacity(0.08), blurRadius: 10, ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const GradientStar(size: 18), const SizedBox(width: 8), const Expanded( child: Text( 'ПРЕДПРОМПТ ДЛЯ НЕЙРОСЕТЕЙ (ИИ)', style: TextStyle( color: Color(0xFFFFD54F), fontSize: 11.5, fontWeight: FontWeight.w900, letterSpacing: 1.0, ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( color: const Color(0xFF3B2B5E), borderRadius: BorderRadius.circular(6), ), child: Text( '№${w.formattedSessionNumber}', style: const TextStyle(color: Color(0xFFFFF9C4), fontSize: 10, fontWeight: FontWeight.bold), ), ), ], ), const SizedBox(height: 8), const Text( 'Содержит всю текущую информацию о знаке (ядро, оракул, тень, вопрос, жест, слои традиций), чтобы при формулировании правок не повторяться, а углублять канон.', style: TextStyle(color: Color(0xFFECEFF1), fontSize: 12, height: 1.35), ), const SizedBox(height: 12), Row( children: [ Expanded( child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFFFFD54F), side: BorderSide(color: const Color(0xFFFFD54F).withValues(alpha: 0.6)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(vertical: 8), ), icon: const Icon(Icons.copy_rounded, size: 14), label: const Text('Скопировать промпт', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11.5)), onPressed: _copyPrePrompt, ), ), const SizedBox(width: 8), Expanded( child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF80DEEA), side: BorderSide(color: const Color(0xFF80DEEA).withValues(alpha: 0.6)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(vertical: 8), ), icon: const Icon(Icons.visibility_rounded, size: 14), label: const Text('Просмотр промпта', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11.5)), onPressed: _showPrePromptViewer, ), ), ], ), const SizedBox(height: 8), Row( children: [ Expanded( child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF1B3D2F), foregroundColor: const Color(0xFFA5D6A7), side: const BorderSide(color: Color(0xFF81C784)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(vertical: 9), ), icon: const Icon(Icons.paste_rounded, size: 15, color: Color(0xFFA5D6A7)), label: const Text('Вставить из Markdown', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11.5)), onPressed: _pasteFromMarkdownClipboard, ), ), const SizedBox(width: 8), Expanded( child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF382959), foregroundColor: const Color(0xFFFFF9C4), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(vertical: 9), ), icon: _isGeneratingAi ? const SizedBox( width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFFFFD54F)), ) : const Icon(Icons.auto_awesome_rounded, size: 15, color: Color(0xFFFFD54F)), label: const Text('ИИ-обогащение', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11.5)), onPressed: _isGeneratingAi ? null : _generateAiRevision, ), ), ], ), ], ), ), const SizedBox(height: 16), _buildSectionField( title: '✨ ЯДРО / ФОРМУЛА ЗАКОНА (Core)', hint: 'Краткая суть принципа и природы знака (1-2 предложения)', controller: _coreController, minLines: 2, maxLines: 4, accentColor: const Color(0xFFFFD54F), ), const SizedBox(height: 16), _buildSectionField( title: '📖 ОРАКУЛ / ТОЛКОВАНИЕ (Oracle)', hint: 'Развернутое символическое и практическое прочтение знака в раскладе', controller: _oracleController, minLines: 6, maxLines: 14, accentColor: const Color(0xFF64B5F6), ), const SizedBox(height: 16), _buildSectionField( title: '🌑 ТЕНЬ / ИСКАЖЕНИЕ (Shadow)', hint: 'Ловушка знака, избыток или дефицит принципа, скрытая опасность', controller: _shadowController, minLines: 2, maxLines: 4, accentColor: const Color(0xFFE57373), ), const SizedBox(height: 16), _buildSectionField( title: '❓ ВОПРОС / ЭКСПЕРТИЗА (Asks)', hint: 'Экзистенциальный вопрос вопрошающему, проверка честности', controller: _asksController, minLines: 2, maxLines: 4, accentColor: const Color(0xFFBA68C8), ), const SizedBox(height: 16), _buildSectionField( title: '⚡ РИТУАЛЬНЫЙ ЖЕСТ / ДЕЙСТВИЕ (Step)', hint: 'Конкретное заземляющее действие или практика в физическом мире', controller: _stepController, minLines: 2, maxLines: 4, accentColor: const Color(0xFF81C784), ), const SizedBox(height: 16), _buildSectionField( title: '🌍 КУЛЬТУРЫ ЧЕЛОВЕЧЕСТВА (Cultures)', hint: '### Шумер и Месопотамия\nТекст…\n\n### Древний Египет\nТекст…\n\nПусто — ветви извлекаются из ОРАКУЛА автоматически', controller: _culturesController, minLines: 4, maxLines: 14, accentColor: const Color(0xFFFFB74D), ), const SizedBox(height: 24), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2E7D32), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), onPressed: _isSaving ? null : _save, icon: _isSaving ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.save_rounded), label: const Text( 'Сохранить изменения знака', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), ), if (w.isUserEdited) ...[ const SizedBox(height: 12), OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFFE57373), side: const BorderSide(color: Color(0xFFE57373)), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), onPressed: _isSaving ? null : _confirmReset, icon: const Icon(Icons.restore_rounded), label: const Text('Сбросить к канону из релиза'), ), ], const SizedBox(height: 20), ], ), ), ); } Widget _buildSectionField({ required String title, required String hint, required TextEditingController controller, required int minLines, required int maxLines, required Color accentColor, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 4, height: 16, decoration: BoxDecoration( color: accentColor, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), Text( title, style: TextStyle( color: accentColor, fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), ), ], ), const SizedBox(height: 8), TextFormField( controller: controller, minLines: minLines, maxLines: maxLines, style: const TextStyle( color: Color(0xFFECEFF1), fontSize: 14, height: 1.45, ), decoration: InputDecoration( hintText: hint, hintStyle: const TextStyle(color: Color(0xFF546E7A), fontSize: 13), filled: true, fillColor: const Color(0xFF151829), contentPadding: const EdgeInsets.all(14), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFF263238)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide(color: accentColor.withOpacity(0.3)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide(color: accentColor, width: 1.5), ), ), ), ], ); } }