import 'dart:async';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

import '../application/application.dart';
import '../domain/domain.dart';
import 'example_badge.dart';
import 'library_tags.dart';
import 'theme.dart';

abstract interface class MediaSourcePicker {
  Future<String?> pickPath(MediaKind kind);
}

final class ImagePickerMediaSourcePicker implements MediaSourcePicker {
  const ImagePickerMediaSourcePicker();

  @override
  Future<String?> pickPath(MediaKind kind) async {
    final picker = ImagePicker();
    final file = switch (kind) {
      MediaKind.image => await picker.pickImage(source: ImageSource.gallery),
      MediaKind.video => await picker.pickVideo(source: ImageSource.gallery),
    };
    return file?.path;
  }
}

final class ExerciseLibraryScreen extends StatefulWidget {
  const ExerciseLibraryScreen({
    required this.exerciseUseCases,
    required this.mediaUseCases,
    super.key,
  });

  final ExerciseUseCases exerciseUseCases;
  final MediaUseCases mediaUseCases;

  @override
  State<ExerciseLibraryScreen> createState() => _ExerciseLibraryScreenState();
}

final class _ExerciseLibraryScreenState extends State<ExerciseLibraryScreen> {
  final _searchController = TextEditingController();
  var _selectedMeasures = <WorkoutMeasure>{};
  var _selectedTags = <String>{};
  late Future<List<Exercise>> _exercises;

  @override
  void initState() {
    super.initState();
    _exercises = widget.exerciseUseCases.listActive();
    _searchController.addListener(() => setState(() {}));
  }

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Exercices')),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () => _openForm(),
        icon: const Icon(Icons.add),
        label: const Text('Créer'),
      ),
      body: FutureBuilder<List<Exercise>>(
        future: _exercises,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return _CenteredMessage(
              title: 'Impossible de charger les exercices',
              actionLabel: 'Réessayer',
              onAction: _reload,
            );
          }

          final exercises = snapshot.data ?? const <Exercise>[];
          final tagSuggestions = tagSuggestionsFor(
            exercises,
            (exercise) => exercise.tags,
          ).map((usage) => usage.tag).toList();
          final filtered = _filter(exercises);
          return RefreshIndicator(
            onRefresh: () {
              _reload();
              return Future<void>.value();
            },
            child: CustomScrollView(
              slivers: [
                SliverToBoxAdapter(
                  child: Padding(
                    padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        TextField(
                          controller: _searchController,
                          decoration: const InputDecoration(
                            labelText: 'Rechercher',
                            prefixIcon: Icon(Icons.search),
                          ),
                        ),
                        const SizedBox(height: 12),
                        Wrap(
                          spacing: 8,
                          runSpacing: 8,
                          children: WorkoutMeasure.values.map((measure) {
                            return FilterChip(
                              label: Text(measure.label),
                              selected: _selectedMeasures.contains(measure),
                              onSelected: (selected) {
                                setState(() {
                                  if (selected) {
                                    _selectedMeasures = {
                                      ..._selectedMeasures,
                                      measure,
                                    };
                                  } else {
                                    _selectedMeasures = {..._selectedMeasures}
                                      ..remove(measure);
                                  }
                                });
                              },
                            );
                          }).toList(),
                        ),
                        if (tagSuggestions.isNotEmpty) ...[
                          const SizedBox(height: 12),
                          LibraryTagFilters(
                            suggestions: tagSuggestions,
                            selectedTags: _selectedTags,
                            onToggle: _toggleTag,
                          ),
                        ],
                        if (_filtersActive) ...[
                          const SizedBox(height: 8),
                          TextButton(
                            onPressed: _clearFilters,
                            child: const Text('Effacer les filtres'),
                          ),
                        ],
                      ],
                    ),
                  ),
                ),
                if (exercises.isEmpty)
                  SliverFillRemaining(
                    hasScrollBody: false,
                    child: _CenteredMessage(
                      title: 'Aucun exercice',
                      message:
                          'Crée ton premier exercice ou restaure les exemples.',
                      actionLabel: 'Créer un exercice',
                      onAction: () => _openForm(),
                    ),
                  )
                else if (filtered.isEmpty)
                  SliverFillRemaining(
                    hasScrollBody: false,
                    child: _CenteredMessage(
                      title: 'Aucun exercice ne correspond',
                      message:
                          'Modifie la recherche, les mesures ou les tags sélectionnés.',
                      actionLabel: 'Effacer les filtres',
                      onAction: _clearFilters,
                    ),
                  )
                else
                  SliverList(
                    delegate: SliverChildBuilderDelegate((context, index) {
                      if (index.isOdd) {
                        return const Divider(height: 1, indent: 72);
                      }
                      final exercise = filtered[index ~/ 2];
                      return ExerciseListTile(
                        exercise: exercise,
                        onTap: () => _openForm(exercise: exercise),
                        onDelete: () => _confirmDelete(exercise),
                      );
                    }, childCount: filtered.length * 2 - 1),
                  ),
              ],
            ),
          );
        },
      ),
    );
  }

  List<Exercise> _filter(List<Exercise> exercises) {
    final query = _searchController.text.trim().toLowerCase();
    final matchingQueryAndMeasures = exercises.where((exercise) {
      final matchesQuery =
          query.isEmpty ||
          exercise.name.toLowerCase().contains(query) ||
          (exercise.description?.toLowerCase().contains(query) ?? false);
      final matchesMeasures =
          _selectedMeasures.isEmpty ||
          _selectedMeasures.every(exercise.availableMeasures.contains);
      return matchesQuery && matchesMeasures;
    }).toList();
    return filterByRequiredTags(
      matchingQueryAndMeasures,
      _selectedTags,
      (exercise) => exercise.tags,
    );
  }

  bool get _filtersActive {
    return _searchController.text.trim().isNotEmpty ||
        _selectedMeasures.isNotEmpty ||
        _selectedTags.isNotEmpty;
  }

  void _toggleTag(String tag) {
    setState(() {
      if (_selectedTags.contains(tag)) {
        _selectedTags = {..._selectedTags}..remove(tag);
      } else {
        _selectedTags = {..._selectedTags, tag};
      }
    });
  }

  void _clearFilters() {
    setState(() {
      _searchController.clear();
      _selectedMeasures = {};
      _selectedTags = {};
    });
  }

  void _reload() {
    setState(() {
      _exercises = widget.exerciseUseCases.listActive();
    });
  }

  Future<void> _openForm({Exercise? exercise}) async {
    final exercises = await _exercises;
    if (!mounted) return;
    final changed = await Navigator.of(context).push<bool>(
      MaterialPageRoute(
        builder: (context) => ExerciseFormScreen(
          exerciseUseCases: widget.exerciseUseCases,
          mediaUseCases: widget.mediaUseCases,
          exercise: exercise,
          tagSuggestions: tagSuggestionsFor(
            exercises,
            (exercise) => exercise.tags,
          ).map((usage) => usage.tag).toList(),
        ),
      ),
    );
    if (changed == true) {
      if (!mounted) return;
      _reload();
    }
  }

  Future<void> _confirmDelete(Exercise exercise) async {
    final used = await widget.exerciseUseCases.isUsedInPrograms(
      exercise.metadata.id,
    );
    if (!mounted) return;
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Supprimer ${exercise.name} ?'),
        content: Text(
          used
              ? 'Cet exercice est utilisé dans un ou plusieurs programmes. '
                    'Il sera retiré de la bibliothèque. '
                    'Les programmes existants restent inchangés. Continuer ?'
              : 'Cette action est irréversible.',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(false),
            child: const Text('Annuler'),
          ),
          FilledButton.icon(
            onPressed: () => Navigator.of(context).pop(true),
            icon: const Icon(Icons.delete_outline),
            label: const Text('Supprimer'),
          ),
        ],
      ),
    );
    if (confirmed != true) return;
    try {
      await widget.exerciseUseCases.delete(exercise.metadata.id);
      if (!mounted) return;
      _reload();
    } on Exception catch (error) {
      _showSnackBar(error.toString());
    }
  }

  void _showSnackBar(String message) {
    if (!mounted) return;
    final messenger = ScaffoldMessenger.of(context);
    messenger.hideCurrentSnackBar();
    messenger.showSnackBar(SnackBar(content: Text(message)));
  }
}

