import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:trainhub_flutter/core/constants/ui_constants.dart'; import 'package:trainhub_flutter/core/theme/app_colors.dart'; import 'package:trainhub_flutter/domain/entities/exercise.dart'; import 'package:trainhub_flutter/domain/entities/training_plan.dart'; import 'package:trainhub_flutter/domain/entities/training_section.dart'; import 'package:trainhub_flutter/presentation/plan_editor/models/exercise_drag_data.dart'; import 'package:trainhub_flutter/presentation/plan_editor/plan_editor_controller.dart'; import 'package:trainhub_flutter/presentation/plan_editor/widgets/plan_exercise_tile.dart'; class PlanSectionCard extends ConsumerStatefulWidget { final TrainingSectionEntity section; final int sectionIndex; final TrainingPlanEntity plan; final List availableExercises; const PlanSectionCard({ super.key, required this.section, required this.sectionIndex, required this.plan, required this.availableExercises, }); @override ConsumerState createState() => _PlanSectionCardState(); } class _PlanSectionCardState extends ConsumerState { bool _isDragOver = false; bool _isHovered = false; @override Widget build(BuildContext context) { final controller = ref.read( planEditorControllerProvider(widget.plan.id).notifier, ); return DragTarget( onWillAcceptWithDetails: (details) => details.data.fromSectionIndex != widget.sectionIndex, onAcceptWithDetails: (details) { controller.moveExerciseBetweenSections( fromSectionIndex: details.data.fromSectionIndex, exerciseIndex: details.data.exerciseIndex, toSectionIndex: widget.sectionIndex, ); setState(() => _isDragOver = false); }, onLeave: (_) => setState(() => _isDragOver = false), onMove: (_) => setState(() => _isDragOver = true), builder: (context, candidateData, rejectedData) { return MouseRegion( onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), child: AnimatedContainer( duration: UIConstants.animationDuration, margin: const EdgeInsets.only(bottom: UIConstants.spacing16), decoration: BoxDecoration( color: AppColors.surfaceContainer, borderRadius: BorderRadius.circular(12), border: Border.all( color: _isDragOver ? AppColors.accent : AppColors.border, width: _isDragOver ? 2 : 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // --- Section header --- Padding( padding: const EdgeInsets.fromLTRB( UIConstants.spacing16, UIConstants.spacing12, UIConstants.spacing8, UIConstants.spacing12, ), child: Row( children: [ const Icon( Icons.drag_handle, color: AppColors.zinc600, size: 18, ), const SizedBox(width: UIConstants.spacing8), Expanded( child: TextField( controller: TextEditingController( text: widget.section.name, )..selection = TextSelection.fromPosition( TextPosition( offset: widget.section.name.length, ), ), decoration: const InputDecoration( border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, filled: false, hintText: 'Section name', isDense: true, contentPadding: EdgeInsets.zero, ), style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), onChanged: (val) => controller.updateSectionName( widget.sectionIndex, val, ), ), ), // Exercise count badge if (widget.section.exercises.isNotEmpty) Container( margin: const EdgeInsets.only( right: UIConstants.spacing8, ), padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: AppColors.zinc800, borderRadius: BorderRadius.circular(10), ), child: Text( '${widget.section.exercises.length}', style: GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w500, color: AppColors.textMuted, ), ), ), // Delete button — shows on hover AnimatedOpacity( opacity: _isHovered ? 1.0 : 0.0, duration: UIConstants.animationDuration, child: Material( color: AppColors.destructive.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), child: InkWell( onTap: () => controller.deleteSection(widget.sectionIndex), borderRadius: BorderRadius.circular(6), child: const Padding( padding: EdgeInsets.all(6), child: Icon( Icons.delete_outline, color: AppColors.destructive, size: 15, ), ), ), ), ), const SizedBox(width: UIConstants.spacing4), ], ), ), const Divider(height: 1), // --- Drop zone indicator --- if (_isDragOver) Container( height: 48, margin: const EdgeInsets.symmetric( horizontal: UIConstants.spacing12, vertical: UIConstants.spacing8, ), decoration: BoxDecoration( color: AppColors.accentMuted, borderRadius: BorderRadius.circular(8), border: Border.all( color: AppColors.accent.withValues(alpha: 0.5), ), ), child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.add_circle_outline, color: AppColors.accent, size: 15, ), const SizedBox(width: 6), Text( 'Drop exercise here', style: GoogleFonts.inter( color: AppColors.accent, fontSize: 13, fontWeight: FontWeight.w500, ), ), ], ), ), ), // --- Exercise list --- if (widget.section.exercises.isNotEmpty) Padding( padding: const EdgeInsets.symmetric( horizontal: UIConstants.spacing12, vertical: UIConstants.spacing8, ), child: ReorderableListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: widget.section.exercises.length, onReorder: (oldIndex, newIndex) => controller.reorderExercise( widget.sectionIndex, oldIndex, newIndex, ), itemBuilder: (context, index) { final exercise = widget.section.exercises[index]; return PlanExerciseTile( key: ValueKey(exercise.instanceId), exercise: exercise, sectionIndex: widget.sectionIndex, exerciseIndex: index, plan: widget.plan, ); }, ), ), // --- Bottom actions --- Padding( padding: const EdgeInsets.fromLTRB( UIConstants.spacing12, UIConstants.spacing4, UIConstants.spacing12, UIConstants.spacing12, ), child: Row( children: [ _ActionButton( icon: Icons.add, label: 'Add Exercise', onTap: () => _showExercisePicker(context, (exercise) { controller.addExerciseToSection( widget.sectionIndex, exercise, ); }), ), const SizedBox(width: UIConstants.spacing8), _ActionButton( icon: Icons.create_outlined, label: 'Create New', onTap: () => _showCreateExerciseDialog(context, controller), ), ], ), ), ], ), ), ); }, ); } void _showExercisePicker( BuildContext context, Function(ExerciseEntity) onSelect, ) { showModalBottomSheet( context: context, isScrollControlled: true, useSafeArea: true, builder: (context) => DraggableScrollableSheet( expand: false, initialChildSize: 0.7, minChildSize: 0.5, maxChildSize: 0.95, builder: (context, scrollController) { return Column( children: [ Container( padding: const EdgeInsets.fromLTRB(24, 20, 24, 12), decoration: const BoxDecoration( border: Border(bottom: BorderSide(color: AppColors.border)), ), child: Row( children: [ Text( 'Select Exercise', style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const Spacer(), Text( '${widget.availableExercises.length} available', style: GoogleFonts.inter( fontSize: 12, color: AppColors.textMuted, ), ), ], ), ), Expanded( child: ListView.builder( controller: scrollController, padding: const EdgeInsets.symmetric( vertical: UIConstants.spacing8, ), itemCount: widget.availableExercises.length, itemBuilder: (context, index) { final exercise = widget.availableExercises[index]; return ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: UIConstants.spacing24, vertical: 4, ), leading: Container( width: 36, height: 36, decoration: BoxDecoration( color: AppColors.zinc800, borderRadius: BorderRadius.circular(8), ), child: const Icon( Icons.fitness_center, size: 16, color: AppColors.textMuted, ), ), title: Text( exercise.name, style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.textPrimary, ), ), subtitle: exercise.muscleGroup != null && exercise.muscleGroup!.isNotEmpty ? Text( exercise.muscleGroup!, style: GoogleFonts.inter( fontSize: 12, color: AppColors.textMuted, ), ) : null, onTap: () { onSelect(exercise); Navigator.pop(context); }, ); }, ), ), ], ); }, ), ); } void _showCreateExerciseDialog( BuildContext context, PlanEditorController controller, ) { final nameCtrl = TextEditingController(); final instructionsCtrl = TextEditingController(); final tagsCtrl = TextEditingController(); showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Create New Exercise'), content: SizedBox( width: UIConstants.dialogWidth, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: nameCtrl, decoration: const InputDecoration(labelText: 'Name *'), autofocus: true, ), const SizedBox(height: UIConstants.spacing12), TextField( controller: instructionsCtrl, decoration: const InputDecoration(labelText: 'Instructions'), maxLines: 2, ), const SizedBox(height: UIConstants.spacing12), TextField( controller: tagsCtrl, decoration: const InputDecoration( labelText: 'Tags (comma separated)', ), ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: const Text('Cancel'), ), FilledButton( onPressed: () async { if (nameCtrl.text.isEmpty) return; Navigator.pop(dialogContext); final exercise = await controller.createExercise( name: nameCtrl.text, instructions: instructionsCtrl.text.isEmpty ? null : instructionsCtrl.text, tags: tagsCtrl.text.isEmpty ? null : tagsCtrl.text, ); controller.addExerciseToSection(widget.sectionIndex, exercise); }, child: const Text('Create & Add'), ), ], ), ); } } // --------------------------------------------------------------------------- // Small action button (Add Exercise / Create New) // --------------------------------------------------------------------------- class _ActionButton extends StatefulWidget { final IconData icon; final String label; final VoidCallback onTap; const _ActionButton({ required this.icon, required this.label, required this.onTap, }); @override State<_ActionButton> createState() => _ActionButtonState(); } class _ActionButtonState extends State<_ActionButton> { bool _isHovered = false; @override Widget build(BuildContext context) { return MouseRegion( onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), child: GestureDetector( onTap: widget.onTap, child: AnimatedContainer( duration: UIConstants.animationDuration, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _isHovered ? AppColors.zinc800 : Colors.transparent, borderRadius: BorderRadius.circular(6), border: Border.all( color: _isHovered ? AppColors.zinc600 : AppColors.border, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( widget.icon, size: 13, color: _isHovered ? AppColors.textSecondary : AppColors.textMuted, ), const SizedBox(width: 5), Text( widget.label, style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: _isHovered ? AppColors.textSecondary : AppColors.textMuted, ), ), ], ), ), ), ); } }