import 'package:flutter/material.dart'; import 'package:finlytic_app/core/theme/app_theme.dart'; import 'package:finlytic_app/core/widgets/status_badge.dart'; import '../../../../features/trades/models/trade_model.dart'; import '../../../../features/trades/models/trade_acceptance_dto.dart'; class TradeExecutionDialog { static const double _defaultPositionSize = 1000.0; static const double _defaultLeverage = 1.0; static void show( BuildContext context, { required TradeModel trade, required String defaultSymbol, bool isActive = false, required Function(TradeAcceptanceDto dto) onAccept, Function(String tradeId)? onReject, }) { final initEntry = trade.actualEntryPrice > 0 ? trade.actualEntryPrice : (trade.entryPrice > 0 ? trade.entryPrice : 100.0); final initPos = trade.positionSize > 0 ? trade.positionSize : _defaultPositionSize; final initLev = trade.leverageUsed > 0 ? trade.leverageUsed : _defaultLeverage; final calcQty = (initEntry > 0 && initPos > 0) ? (initPos * initLev) / initEntry : 10.0; // We don't have a direct quantity field in TradeModel, but we calculate it. // Let's use the explicit quantity if it exists, otherwise calculate it final initQty = trade.quantity > 0 ? trade.quantity : calcQty; final actualEntryController = TextEditingController(text: initEntry.toStringAsFixed(2)); final positionSizeController = TextEditingController(text: initPos.toStringAsFixed(2)); final leverageController = TextEditingController(text: initLev.toStringAsFixed(1)); final quantityController = TextEditingController(text: initQty.toStringAsFixed(4)); final entryFeeController = TextEditingController(text: trade.entryFee.toStringAsFixed(2)); final exitFeeController = TextEditingController(text: trade.exitFee.toStringAsFixed(2)); final slController = TextEditingController(text: trade.stopLoss.toString()); final tpController = TextEditingController(text: trade.takeProfit.toString()); void recalculateQuantity() { final entry = double.tryParse(actualEntryController.text) ?? 0.0; final posSize = double.tryParse(positionSizeController.text) ?? 0.0; final lev = double.tryParse(leverageController.text) ?? 1.0; if (entry > 0 && posSize > 0) { final q = (posSize * lev) / entry; quantityController.text = q.toStringAsFixed(4); } } actualEntryController.addListener(recalculateQuantity); positionSizeController.addListener(recalculateQuantity); leverageController.addListener(recalculateQuantity); showDialog( context: context, builder: (dialogContext) { return AlertDialog( backgroundColor: AppTheme.cardSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: AppTheme.glassBorder), ), title: Row( children: [ Icon(isActive ? Icons.tune : Icons.edit_note_outlined, color: AppTheme.primaryEmerald, size: 22), const SizedBox(width: 8), Expanded( child: Text(isActive ? 'Einstellungen für Trade #${trade.id}' : 'Trade-Ausführung & Parameter', style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), ), ], ), content: SizedBox( width: 580, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Trade-ID: ${trade.id} | Symbol/ISIN: ${trade.symbol.isNotEmpty ? trade.symbol : defaultSymbol}', style: TextStyle(color: AppTheme.textSecondary, fontSize: 12)), const SizedBox(height: 12), Builder( builder: (context) { final signal = trade.signalType.toUpperCase(); final isLong = signal == 'BUY' || signal == 'LONG'; final signalColor = isLong ? AppTheme.primaryEmerald : AppTheme.accentRed; final entryZoneMin = trade.entryZoneMin; final entryZoneMax = trade.entryZoneMax; final entryPrice = trade.entryPrice; final stopLoss = trade.stopLoss; final takeProfit = trade.takeProfit; final takeProfitTargets = trade.takeProfitTargets; final crv = (takeProfit - entryPrice) / (entryPrice - stopLoss).abs(); final maxLeverage = trade.maxLeverage; final reasoning = trade.reasoning; final techRationale = trade.technicalRationale; final fundRationale = trade.fundamentalRationale; final riskWarning = trade.riskWarning; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: signalColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(12), border: Border.all(color: signalColor, width: 1.5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ StatusBadge(label: isLong ? 'LONG / KAUFEN' : 'SHORT / VERKAUFEN', color: signalColor), const SizedBox(width: 8), if (trade.instrumentType.isNotEmpty) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: AppTheme.glassSurface, borderRadius: BorderRadius.circular(6), ), child: Text(trade.instrumentType.toString(), style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold)), ), ], ), Row( children: [ if (trade.winRate > 0) ...[ Icon(Icons.bolt, size: 14, color: AppTheme.accentCyan), Text('Win-Rate: ${trade.winRate}%', style: TextStyle(color: AppTheme.accentCyan, fontWeight: FontWeight.bold, fontSize: 12)), ], ], ), ], ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Haltedauer: ${trade.timeframe.isNotEmpty ? trade.timeframe : '1-14 Tage'}', style: TextStyle(color: AppTheme.textMuted, fontSize: 11)), Text('Risiko: ${trade.riskTolerance}', style: TextStyle(color: AppTheme.textMuted, fontSize: 11)), if (trade.vixValue > 0) Text('VIX: ${_fmt(trade.vixValue)} (${trade.vixRegime})', style: TextStyle(color: Colors.orangeAccent, fontSize: 11, fontWeight: FontWeight.bold)), ], ), const Divider(color: Colors.white12, height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildTradeStat('Einstiegszone', entryZoneMin > 0 && entryZoneMax > 0 ? '€${_fmt(entryZoneMin)} - €${_fmt(entryZoneMax)}' : '€${_fmt(entryPrice)}', Colors.white), _buildTradeStat('Stop-Loss Target', '€${_fmt(stopLoss)}', AppTheme.accentRed), _buildTradeStat('Take-Profit Target', takeProfitTargets.isNotEmpty ? takeProfitTargets.map((t) => '€${_fmt(t)}').join(' / ') : '€${_fmt(takeProfit)}', AppTheme.primaryEmerald), ], ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (crv > 0) _buildTradeStat('Chance-Risiko-Verh. (CRV)', '${_fmt(crv)}x', AppTheme.accentCyan), if (maxLeverage > 0) _buildTradeStat('Empf. Max Hebel', '${_fmt(maxLeverage)}x', Colors.orangeAccent), _buildTradeStat('Signal Typ', isLong ? 'LONG / BULLISH' : 'SHORT / BEARISH', signalColor), ], ), if (reasoning.isNotEmpty || techRationale.isNotEmpty || fundRationale.isNotEmpty || riskWarning.isNotEmpty) ...[ const SizedBox(height: 10), ExpansionTile( tilePadding: EdgeInsets.zero, childrenPadding: EdgeInsets.zero, dense: true, title: Text('Ausführliche KI-Analyse & Begründung anzeigen', style: TextStyle(color: AppTheme.accentCyan, fontWeight: FontWeight.bold, fontSize: 12)), children: [ if (reasoning.isNotEmpty) ...[ _buildRationaleBlock('Gesamt-Strategie & KI-Entscheidung', reasoning, Colors.white70), const SizedBox(height: 6), ], if (techRationale.isNotEmpty) ...[ _buildRationaleBlock('Technische Analyse', techRationale, Colors.white70), const SizedBox(height: 6), ], if (fundRationale.isNotEmpty) ...[ _buildRationaleBlock('Fundamentale Analyse', fundRationale, Colors.white70), const SizedBox(height: 6), ], if (riskWarning.isNotEmpty) _buildRationaleBlock('Risikowarnung', riskWarning, AppTheme.accentRed), ], ), ], ], ), ); }, ), const SizedBox(height: 16), const Text('Ihre Ausführungsdaten für das Depot:', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 10), Row( children: [ Expanded( child: TextField( controller: actualEntryController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Tatsächlicher Einstiegskurs (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: positionSizeController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Investitionsvolumen (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: TextField( controller: leverageController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Genutzter Hebel (x)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: quantityController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Stückzahl (Autom. berechnet)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: TextField( controller: entryFeeController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Einstiegsgebühr (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: exitFeeController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Ausstiegsgebühr (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: TextField( controller: slController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Stop-Loss (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: tpController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Take-Profit (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), ], ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: Text('Abbrechen', style: TextStyle(color: AppTheme.textMuted)), ), if (isActive) ElevatedButton.icon( onPressed: () { final dto = TradeAcceptanceDto( userId: trade.userId, tradeId: trade.id, analysisId: trade.analysisId, isin: trade.isin, symbol: trade.symbol, actualEntryPrice: double.tryParse(actualEntryController.text) ?? trade.entryPrice, positionSize: double.tryParse(positionSizeController.text) ?? 1000.0, leverageUsed: double.tryParse(leverageController.text) ?? 1.0, entryFee: double.tryParse(entryFeeController.text) ?? 0.0, exitFee: double.tryParse(exitFeeController.text) ?? 0.0, quantity: double.tryParse(quantityController.text) ?? 0.0, executionTimestamp: DateTime.now().toUtc(), signalType: trade.signalType, entryPrice: trade.entryPrice, stopLoss: double.tryParse(slController.text) ?? trade.stopLoss, takeProfit: double.tryParse(tpController.text) ?? trade.takeProfit, instrumentType: trade.instrumentType, timeframe: trade.timeframe, reasoning: trade.reasoning, ); onAccept(dto); Navigator.pop(dialogContext); }, icon: const Icon(Icons.save, size: 16), label: const Text('Speichern'), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.primaryEmerald, foregroundColor: Colors.black, ), ) else ...[ if (onReject != null) OutlinedButton.icon( onPressed: () { onReject(trade.id); Navigator.pop(dialogContext); }, icon: Icon(Icons.cancel, color: AppTheme.accentRed, size: 16), label: Text('Trade Ablehnen', style: TextStyle(color: AppTheme.accentRed)), style: OutlinedButton.styleFrom( side: BorderSide(color: AppTheme.accentRed), ), ), const SizedBox(width: 8), ElevatedButton.icon( onPressed: () { final dto = TradeAcceptanceDto( userId: trade.userId, tradeId: trade.id, analysisId: trade.analysisId, isin: trade.isin, symbol: trade.symbol, actualEntryPrice: double.tryParse(actualEntryController.text) ?? trade.entryPrice, positionSize: double.tryParse(positionSizeController.text) ?? 1000.0, leverageUsed: double.tryParse(leverageController.text) ?? 1.0, entryFee: double.tryParse(entryFeeController.text) ?? 0.0, exitFee: double.tryParse(exitFeeController.text) ?? 0.0, quantity: double.tryParse(quantityController.text) ?? 0.0, executionTimestamp: DateTime.now().toUtc(), signalType: trade.signalType, entryPrice: trade.entryPrice, stopLoss: double.tryParse(slController.text) ?? trade.stopLoss, takeProfit: double.tryParse(tpController.text) ?? trade.takeProfit, instrumentType: trade.instrumentType, timeframe: trade.timeframe, reasoning: trade.reasoning, ); onAccept(dto); Navigator.of(dialogContext).pop(); }, icon: const Icon(Icons.check_circle, size: 16), label: const Text('Trade Annehmen & Ausführen'), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.primaryEmerald, foregroundColor: Colors.black, ), ), ], ], ); }, ); } static String _fmt(dynamic val) { if (val == null) return '0.00'; if (val is double) { if (val > 100) return val.toStringAsFixed(1); return val.toStringAsFixed(2); } return val.toString(); } static Widget _buildTradeStat(String label, String value, Color color) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle(color: Colors.white54, fontSize: 11)), const SizedBox(height: 2), Text(value, style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 13)), ], ); } static Widget _buildRationaleBlock(String title, String content, Color color) { return Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text(content, style: const TextStyle(color: Colors.white70, fontSize: 12, height: 1.4)), ], ), ); } }