final class ExerciseListTile extends StatelessWidget {
  const ExerciseListTile({
    required this.exercise,
    this.onTap,
    this.onDelete,
    super.key,
  });

  final Exercise exercise;
  final VoidCallback? onTap;
  final VoidCallback? onDelete;

  @override
  Widget build(BuildContext context) {
    final description = exercise.description?.trim();
    final iconMediaId = exercise.iconMediaId ?? exercise.imageMediaId;
    final hasMedia = iconMediaId != null || exercise.videoMediaId != null;
    return ListTile(
      onTap: onTap,
      leading: _ExerciseIconAvatar(iconMediaId: iconMediaId),
      title: Wrap(
        spacing: 8,
        runSpacing: 4,
        crossAxisAlignment: WrapCrossAlignment.center,
        children: [
          Text(exercise.name),
          if (exercise.isExample) const ExampleBadge(),
        ],
      ),
      subtitle: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          if (description != null && description.isNotEmpty)
            Padding(
              padding: const EdgeInsets.only(top: 2, bottom: 6),
              child: Text(
                description,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
            )
          else
            const SizedBox(height: 6),
          Wrap(
            spacing: 6,
            runSpacing: 6,
            children: [
              _CategoryBadge(category: exercise.category),
              ...exercise.availableMeasures.map(
                (measure) => MeasureBadge(
                  measure: measure,
                  scoreUnit: exercise.scoreUnit,
                  scoreInputMode: exercise.scoreInputMode,
                ),
              ),
              ...exercise.tags.take(3).map((tag) => TagChip(label: tag)),
              if (exercise.tags.length > 3)
                TagChip(label: '+${exercise.tags.length - 3}'),
              if (hasMedia)
                const Tooltip(
                  message: 'Média associé',
                  child: Icon(Icons.image_outlined, size: 18),
                ),
            ],
          ),
        ],
      ),
      trailing: onDelete == null
          ? const Icon(Icons.chevron_right)
          : Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                IconButton(
                  tooltip: 'Supprimer l’exercice',
                  icon: const Icon(Icons.delete_outline),
                  onPressed: onDelete,
                ),
                const Icon(Icons.chevron_right),
              ],
            ),
    );
  }
}

final class _CategoryBadge extends StatelessWidget {
  const _CategoryBadge({required this.category});

  final ExerciseCategory category;

  @override
  Widget build(BuildContext context) {
    return Chip(
      visualDensity: VisualDensity.compact,
      label: Text(category.label),
    );
  }
}

final class _ExerciseIconAvatar extends StatelessWidget {
  const _ExerciseIconAvatar({required this.iconMediaId});

  final String? iconMediaId;

  @override
  Widget build(BuildContext context) {
    final icon = CircleAvatar(
      child: Icon(
        iconMediaId == null ? Icons.fitness_center : Icons.perm_media_outlined,
      ),
    );
    if (iconMediaId == null) {
      return icon;
    }
    return Tooltip(
      message: 'Icône d’exercice $iconMediaId',
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          icon,
          Positioned(
            right: -2,
            bottom: -2,
            child: Icon(
              Icons.star,
              size: 16,
              color: Theme.of(context).colorScheme.primary,
            ),
          ),
        ],
      ),
    );
  }
}

final class ExerciseFormScreen extends StatefulWidget {
  const ExerciseFormScreen({
    required this.exerciseUseCases,
    required this.mediaUseCases,
    this.mediaPicker = const ImagePickerMediaSourcePicker(),
    this.exercise,
    this.tagSuggestions = const [],
    super.key,
  });

  final ExerciseUseCases exerciseUseCases;
  final MediaUseCases mediaUseCases;
  final MediaSourcePicker mediaPicker;
  final Exercise? exercise;
  final List<String> tagSuggestions;

  @override
  State<ExerciseFormScreen> createState() => _ExerciseFormScreenState();
}

