import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../app/theme.dart'; import '../../shared/api_client.dart'; import '../../shared/models/match.dart'; import '../../shared/widgets/primary_cta.dart'; import '../../shared/widgets/screen_insets.dart'; class StepMatch extends ConsumerStatefulWidget { const StepMatch({ super.key, required this.match, required this.onNext, }); final MatchModel match; final VoidCallback onNext; @override ConsumerState createState() => _StepMatchState(); } class _StepMatchState extends ConsumerState { late final TextEditingController _opponentController; late final TextEditingController _locationController; late final TextEditingController _categoryController; late final TextEditingController _phaseController; late final TextEditingController _rosterController; int _setsToWin = 3; DateTime? _scheduledAt; bool _saving = false; bool _customRules = false; int _pointsPerSet = 25; int _pointsDecidingSet = 15; int _minPointLead = 2; @override void initState() { super.initState(); _opponentController = TextEditingController(text: widget.match.opponentName); _locationController = TextEditingController(text: widget.match.location ?? ''); _categoryController = TextEditingController(text: widget.match.category ?? ''); _phaseController = TextEditingController(text: widget.match.phase ?? ''); _rosterController = TextEditingController( text: widget.match.rosterNumbers.join(', '), ); _setsToWin = widget.match.setsToWin; _scheduledAt = widget.match.scheduledAt; final rules = widget.match.scoringRules; if (rules != null && rules.isNotEmpty) { _customRules = true; _pointsPerSet = rules['points_per_set'] as int? ?? 25; _pointsDecidingSet = rules['points_deciding_set'] as int? ?? 15; _minPointLead = rules['min_point_lead'] as int? ?? 2; } } @override void dispose() { _opponentController.dispose(); _locationController.dispose(); _categoryController.dispose(); _phaseController.dispose(); _rosterController.dispose(); super.dispose(); } List _parseRoster(String raw) { return raw .split(RegExp(r'[,\s]+')) .where((s) => s.isNotEmpty) .map(int.tryParse) .whereType() .toList(); } Future _pickDateTime() async { final date = await showDatePicker( context: context, initialDate: _scheduledAt ?? DateTime.now(), firstDate: DateTime.now().subtract(const Duration(days: 1)), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (date == null || !mounted) return; final time = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(_scheduledAt ?? DateTime.now()), ); if (time == null) return; setState(() { _scheduledAt = DateTime(date.year, date.month, date.day, time.hour, time.minute); }); } Future _save() async { if (_opponentController.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Inserisci il nome avversario')), ); return; } setState(() => _saving = true); try { final client = ref.read(apiClientProvider); await client.updateMatch(widget.match.id, { 'opponent_name': _opponentController.text.trim(), 'location': _locationController.text.trim(), 'scheduled_at': _scheduledAt?.toIso8601String(), 'sets_to_win': _setsToWin, if (_customRules) 'scoring_rules': { 'points_per_set': _pointsPerSet, 'points_deciding_set': _pointsDecidingSet, 'min_point_lead': _minPointLead, }, 'category': _categoryController.text.trim(), 'phase': _phaseController.text.trim(), 'roster_numbers': _parseRoster(_rosterController.text), }); widget.onNext(); } catch (_) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Errore nel salvataggio')), ); } } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { final theme = Theme.of(context); final dateLabel = _scheduledAt != null ? DateFormat('EEE d MMM yyyy ยท HH:mm', 'it_IT').format(_scheduledAt!.toLocal()) : 'Seleziona data e ora'; return SingleChildScrollView( padding: ScreenInsets.contentPadding(context, horizontal: 20, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('Dettagli partita', style: theme.textTheme.titleLarge), const SizedBox(height: 20), _ReadOnlyField(label: 'Squadra casa', value: widget.match.teamName), const SizedBox(height: 12), TextField( controller: _opponentController, decoration: const InputDecoration(labelText: 'Avversario'), ), const SizedBox(height: 12), TextField( controller: _locationController, decoration: const InputDecoration(labelText: 'Luogo'), ), const SizedBox(height: 12), ListTile( contentPadding: EdgeInsets.zero, title: Text('Orario', style: theme.textTheme.bodyMedium), subtitle: Text(dateLabel, style: theme.textTheme.titleMedium), trailing: const Icon(Icons.calendar_today), onTap: _pickDateTime, ), const SizedBox(height: 12), Text('Set da vincere', style: theme.textTheme.bodyMedium), const SizedBox(height: 8), SegmentedButton( segments: const [ ButtonSegment(value: 2, label: Text('2')), ButtonSegment(value: 3, label: Text('3')), ], selected: {_setsToWin}, onSelectionChanged: (s) => setState(() => _setsToWin = s.first), ), const SizedBox(height: 12), SwitchListTile( contentPadding: EdgeInsets.zero, title: const Text('Regole punteggio personalizzate'), subtitle: const Text( 'Per tornei non standard (punti set, tie-break, vantaggio minimo)', ), value: _customRules, onChanged: (v) => setState(() => _customRules = v), ), if (_customRules) ...[ const SizedBox(height: 8), Text('Punti per vincere un set', style: theme.textTheme.bodyMedium), const SizedBox(height: 6), SegmentedButton( segments: const [ ButtonSegment(value: 21, label: Text('21')), ButtonSegment(value: 25, label: Text('25')), ButtonSegment(value: 30, label: Text('30')), ], selected: {_pointsPerSet}, onSelectionChanged: (s) => setState(() => _pointsPerSet = s.first), ), const SizedBox(height: 10), Text('Punti set decisivo', style: theme.textTheme.bodyMedium), const SizedBox(height: 6), SegmentedButton( segments: const [ ButtonSegment(value: 15, label: Text('15')), ButtonSegment(value: 21, label: Text('21')), ], selected: {_pointsDecidingSet}, onSelectionChanged: (s) => setState(() => _pointsDecidingSet = s.first), ), const SizedBox(height: 10), Text('Vantaggio minimo', style: theme.textTheme.bodyMedium), const SizedBox(height: 6), SegmentedButton( segments: const [ ButtonSegment(value: 1, label: Text('1')), ButtonSegment(value: 2, label: Text('2')), ], selected: {_minPointLead}, onSelectionChanged: (s) => setState(() => _minPointLead = s.first), ), ], const SizedBox(height: 12), TextField( controller: _categoryController, decoration: const InputDecoration(labelText: 'Categoria'), ), const SizedBox(height: 12), TextField( controller: _phaseController, decoration: const InputDecoration(labelText: 'Fase (es. semifinale)'), ), const SizedBox(height: 12), TextField( controller: _rosterController, decoration: const InputDecoration( labelText: 'Convocate (numeri maglia)', hintText: '1, 5, 7, 12', ), keyboardType: TextInputType.number, ), const SizedBox(height: 32), PrimaryCta( label: 'AVANTI >', loading: _saving, onPressed: _save, icon: Icons.arrow_forward, ), ], ), ); } } class _ReadOnlyField extends StatelessWidget { const _ReadOnlyField({required this.label, required this.value}); final String label; final String value; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppTheme.surfaceElevated, borderRadius: BorderRadius.circular(10), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: theme.textTheme.bodyMedium), const SizedBox(height: 4), Text(value, style: theme.textTheme.titleMedium), ], ), ); } }