import 'package:flutter/material.dart'; import '../../../core/network/api_client.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/widgets/glass_container.dart'; import '../../../core/widgets/status_badge.dart'; /// Service Metadata Info used for Admin Config Navigation class ServiceConfigMeta { final String key; final String displayName; final String description; final IconData icon; final Color accentColor; const ServiceConfigMeta({ required this.key, required this.displayName, required this.description, required this.icon, required this.accentColor, }); } /// Centralized Service Configuration Management Widget for Admin Panel. class PipelineSettingsWidget extends StatefulWidget { final ApiClient? apiClient; const PipelineSettingsWidget({super.key, this.apiClient}); @override State createState() => _PipelineSettingsWidgetState(); } class _PipelineSettingsWidgetState extends State { String _selectedServiceKey = 'FinlyticAssets'; bool _isLoading = false; bool _isSaving = false; static const List _services = [ ServiceConfigMeta( key: 'FinlyticAssets', displayName: 'Asset Katalog & Logos', description: 'Verwaltet ISIN Asset-Stammdaten & Trade Republic Logo Fetcher', icon: Icons.inventory_2_outlined, accentColor: Color(0xFF00E5FF), ), ServiceConfigMeta( key: 'FinlyticNews', displayName: 'News Scraper & AI', description: 'RSS Web Scraper Intervall & Entwurf-Retention', icon: Icons.newspaper_outlined, accentColor: Color(0xFF3B82F6), ), ServiceConfigMeta( key: 'FinlyticTechnicalAnalysis', displayName: 'Technische Analyse', description: 'EMA/SMA Perioden, RSI Grenzwerte & Supertrend Multiplikator', icon: Icons.show_chart_outlined, accentColor: Color(0xFF8B5CF6), ), ServiceConfigMeta( key: 'FinlyticSentiment', displayName: 'Sentiment NLP', description: 'NLP Vertrauens-Schwellenwerte & Text-Batching', icon: Icons.psychology_outlined, accentColor: Color(0xFFEC4899), ), ServiceConfigMeta( key: 'FinlyticAnalyzer', displayName: 'Analyzer Signal Engine', description: 'Scraper Cron-Schedule & Minimaler Signal-Score', icon: Icons.analytics_outlined, accentColor: Color(0xFFF59E0B), ), ServiceConfigMeta( key: 'FinlyticTrades', displayName: 'Trade Manager', description: 'ATR Stop-Loss Multiplikator, Risiko-Prozente & Positionen', icon: Icons.candlestick_chart_outlined, accentColor: Color(0xFF10B981), ), ServiceConfigMeta( key: 'FinlyticFundamentals', displayName: 'Fundamentaldaten', description: 'Cache TTL Dauer & Yahoo Finance Fallback', icon: Icons.corporate_fare_outlined, accentColor: Color(0xFF06B6D4), ), ]; final Map> _controllers = { 'FinlyticAssets': { 'TradeRepublicMaxRequestPageSize': TextEditingController(text: '100'), 'AssetUpdateTypeDelay': TextEditingController(text: '0'), 'BatchAssetUpdateDelay': TextEditingController(text: '5'), }, 'FinlyticNews': { 'ScrapingIntervalMinutes': TextEditingController(text: '15'), 'PollingFrequencyMinutes': TextEditingController(text: '15'), 'ArticleRetentionDays': TextEditingController(text: '90'), 'DefaultPageSize': TextEditingController(text: '20'), }, 'FinlyticTechnicalAnalysis': { 'EmaShortPeriod': TextEditingController(text: '20'), 'SmaMediumPeriod': TextEditingController(text: '50'), 'SmaLongPeriod': TextEditingController(text: '200'), 'RsiOverboughtLimit': TextEditingController(text: '70'), 'RsiOversoldLimit': TextEditingController(text: '30'), 'SupertrendMultiplier': TextEditingController(text: '3.0'), }, 'FinlyticSentiment': { 'MinConfidenceScore': TextEditingController(text: '0.70'), 'MaxBatchSize': TextEditingController(text: '50'), }, 'FinlyticAnalyzer': { 'ScanCronSchedule': TextEditingController(text: '0 */1 * * *'), 'MinSignalScore': TextEditingController(text: '75'), 'EnableLog_MqttHealthPing': TextEditingController(text: 'false'), 'EnableLog_MqttGeneral': TextEditingController(text: 'true'), 'EnableLog_AnalyzerAuto': TextEditingController(text: 'true'), 'EnableLog_AnalyzerManual': TextEditingController(text: 'true'), 'EnableLog_DatabaseOps': TextEditingController(text: 'true'), }, 'FinlyticTrades': { 'AtrStopLossMultiplier': TextEditingController(text: '1.5'), 'RiskPerTradePercentage': TextEditingController(text: '1.0'), 'MaxOpenPositions': TextEditingController(text: '5'), }, 'FinlyticFundamentals': { 'CacheTtlHours': TextEditingController(text: '24'), 'EnableYahooFallback': TextEditingController(text: 'true'), }, }; @override void initState() { super.initState(); _fetchSettings(); } Future _fetchSettings() async { if (widget.apiClient == null) return; setState(() => _isLoading = true); try { final res = await widget.apiClient!.get('/api/v1/admin/settings'); if (res.statusCode == 200 && res.data is Map) { final Map data = Map.from(res.data); data.forEach((svc, items) { if (items is List) { _controllers.putIfAbsent(svc, () => {}); for (var item in items) { final key = item['key']?.toString(); final val = item['value']?.toString(); if (key != null && val != null) { if (_controllers[svc]!.containsKey(key)) { _controllers[svc]![key]!.text = val; } else { _controllers[svc]![key] = TextEditingController(text: val); } } } } }); } } catch (_) { // Retain standard default in-memory values } finally { if (mounted) setState(() => _isLoading = false); } } Future _saveSettings() async { setState(() => _isSaving = true); try { final currentSvcControllers = _controllers[_selectedServiceKey] ?? {}; final payload = {}; currentSvcControllers.forEach((k, v) { payload[k] = v.text; }); if (widget.apiClient != null) { await widget.apiClient!.put('/api/v1/admin/settings/$_selectedServiceKey', data: payload); } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle_outline, color: Colors.black), const SizedBox(width: 8), Expanded( child: Text( 'Einstellungen für $_selectedServiceKey gespeichert & via MQTT synchronisiert.', style: const TextStyle(color: Colors.black, fontWeight: FontWeight.w600), ), ), ], ), backgroundColor: AppTheme.primaryEmerald, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ); } } catch (ex) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Fehler beim Speichern: $ex'), backgroundColor: AppTheme.accentRed, behavior: SnackBarBehavior.floating, ), ); } } finally { if (mounted) setState(() => _isSaving = false); } } @override Widget build(BuildContext context) { final activeService = _services.firstWhere((s) => s.key == _selectedServiceKey, orElse: () => _services.first); final activeControllers = _controllers[_selectedServiceKey] ?? {}; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Service Selection Ribbon SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: _services.map((svc) { final isSelected = svc.key == _selectedServiceKey; return Padding( padding: const EdgeInsets.only(right: 8, bottom: 12), child: InkWell( onTap: () => setState(() => _selectedServiceKey = svc.key), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: isSelected ? svc.accentColor.withValues(alpha: 0.15) : AppTheme.glassSurface, borderRadius: BorderRadius.circular(12), border: Border.all( color: isSelected ? svc.accentColor : AppTheme.glassBorder, width: isSelected ? 1.5 : 1, ), ), child: Row( children: [ Icon(svc.icon, size: 18, color: isSelected ? svc.accentColor : AppTheme.textMuted), const SizedBox(width: 8), Text( svc.displayName, style: TextStyle( fontSize: 13, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? AppTheme.textPrimary : AppTheme.textSecondary, ), ), ], ), ), ), ); }).toList(), ), ), const SizedBox(height: 8), // Service Details & Config Panel GlassContainer( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: activeService.accentColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), border: Border.all(color: activeService.accentColor.withValues(alpha: 0.3)), ), child: Icon(activeService.icon, color: activeService.accentColor, size: 22), ), const SizedBox(width: 14), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( activeService.displayName, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), Text( activeService.description, style: TextStyle(fontSize: 12, color: AppTheme.textSecondary), ), ], ), ], ), if (_isLoading) SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2, color: AppTheme.primaryEmerald)) else StatusBadge(label: 'MQTT Sync', color: AppTheme.primaryEmerald), ], ), const SizedBox(height: 16), const Divider(color: Colors.white10), const SizedBox(height: 16), // Parameter Input List ...activeControllers.entries.map((entry) { final keyName = entry.key; final controller = entry.value; final isBoolean = controller.text.toLowerCase() == 'true' || controller.text.toLowerCase() == 'false'; if (isBoolean) { final boolVal = controller.text.toLowerCase() == 'true'; return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: AppTheme.glassSurface, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppTheme.glassBorder), ), child: SwitchListTile( contentPadding: EdgeInsets.zero, title: Text(_formatLabel(keyName), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)), //subtitle: Text('Schlüssel: $keyName', style: TextStyle(fontSize: 11, color: AppTheme.textMuted)), value: boolVal, activeThumbColor: activeService.accentColor, onChanged: (val) => setState(() => controller.text = val.toString()), ), ); } return Padding( padding: const EdgeInsets.only(bottom: 14), child: Row( children: [ Expanded( child: TextField( controller: controller, decoration: InputDecoration( labelText: _formatLabel(keyName), //helperText: 'Schlüssel: $keyName', prefixIcon: Icon(Icons.tune_outlined, size: 18, color: activeService.accentColor), ), ), ), /*if (isNumeric) ...[ const SizedBox(width: 8), IconButton.filledTonal( icon: const Icon(Icons.remove, size: 18), onPressed: () => setState(() => _adjustNumericValue(controller, -1.0, isDouble: isDouble)), ), IconButton.filledTonal( icon: const Icon(Icons.add, size: 18), onPressed: () => setState(() => _adjustNumericValue(controller, 1.0, isDouble: isDouble)), ), ],*/ ], ), ); }), const SizedBox(height: 12), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _isSaving ? null : _saveSettings, icon: _isSaving ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black)) : const Icon(Icons.save_outlined), label: Text( _isSaving ? 'Speichere & Sende via MQTT...' : 'Einstellungen für ${activeService.displayName} Speichern', style: const TextStyle(fontWeight: FontWeight.bold), ), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.primaryEmerald, foregroundColor: Colors.black, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), ), ], ), ), ], ); } String _formatLabel(String key) { return key .replaceAll(RegExp(r'(?