final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
  final _formKey = GlobalKey<FormState>();
  late final TextEditingController _nameController;
  late final TextEditingController _descriptionController;
  late final TextEditingController _scoreLabelController;
  late final TextEditingController _scoreUnitController;
  late final TextEditingController _defaultTimeController;
  late final TextEditingController _defaultRepsController;
  late final TextEditingController _defaultScoreController;
  late final TextEditingController _defaultScoreTimeController;
  late List<String> _imageMediaIds;
  String? _iconMediaId;
  String? _videoMediaId;
  final _selectedImageNamesById = <String, String>{};
  String? _selectedVideoName;
  var _hasTime = true;
  var _hasReps = false;
  var _hasScore = false;
  var _scoreInputMode = ScoreInputMode.manual;
  var _saving = false;
  var _importingImage = false;
  var _importingVideo = false;
  var _stepsEnabled = false;
  var _autoStartNextTimedStep = true;
  final _stepDrafts = <_ExerciseStepDraft>[];
  late List<String> _tags;
  String? _measureError;
  String? _stepError;
  String? _warning;

  bool get _isEditing => widget.exercise != null;

  @override
  void initState() {
    super.initState();
    final exercise = widget.exercise;
    _nameController = TextEditingController(text: exercise?.name);
    _descriptionController = TextEditingController(text: exercise?.description);
    _scoreLabelController = TextEditingController(text: exercise?.scoreLabel);
    _scoreUnitController = TextEditingController(text: exercise?.scoreUnit);
    _defaultTimeController = TextEditingController(
      text: _optionalIntText(exercise?.defaultTargetTimeSeconds),
    );
    _defaultRepsController = TextEditingController(
      text: _optionalIntText(exercise?.defaultTargetReps),
    );
    _defaultScoreController = TextEditingController(
      text: _optionalDoubleText(exercise?.defaultTargetScore),
    );
    _defaultScoreTimeController = TextEditingController(
      text: _optionalDoubleText(
        _millisecondsToSeconds(exercise?.defaultTargetScoreTimeMs),
      ),
    );
    _imageMediaIds = List<String>.of(exercise?.imageMediaIds ?? const []);
    _iconMediaId = exercise?.iconMediaId;
    _videoMediaId = exercise?.videoMediaId;
    _hasTime = exercise?.hasTimeMeasure ?? true;
    _hasReps = exercise?.hasRepsMeasure ?? false;
    _hasScore = exercise?.hasScoreMeasure ?? false;
    _scoreInputMode = exercise?.scoreInputMode ?? ScoreInputMode.manual;
    _stepsEnabled = exercise?.steps.isNotEmpty ?? false;
    _autoStartNextTimedStep = exercise?.autoStartNextTimedStep ?? true;
    _tags = List<String>.of(exercise?.tags ?? const []);
    for (final step in exercise?.steps ?? const <ExerciseStep>[]) {
      _stepDrafts.add(_draftFromStep(step));
    }
  }

  @override
  void dispose() {
    _nameController.dispose();
    _descriptionController.dispose();
    _scoreLabelController.dispose();
    _scoreUnitController.dispose();
    _defaultTimeController.dispose();
    _defaultRepsController.dispose();
    _defaultScoreController.dispose();
    _defaultScoreTimeController.dispose();
    for (final draft in _stepDrafts) {
      draft.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_isEditing ? 'Modifier l’exercice' : 'Créer un exercice'),
      ),
      body: Form(
        key: _formKey,
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            TextFormField(
              controller: _nameController,
              decoration: const InputDecoration(labelText: 'Nom'),
              textInputAction: TextInputAction.next,
              validator: (value) => value == null || value.trim().isEmpty
                  ? 'Le nom est obligatoire.'
                  : null,
            ),
            const SizedBox(height: 12),
            TextFormField(
              controller: _descriptionController,
              decoration: const InputDecoration(labelText: 'Description'),
              minLines: 2,
              maxLines: 4,
            ),
            const SizedBox(height: 24),
            EditableTagsSection(
              tags: _tags,
              suggestions: widget.tagSuggestions,
              description:
                  'Ajoute quelques mots pour retrouver cet exercice plus vite.',
              onChanged: (tags) => setState(() => _tags = tags),
            ),
            const SizedBox(height: 24),
            Text(
              'Média optionnel',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            _ImageGalleryField(
              imageMediaIds: _imageMediaIds,
              imageNamesById: _selectedImageNamesById,
              iconMediaId: _iconMediaId,
              importing: _importingImage,
              onAdd: _importImage,
              onRemove: _removeImage,
              onSetIcon: _setIcon,
            ),
            const SizedBox(height: 8),
            _MediaImportField(
              label: 'Vidéo',
              selectedFileName: _selectedVideoName,
              imported: _videoMediaId != null,
              importing: _importingVideo,
              actionLabel: 'Choisir une vidéo',
              onPick: _importVideo,
            ),
            const SizedBox(height: 24),
            Text(
              'Mesures disponibles',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            _MeasureSwitch(
              title: 'Temps',
              subtitle: 'Chronométrer la durée d’une série.',
              value: _hasTime,
              onChanged: (value) => _setMeasure(() => _hasTime = value),
            ),
            if (_hasTime) ...[
              const SizedBox(height: 8),
              TextFormField(
                controller: _defaultTimeController,
                decoration: const InputDecoration(
                  labelText: 'Temps par défaut (s)',
                ),
                keyboardType: TextInputType.number,
                validator: (value) => _hasTime
                    ? _positiveIntValidator(
                        value,
                        'Saisis un temps supérieur à 0.',
                      )
                    : null,
              ),
            ],
            _MeasureSwitch(
              title: 'Répétitions',
              subtitle: 'Compter le nombre de mouvements réalisés.',
              value: _hasReps,
              onChanged: (value) => _setMeasure(() => _hasReps = value),
            ),
            if (_hasReps) ...[
              const SizedBox(height: 8),
              TextFormField(
                controller: _defaultRepsController,
                decoration: const InputDecoration(
                  labelText: 'Répétitions par défaut',
                ),
                keyboardType: TextInputType.number,
                validator: (value) => _hasReps
                    ? _positiveIntValidator(
                        value,
                        'Saisis un nombre de répétitions supérieur à 0.',
                      )
                    : null,
              ),
            ],
            _MeasureSwitch(
              title: 'Score',
              subtitle: 'Saisir une valeur libre avec son unité.',
              value: _hasScore,
              onChanged: (value) => _setMeasure(() => _hasScore = value),
            ),
            if (_measureError != null)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: Text(
                  _measureError!,
                  style: TextStyle(color: Theme.of(context).colorScheme.error),
                ),
              ),
            if (_hasScore) ...[
              const SizedBox(height: 12),
              Text(
                'Mode de saisie',
                style: Theme.of(context).textTheme.titleSmall,
              ),
              RadioListTile<ScoreInputMode>(
                contentPadding: EdgeInsets.zero,
                title: const Text('Saisie libre'),
                value: ScoreInputMode.manual,
                groupValue: _scoreInputMode,
                onChanged: (value) {
                  if (value == null) return;
                  setState(() => _scoreInputMode = value);
                },
              ),
              RadioListTile<ScoreInputMode>(
                contentPadding: EdgeInsets.zero,
                title: const Text('Chrono intégré'),
                subtitle: const Text('Temps réalisé'),
                value: ScoreInputMode.stopwatch,
                groupValue: _scoreInputMode,
                onChanged: (value) {
                  if (value == null) return;
                  setState(() => _scoreInputMode = value);
                },
              ),
              if (_scoreInputMode == ScoreInputMode.manual) ...[
                const SizedBox(height: 12),
                TextFormField(
                  controller: _scoreLabelController,
                  decoration: const InputDecoration(
                    labelText: 'Score à saisir',
                  ),
                  validator: (value) {
                    if (!_hasScore ||
                        _scoreInputMode != ScoreInputMode.manual) {
                      return null;
                    }
                    return value == null || value.trim().isEmpty
                        ? 'Le libellé du score est obligatoire.'
                        : null;
                  },
                ),
                const SizedBox(height: 12),
                TextFormField(
                  controller: _scoreUnitController,
                  decoration: const InputDecoration(labelText: 'Unité'),
                  validator: (value) {
                    if (!_hasScore ||
                        _scoreInputMode != ScoreInputMode.manual) {
                      return null;
                    }
                    return value == null || value.trim().isEmpty
                        ? 'L’unité du score est obligatoire.'
                        : null;
                  },
                ),
                const SizedBox(height: 12),
                TextFormField(
                  controller: _defaultScoreController,
                  decoration: const InputDecoration(
                    labelText: 'Score par défaut',
                  ),
                  keyboardType: const TextInputType.numberWithOptions(
                    decimal: true,
                  ),
                  validator: (value) {
                    if (!_hasScore ||
                        _scoreInputMode != ScoreInputMode.manual) {
                      return null;
                    }
                    return _nonNegativeDoubleValidator(
                      value,
                      'Saisis un score positif ou nul.',
                    );
                  },
                ),
              ] else ...[
                const SizedBox(height: 12),
                TextFormField(
                  controller: _defaultScoreTimeController,
                  decoration: const InputDecoration(
                    labelText: 'Objectif de chrono par défaut (optionnel)',
                  ),
                  keyboardType: const TextInputType.numberWithOptions(
                    decimal: true,
                  ),
                  validator: (value) {
                    if (!_hasScore ||
                        _scoreInputMode != ScoreInputMode.stopwatch ||
                        value == null ||
                        value.trim().isEmpty) {
                      return null;
                    }
                    return _positiveDoubleValidator(
                      value,
                      'Saisis un objectif supérieur à 0.',
                    );
                  },
                ),
              ],
            ],
            const SizedBox(height: 24),
            _buildExerciseStepsSection(context),
            if (_warning != null) ...[
              const SizedBox(height: 16),
              MaterialBanner(
                content: Text(_warning!),
                leading: const Icon(Icons.info_outline),
                actions: [
                  TextButton(
                    onPressed: () => setState(() => _warning = null),
                    child: const Text('OK'),
                  ),
                ],
              ),
            ],
            const SizedBox(height: 24),
            FilledButton.icon(
              onPressed: _saving ? null : _save,
              icon: _saving
                  ? const SizedBox.square(
                      dimension: 18,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : const Icon(Icons.check),
              label: const Text('Enregistrer'),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildExerciseStepsSection(BuildContext context) {
    final reachedLimit = _stepDrafts.length >= 8;
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Text(
          'Séquence d’étapes',
          style: Theme.of(context).textTheme.titleMedium,
        ),
        const SizedBox(height: 8),
        SwitchListTile(
          contentPadding: EdgeInsets.zero,
          title: const Text('Rythmer cet exercice avec des étapes'),
          subtitle: Text(
            _stepsEnabled
                ? 'Chaque passage suit ces étapes dans l’ordre. Si la série '
                      'suit des répétitions, une répétition correspond à un '
                      'passage complet.'
                : 'Ajoute des étapes si l’exercice doit suivre un ordre précis '
                      'pendant chaque série.',
          ),
          value: _stepsEnabled,
          onChanged: (value) {
            setState(() {
              _stepsEnabled = value;
              _stepError = null;
            });
          },
        ),
        if (_stepsEnabled) ...[
          const SizedBox(height: 8),
          SwitchListTile(
            contentPadding: EdgeInsets.zero,
            title: const Text(
              'Enchaîner automatiquement les chronos consécutifs',
            ),
            subtitle: Text(
              _autoStartNextTimedStep
                  ? 'Le chrono suivant démarre dès que le précédent arrive à 0.'
                  : "L'app attendra ton démarrage avant de lancer le chrono "
                        'suivant.',
            ),
            value: _autoStartNextTimedStep,
            onChanged: (value) {
              setState(() {
                _autoStartNextTimedStep = value;
              });
            },
          ),
          const SizedBox(height: 8),
          ReorderableListView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            buildDefaultDragHandles: false,
            itemCount: _stepDrafts.length,
            onReorder: _reorderStep,
            itemBuilder: (context, index) {
              return _buildStepCard(context, index, _stepDrafts[index]);
            },
          ),
          if (_stepError != null) ...[
            const SizedBox(height: 8),
            Text(
              _stepError!,
              style: TextStyle(color: Theme.of(context).colorScheme.error),
            ),
          ],
          const SizedBox(height: 8),
          if (reachedLimit)
            const ListTile(
              contentPadding: EdgeInsets.zero,
              leading: Icon(Icons.info_outline),
              title: Text('Limite atteinte'),
              subtitle: Text('Un exercice peut contenir jusqu’à 8 étapes.'),
            )
          else
            OutlinedButton.icon(
              onPressed: _addStep,
              icon: const Icon(Icons.add),
              label: const Text('Ajouter une étape'),
            ),
        ],
      ],
    );
  }

  Widget _buildStepCard(
    BuildContext context,
    int index,
    _ExerciseStepDraft draft,
  ) {
    final targetLabel = draft.type == ExerciseStepType.time
        ? 'Durée par défaut (s)'
        : 'Répétitions par défaut';
    final targetMessage = draft.type == ExerciseStepType.time
        ? 'Saisis une durée supérieure à 0.'
        : 'Saisis un nombre de répétitions supérieur à 0.';
    final summaryUnit = draft.type == ExerciseStepType.time ? 's' : '';
    return CourtBlazerAccentPanel(
      key: ValueKey(draft.id),
      margin: const EdgeInsets.only(bottom: 12),
      padding: const EdgeInsets.all(12),
      child: Material(
        type: MaterialType.transparency,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Row(
              children: [
                ReorderableDragStartListener(
                  index: index,
                  child: const Padding(
                    padding: EdgeInsets.only(right: 8),
                    child: Icon(Icons.drag_handle),
                  ),
                ),
                Expanded(
                  child: Text(
                    '${index + 1}. ${draft.displayName}',
                    style: Theme.of(context).textTheme.titleSmall,
                  ),
                ),
                PopupMenuButton<_StepAction>(
                  tooltip: 'Actions de l’étape ${index + 1}',
                  onSelected: (action) => _handleStepAction(index, action),
                  itemBuilder: (context) => [
                    PopupMenuItem(
                      value: _StepAction.moveUp,
                      enabled: index > 0,
                      child: const Text('Monter'),
                    ),
                    PopupMenuItem(
                      value: _StepAction.moveDown,
                      enabled: index < _stepDrafts.length - 1,
                      child: const Text('Descendre'),
                    ),
                    const PopupMenuItem(
                      value: _StepAction.duplicate,
                      child: Text('Dupliquer'),
                    ),
                    const PopupMenuItem(
                      value: _StepAction.delete,
                      child: Text('Supprimer'),
                    ),
                  ],
                ),
              ],
            ),
            const SizedBox(height: 8),
            Text(
              draft.type == ExerciseStepType.time
                  ? 'Temps · ${draft.targetController.text.trim()} $summaryUnit'
                  : 'Répétitions · ${draft.targetController.text.trim()}',
              style: Theme.of(context).textTheme.bodySmall,
            ),
            const SizedBox(height: 12),
            TextFormField(
              controller: draft.nameController,
              decoration: const InputDecoration(labelText: 'Nom de l’étape'),
              validator: (value) {
                if (!_stepsEnabled) return null;
                return value == null || value.trim().isEmpty
                    ? 'Le nom de l’étape est obligatoire.'
                    : null;
              },
              onChanged: (_) => setState(() {}),
            ),
            const SizedBox(height: 8),
            Text('Type d’étape', style: Theme.of(context).textTheme.titleSmall),
            RadioListTile<ExerciseStepType>(
              contentPadding: EdgeInsets.zero,
              title: const Text('Temps'),
              value: ExerciseStepType.time,
              groupValue: draft.type,
              onChanged: (value) {
                if (value == null) return;
                setState(() => draft.type = value);
              },
            ),
            RadioListTile<ExerciseStepType>(
              contentPadding: EdgeInsets.zero,
              title: const Text('Répétitions'),
              value: ExerciseStepType.reps,
              groupValue: draft.type,
              onChanged: (value) {
                if (value == null) return;
                setState(() => draft.type = value);
              },
            ),
            TextFormField(
              controller: draft.targetController,
              decoration: InputDecoration(labelText: targetLabel),
              keyboardType: TextInputType.number,
              validator: (value) => _stepsEnabled
                  ? _positiveIntValidator(value, targetMessage)
                  : null,
              onChanged: (_) => setState(() {}),
            ),
            const SizedBox(height: 8),
            SwitchListTile(
              contentPadding: EdgeInsets.zero,
              title: const Text('Score d’étape'),
              subtitle: const Text('Ajouter un score pour cette étape'),
              value: draft.hasScore,
              onChanged: (value) => setState(() => draft.hasScore = value),
            ),
            if (draft.hasScore) ...[
              Text(
                'Mode de score',
                style: Theme.of(context).textTheme.titleSmall,
              ),
              RadioListTile<ScoreInputMode>(
                contentPadding: EdgeInsets.zero,
                title: const Text('Saisie libre'),
                value: ScoreInputMode.manual,
                groupValue: draft.scoreInputMode,
                onChanged: (value) {
                  if (value == null) return;
                  setState(() => draft.scoreInputMode = value);
                },
              ),
              RadioListTile<ScoreInputMode>(
                contentPadding: EdgeInsets.zero,
                title: const Text('Chrono intégré'),
                value: ScoreInputMode.stopwatch,
                groupValue: draft.scoreInputMode,
                onChanged: (value) {
                  if (value == null) return;
                  setState(() => draft.scoreInputMode = value);
                },
              ),
              if (draft.scoreInputMode == ScoreInputMode.manual) ...[
                TextFormField(
                  controller: draft.scoreLabelController,
                  decoration: const InputDecoration(
                    labelText: 'Score à saisir',
                  ),
                  validator: (value) {
                    if (!_stepsEnabled || !draft.hasScore) return null;
                    if (draft.scoreInputMode != ScoreInputMode.manual) {
                      return null;
                    }
                    return value == null || value.trim().isEmpty
                        ? 'Le libellé du score est obligatoire.'
                        : null;
                  },
                ),
                const SizedBox(height: 8),
                TextFormField(
                  controller: draft.scoreUnitController,
                  decoration: const InputDecoration(labelText: 'Unité'),
                  validator: (value) {
                    if (!_stepsEnabled || !draft.hasScore) return null;
                    if (draft.scoreInputMode != ScoreInputMode.manual) {
                      return null;
                    }
                    return value == null || value.trim().isEmpty
                        ? 'L’unité du score est obligatoire.'
                        : null;
                  },
                ),
                const SizedBox(height: 8),
                TextFormField(
                  controller: draft.scoreTargetController,
                  decoration: const InputDecoration(
                    labelText: 'Score par défaut',
                  ),
                  keyboardType: const TextInputType.numberWithOptions(
                    decimal: true,
                  ),
                  validator: (value) {
                    if (!_stepsEnabled ||
                        !draft.hasScore ||
                        draft.scoreInputMode != ScoreInputMode.manual ||
                        value == null ||
                        value.trim().isEmpty) {
                      return null;
                    }
                    return _nonNegativeDoubleValidator(
                      value,
                      'Saisis un score supérieur ou égal à 0.',
                    );
                  },
                ),
              ] else ...[
                if (draft.type == ExerciseStepType.time) ...[
                  const SizedBox(height: 8),
                  const Text(
                    'Cette étape utilise déjà un compte à rebours. Le score '
                    'chrono ajoute un second temps mesuré ; garde-le seulement '
                    'si tu veux enregistrer une performance distincte.',
                  ),
                ],
                const SizedBox(height: 8),
                TextFormField(
                  controller: draft.scoreTargetController,
                  decoration: const InputDecoration(
                    labelText: 'Objectif de chrono par défaut (optionnel)',
                  ),
                  keyboardType: const TextInputType.numberWithOptions(
                    decimal: true,
                  ),
                  validator: (value) {
                    if (!_stepsEnabled ||
                        !draft.hasScore ||
                        draft.scoreInputMode != ScoreInputMode.stopwatch ||
                        value == null ||
                        value.trim().isEmpty) {
                      return null;
                    }
                    return _positiveDoubleValidator(
                      value,
                      'Saisis un objectif supérieur à 0.',
                    );
                  },
                ),
              ],
            ],
          ],
        ),
      ),
    );
  }

  void _setMeasure(VoidCallback update) {
    setState(() {
      update();
      _measureError = null;
      _warning = null;
    });
  }

  void _addStep() {
    if (_stepDrafts.length >= 8) {
      setState(() {
        _stepError = 'Un exercice peut contenir jusqu’à 8 étapes.';
      });
      return;
    }
    setState(() {
      _stepError = null;
      _stepDrafts.add(
        _ExerciseStepDraft(
          id: widget.exerciseUseCases.ids.newId(),
          name: 'Nouvelle étape',
          defaultTargetValue: '10',
        ),
      );
    });
  }

  void _reorderStep(int oldIndex, int newIndex) {
    setState(() {
      if (newIndex > oldIndex) {
        newIndex -= 1;
      }
      final draft = _stepDrafts.removeAt(oldIndex);
      _stepDrafts.insert(newIndex, draft);
    });
  }

  void _handleStepAction(int index, _StepAction action) {
    switch (action) {
      case _StepAction.moveUp:
        if (index <= 0) return;
        setState(() {
          final draft = _stepDrafts.removeAt(index);
          _stepDrafts.insert(index - 1, draft);
        });
        break;
      case _StepAction.moveDown:
        if (index >= _stepDrafts.length - 1) return;
        setState(() {
          final draft = _stepDrafts.removeAt(index);
          _stepDrafts.insert(index + 1, draft);
        });
        break;
      case _StepAction.duplicate:
        _duplicateStep(index);
        break;
      case _StepAction.delete:
        unawaited(_confirmRemoveStep(index));
        break;
    }
  }

  void _duplicateStep(int index) {
    if (_stepDrafts.length >= 8) {
      setState(() {
        _stepError = 'Un exercice peut contenir jusqu’à 8 étapes.';
      });
      return;
    }
    setState(() {
      _stepError = null;
      _stepDrafts.insert(
        index + 1,
        _stepDrafts[index].duplicate(widget.exerciseUseCases.ids.newId()),
      );
    });
  }

  Future<void> _confirmRemoveStep(int index) async {
    final draft = _stepDrafts[index];
    var confirmed = true;
    if (draft.hasFilledContent) {
      confirmed =
          await showDialog<bool>(
            context: context,
            builder: (context) => AlertDialog(
              title: const Text('Supprimer cette étape ?'),
              content: const Text('Cette étape sera retirée de la séquence.'),
              actions: [
                TextButton(
                  onPressed: () => Navigator.of(context).pop(false),
                  child: const Text('Annuler'),
                ),
                FilledButton(
                  onPressed: () => Navigator.of(context).pop(true),
                  child: const Text('Supprimer'),
                ),
              ],
            ),
          ) ??
          false;
    }
    if (!confirmed || !mounted) return;
    setState(() {
      final removed = _stepDrafts.removeAt(index);
      removed.dispose();
      _stepError = null;
    });
  }

  _ExerciseStepDraft _draftFromStep(ExerciseStep step) {
    return _ExerciseStepDraft(
      id: step.id,
      name: step.name,
      type: step.type,
      defaultTargetValue: step.defaultTargetValue.toString(),
      hasScore: step.hasScore,
      scoreInputMode: step.scoreInputMode,
      scoreLabel: step.scoreLabel,
      scoreUnit: step.scoreUnit,
      scoreTarget: step.scoreInputMode == ScoreInputMode.stopwatch
          ? _optionalDoubleText(
              _millisecondsToSeconds(step.defaultTargetScoreTimeMs),
            )
          : _optionalDoubleText(step.defaultTargetScore),
    );
  }

  List<ExerciseStep> _buildSteps() {
    if (!_stepsEnabled) return const [];
    return [
      for (var index = 0; index < _stepDrafts.length; index++)
        _stepDrafts[index].toExerciseStep(
          position: index,
          defaultTargetScoreTimeMs:
              _stepDrafts[index].scoreInputMode == ScoreInputMode.stopwatch
              ? _optionalSecondsToMilliseconds(
                  _stepDrafts[index].scoreTargetController.text,
                )
              : null,
        ),
    ];
  }

  Future<void> _importImage() async {
    if (_imageMediaIds.length >= 5) {
      _showSnackBar('Maximum 5 images par exercice.');
      return;
    }
    final sourcePath = await widget.mediaPicker.pickPath(MediaKind.image);
    if (sourcePath == null) {
      return;
    }
    final fileName = _fileNameFromPath(sourcePath);
    setState(() => _importingImage = true);
    try {
      final asset = await widget.mediaUseCases.importMedia(
        sourcePath: sourcePath,
        kind: MediaKind.image,
      );
      if (!mounted) return;
      setState(() {
        _imageMediaIds = [..._imageMediaIds, asset.metadata.id];
        _selectedImageNamesById[asset.metadata.id] = fileName;
      });
      _showSnackBar('Image ajoutée.');
    } on Exception catch (error) {
      _showSnackBar(error.toString());
    } finally {
      if (mounted) {
        setState(() => _importingImage = false);
      }
    }
  }

  void _removeImage(String mediaAssetId) {
    setState(() {
      _imageMediaIds = _imageMediaIds
          .where((imageId) => imageId != mediaAssetId)
          .toList();
      if (_iconMediaId == mediaAssetId) {
        _iconMediaId = null;
      }
      _selectedImageNamesById.remove(mediaAssetId);
    });
  }

  void _setIcon(String mediaAssetId) {
    setState(() => _iconMediaId = mediaAssetId);
  }

  Future<void> _importVideo() async {
    final sourcePath = await widget.mediaPicker.pickPath(MediaKind.video);
    if (sourcePath == null) {
      return;
    }
    final fileName = _fileNameFromPath(sourcePath);
    setState(() {
      _importingVideo = true;
      _selectedVideoName = fileName;
    });
    try {
      final asset = await widget.mediaUseCases.importMedia(
        sourcePath: sourcePath,
        kind: MediaKind.video,
      );
      if (!mounted) return;
      setState(() {
        _videoMediaId = asset.metadata.id;
      });
      _showSnackBar('Vidéo importée.');
    } on Exception catch (error) {
      _showSnackBar(error.toString());
    } finally {
      if (mounted) {
        setState(() {
          _importingVideo = false;
        });
      }
    }
  }

  Future<void> _save() async {
    final hasMeasure = _hasTime || _hasReps || _hasScore;
    final hasValidSteps = !_stepsEnabled || _stepDrafts.isNotEmpty;
    setState(() {
      _measureError = hasMeasure
          ? null
          : 'Active au moins une mesure pour enregistrer.';
      _stepError = hasValidSteps
          ? null
          : 'Ajoute au moins une étape ou désactive la séquence.';
    });
    if (!_formKey.currentState!.validate() || !hasMeasure || !hasValidSteps) {
      return;
    }

    final exercise = widget.exercise;
    if (exercise != null &&
        _measuresChanged(exercise) &&
        _warning == null &&
        await widget.exerciseUseCases.isUsedInPrograms(exercise.metadata.id)) {
      if (!mounted) return;
      setState(() {
        _warning =
            'Cet exercice est déjà utilisé dans un programme. '
            'Les programmes existants restent inchangés.';
      });
      return;
    }

    setState(() => _saving = true);
    final scoreInputMode = _hasScore ? _scoreInputMode : ScoreInputMode.manual;
    final scoreLabel = _hasScore
        ? switch (scoreInputMode) {
            ScoreInputMode.manual => _scoreLabelController.text.trim(),
            ScoreInputMode.stopwatch => 'Temps réalisé',
          }
        : null;
    final scoreUnit = _hasScore && scoreInputMode == ScoreInputMode.manual
        ? _scoreUnitController.text.trim()
        : null;
    final defaultTargetTimeSeconds = _hasTime
        ? int.parse(_defaultTimeController.text.trim())
        : null;
    final defaultTargetReps = _hasReps
        ? int.parse(_defaultRepsController.text.trim())
        : null;
    final defaultTargetScore =
        _hasScore && scoreInputMode == ScoreInputMode.manual
        ? double.parse(_defaultScoreController.text.trim())
        : null;
    final defaultTargetScoreTimeMs =
        _hasScore && scoreInputMode == ScoreInputMode.stopwatch
        ? _optionalSecondsToMilliseconds(_defaultScoreTimeController.text)
        : null;
    final steps = _buildSteps();
    try {
      if (exercise == null) {
        await widget.exerciseUseCases.create(
          name: _nameController.text.trim(),
          description: _optionalText(_descriptionController),
          imageMediaIds: _imageMediaIds,
          iconMediaId: _iconMediaId,
          videoMediaId: _videoMediaId,
          hasTimeMeasure: _hasTime,
          hasRepsMeasure: _hasReps,
          hasScoreMeasure: _hasScore,
          scoreInputMode: scoreInputMode,
          scoreLabel: scoreLabel,
          scoreUnit: scoreUnit,
          defaultTargetTimeSeconds: defaultTargetTimeSeconds,
          defaultTargetReps: defaultTargetReps,
          defaultTargetScore: defaultTargetScore,
          defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
          autoStartNextTimedStep: _autoStartNextTimedStep,
          steps: steps,
          tags: _tags,
        );
      } else {
        await widget.exerciseUseCases.update(
          id: exercise.metadata.id,
          name: _nameController.text.trim(),
          description: _optionalText(_descriptionController),
          imageMediaIds: _imageMediaIds,
          iconMediaId: _iconMediaId,
          videoMediaId: _videoMediaId,
          hasTimeMeasure: _hasTime,
          hasRepsMeasure: _hasReps,
          hasScoreMeasure: _hasScore,
          scoreInputMode: scoreInputMode,
          scoreLabel: scoreLabel,
          scoreUnit: scoreUnit,
          defaultTargetTimeSeconds: defaultTargetTimeSeconds,
          defaultTargetReps: defaultTargetReps,
          defaultTargetScore: defaultTargetScore,
          defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
          autoStartNextTimedStep: _autoStartNextTimedStep,
          steps: steps,
          tags: _tags,
        );
      }
      if (mounted) {
        final navigator = Navigator.of(context);
        if (navigator.canPop()) {
          navigator.pop(true);
        }
      }
    } on Exception catch (error) {
      _showSnackBar(error.toString());
    } finally {
      if (mounted) {
        setState(() => _saving = false);
      }
    }
  }

  bool _measuresChanged(Exercise exercise) {
    return exercise.hasTimeMeasure != _hasTime ||
        exercise.hasRepsMeasure != _hasReps ||
        exercise.hasScoreMeasure != _hasScore ||
        exercise.scoreInputMode != _scoreInputMode;
  }

  String? _optionalText(TextEditingController controller) {
    final text = controller.text.trim();
    return text.isEmpty ? null : text;
  }

  String? _positiveIntValidator(String? value, String message) {
    final number = int.tryParse(value?.trim() ?? '');
    if (number == null || number <= 0) {
      return message;
    }
    return null;
  }

  String? _positiveDoubleValidator(String? value, String message) {
    final number = double.tryParse(value?.trim() ?? '');
    if (number == null || number <= 0) {
      return message;
    }
    return null;
  }

  String? _nonNegativeDoubleValidator(String? value, String message) {
    final number = double.tryParse(value?.trim() ?? '');
    if (number == null || number < 0) {
      return message;
    }
    return null;
  }

  String _optionalIntText(int? value) {
    return value == null ? '' : '$value';
  }

  String _optionalDoubleText(double? value) {
    if (value == null) {
      return '';
    }
    if (value == value.roundToDouble()) {
      return '${value.round()}';
    }
    return '$value';
  }

  double? _millisecondsToSeconds(int? milliseconds) {
    return milliseconds == null ? null : milliseconds / 1000;
  }

  int? _optionalSecondsToMilliseconds(String value) {
    final text = value.trim();
    if (text.isEmpty) {
      return null;
    }
    return (double.parse(text) * 1000).round();
  }

  String _fileNameFromPath(String path) {
    final parts = path.split(RegExp(r'[/\\]'));
    return parts.isEmpty ? path : parts.last;
  }

  void _showSnackBar(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(SnackBar(content: Text(message)));
  }
}

enum _StepAction { moveUp, moveDown, duplicate, delete }

final class _ExerciseStepDraft {
  _ExerciseStepDraft({
    required this.id,
    required String name,
    this.type = ExerciseStepType.time,
    required String defaultTargetValue,
    this.hasScore = false,
    this.scoreInputMode = ScoreInputMode.manual,
    String? scoreLabel,
    String? scoreUnit,
    String? scoreTarget,
  }) : nameController = TextEditingController(text: name),
       targetController = TextEditingController(text: defaultTargetValue),
       scoreLabelController = TextEditingController(text: scoreLabel),
       scoreUnitController = TextEditingController(text: scoreUnit),
       scoreTargetController = TextEditingController(text: scoreTarget);

  final String id;
  ExerciseStepType type;
  bool hasScore;
  ScoreInputMode scoreInputMode;
  final TextEditingController nameController;
  final TextEditingController targetController;
  final TextEditingController scoreLabelController;
  final TextEditingController scoreUnitController;
  final TextEditingController scoreTargetController;

  String get displayName {
    final text = nameController.text.trim();
    return text.isEmpty ? 'Étape sans nom' : text;
  }

  bool get hasFilledContent {
    return nameController.text.trim().isNotEmpty ||
        targetController.text.trim().isNotEmpty ||
        hasScore ||
        scoreLabelController.text.trim().isNotEmpty ||
        scoreUnitController.text.trim().isNotEmpty ||
        scoreTargetController.text.trim().isNotEmpty;
  }

  _ExerciseStepDraft duplicate(String id) {
    return _ExerciseStepDraft(
      id: id,
      name: nameController.text,
      type: type,
      defaultTargetValue: targetController.text,
      hasScore: hasScore,
      scoreInputMode: scoreInputMode,
      scoreLabel: scoreLabelController.text,
      scoreUnit: scoreUnitController.text,
      scoreTarget: scoreTargetController.text,
    );
  }

  ExerciseStep toExerciseStep({
    required int position,
    required int? defaultTargetScoreTimeMs,
  }) {
    final scoreTargetText = scoreTargetController.text.trim();
    return ExerciseStep(
      id: id,
      position: position,
      name: nameController.text.trim(),
      type: type,
      defaultTargetValue: int.parse(targetController.text.trim()),
      hasScore: hasScore,
      scoreInputMode: hasScore ? scoreInputMode : ScoreInputMode.manual,
      scoreLabel: hasScore && scoreInputMode == ScoreInputMode.manual
          ? scoreLabelController.text.trim()
          : null,
      scoreUnit: hasScore && scoreInputMode == ScoreInputMode.manual
          ? scoreUnitController.text.trim()
          : null,
      defaultTargetScore:
          hasScore &&
              scoreInputMode == ScoreInputMode.manual &&
              scoreTargetText.isNotEmpty
          ? double.parse(scoreTargetText)
          : null,
      defaultTargetScoreTimeMs:
          hasScore && scoreInputMode == ScoreInputMode.stopwatch
          ? defaultTargetScoreTimeMs
          : null,
    );
  }

  void dispose() {
    nameController.dispose();
    targetController.dispose();
    scoreLabelController.dispose();
    scoreUnitController.dispose();
    scoreTargetController.dispose();
  }
}

final class MeasureBadge extends StatelessWidget {
  const MeasureBadge({
    required this.measure,
    this.scoreInputMode = ScoreInputMode.manual,
    this.scoreUnit,
    super.key,
  });

  final WorkoutMeasure measure;
  final ScoreInputMode scoreInputMode;
  final String? scoreUnit;

  @override
  Widget build(BuildContext context) {
    final unit = scoreUnit?.trim();
    var label = measure.label;
    if (measure == WorkoutMeasure.score &&
        scoreInputMode == ScoreInputMode.stopwatch) {
      label = 'Score chrono';
    } else if (measure == WorkoutMeasure.score &&
        unit != null &&
        unit.isNotEmpty) {
      label = '${measure.label} ($unit)';
    }
    return Semantics(
      label: label,
      excludeSemantics: true,
      child: Chip(visualDensity: VisualDensity.compact, label: Text(label)),
    );
  }
}

final class _MeasureSwitch extends StatelessWidget {
  const _MeasureSwitch({
    required this.title,
    required this.subtitle,
    required this.value,
    required this.onChanged,
  });

  final String title;
  final String subtitle;
  final bool value;
  final ValueChanged<bool> onChanged;

  @override
  Widget build(BuildContext context) {
    return SwitchListTile(
      contentPadding: EdgeInsets.zero,
      title: Text(title),
      subtitle: Text(subtitle),
      value: value,
      onChanged: onChanged,
    );
  }
}

final class _ImageGalleryField extends StatelessWidget {
  const _ImageGalleryField({
    required this.imageMediaIds,
    required this.imageNamesById,
    required this.iconMediaId,
    required this.importing,
    required this.onAdd,
    required this.onRemove,
    required this.onSetIcon,
  });

  final List<String> imageMediaIds;
  final Map<String, String> imageNamesById;
  final String? iconMediaId;
  final bool importing;
  final VoidCallback onAdd;
  final ValueChanged<String> onRemove;
  final ValueChanged<String> onSetIcon;

  @override
  Widget build(BuildContext context) {
    final canAdd = imageMediaIds.length < 5;
    return InputDecorator(
      decoration: const InputDecoration(labelText: 'Images'),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          if (imageMediaIds.isEmpty)
            const Text('Aucune image sélectionnée')
          else
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                for (var index = 0; index < imageMediaIds.length; index++)
                  _ImageThumbnail(
                    mediaAssetId: imageMediaIds[index],
                    selectedAsIcon: imageMediaIds[index] == iconMediaId,
                    label:
                        imageNamesById[imageMediaIds[index]] ??
                        'Image ${index + 1}',
                    onRemove: onRemove,
                    onSetIcon: onSetIcon,
                  ),
              ],
            ),
          const SizedBox(height: 12),
          Align(
            alignment: Alignment.centerLeft,
            child: OutlinedButton.icon(
              onPressed: importing ? null : onAdd,
              icon: importing
                  ? const SizedBox.square(
                      dimension: 16,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : const Icon(Icons.add_photo_alternate_outlined),
              label: const Text('Ajouter une image'),
            ),
          ),
          if (!canAdd) ...[
            const SizedBox(height: 8),
            Text(
              'Maximum 5 images par exercice.',
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ],
        ],
      ),
    );
  }
}

final class _ImageThumbnail extends StatelessWidget {
  const _ImageThumbnail({
    required this.mediaAssetId,
    required this.selectedAsIcon,
    required this.label,
    required this.onRemove,
    required this.onSetIcon,
  });

  final String mediaAssetId;
  final bool selectedAsIcon;
  final String label;
  final ValueChanged<String> onRemove;
  final ValueChanged<String> onSetIcon;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final tokens = courtBlazerTokensOf(context);
    return SizedBox(
      width: 132,
      child: Stack(
        children: [
          Container(
            height: 154,
            padding: const EdgeInsets.all(8),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(6),
              border: Border.all(
                color: selectedAsIcon
                    ? theme.colorScheme.primary
                    : tokens.border,
                width: selectedAsIcon ? 2 : 1,
              ),
              color: theme.colorScheme.surface,
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(
                  Icons.image_outlined,
                  color: selectedAsIcon ? theme.colorScheme.primary : null,
                ),
                const SizedBox(height: 8),
                Text(
                  label,
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                  textAlign: TextAlign.center,
                  style: theme.textTheme.labelSmall,
                ),
                const SizedBox(height: 8),
                IconButton(
                  tooltip: 'Définir comme icône',
                  onPressed: selectedAsIcon
                      ? null
                      : () => onSetIcon(mediaAssetId),
                  icon: const Icon(Icons.star_outline, size: 18),
                ),
              ],
            ),
          ),
          if (selectedAsIcon)
            Positioned(
              top: 4,
              left: 4,
              child: Tooltip(
                message: 'Icône choisie',
                child: Icon(
                  Icons.star,
                  color: theme.colorScheme.primary,
                  size: 18,
                ),
              ),
            ),
          Positioned(
            top: 2,
            right: 2,
            child: IconButton.filledTonal(
              tooltip: 'Supprimer l’image',
              visualDensity: VisualDensity.compact,
              icon: const Icon(Icons.close, size: 18),
              onPressed: () => onRemove(mediaAssetId),
            ),
          ),
        ],
      ),
    );
  }
}

final class _MediaImportField extends StatelessWidget {
  const _MediaImportField({
    required this.label,
    required this.selectedFileName,
    required this.imported,
    required this.importing,
    required this.actionLabel,
    required this.onPick,
  });

  final String label;
  final String? selectedFileName;
  final bool imported;
  final bool importing;
  final String actionLabel;
  final VoidCallback onPick;

  @override
  Widget build(BuildContext context) {
    final fileName = selectedFileName;
    final status =
        fileName ??
        (imported ? 'Média déjà importé' : 'Aucun fichier sélectionné');
    return InputDecorator(
      decoration: InputDecoration(labelText: label),
      child: Row(
        children: [
          Icon(imported ? Icons.check_circle_outline : Icons.perm_media),
          const SizedBox(width: 12),
          Expanded(
            child: Text(status, maxLines: 1, overflow: TextOverflow.ellipsis),
          ),
          const SizedBox(width: 12),
          OutlinedButton.icon(
            onPressed: importing ? null : onPick,
            icon: importing
                ? const SizedBox.square(
                    dimension: 16,
                    child: CircularProgressIndicator(strokeWidth: 2),
                  )
                : const Icon(Icons.upload_file),
            label: Text(actionLabel),
          ),
        ],
      ),
    );
  }
}

final class _CenteredMessage extends StatelessWidget {
  const _CenteredMessage({
    required this.title,
    this.message,
    this.actionLabel,
    this.onAction,
  });

  final String title;
  final String? message;
  final String? actionLabel;
  final VoidCallback? onAction;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              title,
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            if (message != null) ...[
              const SizedBox(height: 8),
              Text(message!, textAlign: TextAlign.center),
            ],
            if (actionLabel != null && onAction != null) ...[
              const SizedBox(height: 16),
              FilledButton(onPressed: onAction, child: Text(actionLabel!)),
            ],
          ],
        ),
      ),
    );
  }
}

extension on WorkoutMeasure {
  String get label {
    return switch (this) {
      WorkoutMeasure.time => 'Temps',
      WorkoutMeasure.reps => 'Répétitions',
      WorkoutMeasure.score => 'Score',
    };
  }
}

extension on ExerciseCategory {
  String get label {
    return switch (this) {
      ExerciseCategory.shoot => 'Tir',
      ExerciseCategory.freeThrows => 'Lancers francs',
      ExerciseCategory.dribble => 'Dribble',
      ExerciseCategory.finishing => 'Finition',
      ExerciseCategory.conditioning => 'Condition physique',
      ExerciseCategory.defense => 'Défense',
      ExerciseCategory.mobility => 'Mobilité',
      ExerciseCategory.uncategorized => 'Non catégorisé',
    };
  }
}
