import 'dart:async';

import 'package:flutter/material.dart';

import '../application/application.dart';
import '../domain/domain.dart';
import 'example_badge.dart';
import 'exercise_library_screen.dart';
import 'library_tags.dart';
import 'share_screen.dart';

final class ProgramListScreen extends StatefulWidget {
  const ProgramListScreen({
    required this.programUseCases,
    required this.exerciseUseCases,
    this.shareUseCases,
    this.authUseCases,
    this.syncUseCases,
    super.key,
  });

  final ProgramUseCases programUseCases;
  final ExerciseUseCases exerciseUseCases;
  final ShareUseCases? shareUseCases;
  final AuthUseCases? authUseCases;
  final SyncUseCases? syncUseCases;

  @override
  State<ProgramListScreen> createState() => _ProgramListScreenState();
}

final class _ProgramListScreenState extends State<ProgramListScreen> {
  final _searchController = TextEditingController();
  var _selectedTags = <String>{};
  late Future<List<Program>> _programs;

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Programmes')),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () => _openForm(),
        icon: const Icon(Icons.add),
        label: const Text('Créer'),
      ),
      body: FutureBuilder<List<Program>>(
        future: _programs,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return _CenteredMessage(
              title: 'Impossible de charger les programmes',
              actionLabel: 'Réessayer',
              onAction: _reload,
            );
          }
          final programs = snapshot.data ?? const <Program>[];
          final tagSuggestions = tagSuggestionsFor(
            programs,
            (program) => program.tags,
          ).map((usage) => usage.tag).toList();
          if (programs.isEmpty) {
            return _CenteredMessage(
              title: 'Aucun programme',
              message: 'Assemble quelques exercices pour préparer une séance.',
              actionLabel: 'Créer un programme',
              onAction: () => _openForm(),
            );
          }
          final filtered = _filter(programs);
          return ListView.separated(
            itemCount: filtered.isEmpty ? 2 : filtered.length + 1,
            separatorBuilder: (context, index) => const Divider(height: 1),
            itemBuilder: (context, index) {
              if (index == 0) {
                return _ProgramFilters(
                  controller: _searchController,
                  tagSuggestions: tagSuggestions,
                  selectedTags: _selectedTags,
                  filtersActive: _filtersActive,
                  onToggleTag: _toggleTag,
                  onClearFilters: _clearFilters,
                );
              }
              if (filtered.isEmpty) {
                return _CenteredMessage(
                  title: 'Aucun programme ne correspond',
                  message: 'Modifie la recherche ou les tags sélectionnés.',
                  actionLabel: 'Effacer les filtres',
                  onAction: _clearFilters,
                );
              }
              final program = filtered[index - 1];
              return ListTile(
                title: Wrap(
                  spacing: 8,
                  runSpacing: 4,
                  crossAxisAlignment: WrapCrossAlignment.center,
                  children: [
                    Text(program.name),
                    if (program.isExample) const ExampleBadge(),
                  ],
                ),
                subtitle: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(_programSummary(program)),
                    if (program.tags.isNotEmpty) ...[
                      const SizedBox(height: 6),
                      TagBadgeWrap(tags: program.tags),
                    ],
                  ],
                ),
                trailing: Wrap(
                  spacing: 8,
                  children: [
                    PopupMenuButton<_ProgramItemAction>(
                      tooltip: 'Actions',
                      onSelected: (action) {
                        switch (action) {
                          case _ProgramItemAction.duplicate:
                            unawaited(_duplicate(program));
                          case _ProgramItemAction.delete:
                            unawaited(_confirmDelete(program));
                        }
                      },
                      itemBuilder: (context) => const [
                        PopupMenuItem(
                          value: _ProgramItemAction.duplicate,
                          child: Text('Dupliquer'),
                        ),
                        PopupMenuItem(
                          value: _ProgramItemAction.delete,
                          child: Text('Supprimer'),
                        ),
                      ],
                    ),
                    const Icon(Icons.chevron_right),
                  ],
                ),
                onTap: () => _openForm(program: program),
              );
            },
          );
        },
      ),
    );
  }

  void _reload() {
    setState(() {
      _programs = widget.programUseCases.listActive();
    });
  }

  List<Program> _filter(List<Program> programs) {
    final query = _searchController.text.trim().toLowerCase();
    final matchingQuery = programs.where((program) {
      return query.isEmpty ||
          program.name.toLowerCase().contains(query) ||
          _programSummary(program).toLowerCase().contains(query);
    }).toList();
    return filterByRequiredTags(
      matchingQuery,
      _selectedTags,
      (program) => program.tags,
    );
  }

  bool get _filtersActive {
    return _searchController.text.trim().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();
      _selectedTags = {};
    });
  }

  Future<void> _openForm({Program? program, String? initialSnackBar}) async {
    final programs = await _programs;
    if (!mounted) return;
    final changed = await Navigator.of(context).push<bool>(
      MaterialPageRoute(
        builder: (context) => ProgramFormScreen(
          programUseCases: widget.programUseCases,
          exerciseUseCases: widget.exerciseUseCases,
          shareUseCases: widget.shareUseCases,
          authUseCases: widget.authUseCases,
          syncUseCases: widget.syncUseCases,
          program: program,
          tagSuggestions: tagSuggestionsFor(
            programs,
            (program) => program.tags,
          ).map((usage) => usage.tag).toList(),
          initialSnackBar: initialSnackBar,
        ),
      ),
    );
    if (changed == true) {
      if (!mounted) return;
      _reload();
    }
  }

  Future<void> _duplicate(Program program) async {
    try {
      final copy = await widget.programUseCases.duplicate(program.metadata.id);
      if (!mounted) return;
      await _openForm(
        program: copy,
        initialSnackBar:
            "Programme dupliqué. Ajuste la copie avant de l'utiliser.",
      );
      if (!mounted) return;
      _reload();
    } on Exception {
      _showSnackBar('Duplication impossible pour le moment.');
    }
  }

  Future<void> _confirmDelete(Program program) async {
    final used = await widget.programUseCases.isUsedInWorkoutTemplates(
      program.metadata.id,
    );
    if (!mounted) return;
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Supprimer ${program.name} ?'),
        content: Text(
          used
              ? 'Ce programme est utilisé dans une ou plusieurs séances. '
                    'Le supprimer le retirera de ces séances. 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.programUseCases.delete(program.metadata.id);
      if (!mounted) return;
      _reload();
    } on Exception catch (error) {
      _showSnackBar(error.toString());
    }
  }

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

enum _ProgramItemAction { duplicate, delete }

final class _ProgramFilters extends StatelessWidget {
  const _ProgramFilters({
    required this.controller,
    required this.tagSuggestions,
    required this.selectedTags,
    required this.filtersActive,
    required this.onToggleTag,
    required this.onClearFilters,
  });

  final TextEditingController controller;
  final List<String> tagSuggestions;
  final Set<String> selectedTags;
  final bool filtersActive;
  final ValueChanged<String> onToggleTag;
  final VoidCallback onClearFilters;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextField(
            controller: controller,
            decoration: const InputDecoration(
              labelText: 'Rechercher',
              prefixIcon: Icon(Icons.search),
            ),
          ),
          if (tagSuggestions.isNotEmpty) ...[
            const SizedBox(height: 12),
            LibraryTagFilters(
              suggestions: tagSuggestions,
              selectedTags: selectedTags,
              onToggle: onToggleTag,
            ),
          ],
          if (filtersActive) ...[
            const SizedBox(height: 8),
            Align(
              alignment: Alignment.centerLeft,
              child: TextButton(
                onPressed: onClearFilters,
                child: const Text('Effacer les filtres'),
              ),
            ),
          ],
        ],
      ),
    );
  }
}

final class ProgramFormScreen extends StatefulWidget {
  const ProgramFormScreen({
    required this.programUseCases,
    required this.exerciseUseCases,
    this.shareUseCases,
    this.authUseCases,
    this.syncUseCases,
    this.program,
    this.tagSuggestions = const [],
    this.initialSnackBar,
    super.key,
  });

  final ProgramUseCases programUseCases;
  final ExerciseUseCases exerciseUseCases;
  final ShareUseCases? shareUseCases;
  final AuthUseCases? authUseCases;
  final SyncUseCases? syncUseCases;
  final Program? program;
  final List<String> tagSuggestions;
  final String? initialSnackBar;

  @override
  State<ProgramFormScreen> createState() => _ProgramFormScreenState();
}

final class _ProgramFormScreenState extends State<ProgramFormScreen> {
  final _formKey = GlobalKey<FormState>();
  late final TextEditingController _nameController;
  late final TextEditingController _defaultRestController;
  late final List<_ProgramExerciseDraft> _exercises;
  late List<String> _tags;
  Timer? _addedSnackBarTimer;
  var _saving = false;

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

  int get _defaultRestSeconds {
    return int.tryParse(_defaultRestController.text.trim()) ?? 0;
  }

  @override
  void initState() {
    super.initState();
    final program = widget.program;
    _nameController = TextEditingController(text: program?.name);
    _defaultRestController = TextEditingController(
      text: '${program?.defaultRestSeconds ?? 60}',
    );
    _exercises = [
      for (final exercise in program?.exercises ?? const <ProgramExercise>[])
        _ProgramExerciseDraft.fromProgramExercise(exercise),
    ];
    _tags = List<String>.of(program?.tags ?? const []);
    final initialSnackBar = widget.initialSnackBar;
    if (initialSnackBar != null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) _showSnackBar(initialSnackBar);
      });
    }
  }

  @override
  void dispose() {
    _addedSnackBarTimer?.cancel();
    _nameController.dispose();
    _defaultRestController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          _isEditing ? 'Modifier le programme' : 'Créer un programme',
        ),
        actions: [
          if (_isEditing && widget.shareUseCases != null)
            IconButton(
              tooltip: 'Partager',
              icon: const Icon(Icons.ios_share),
              onPressed: _shareProgram,
            ),
        ],
      ),
      bottomNavigationBar: SafeArea(
        minimum: const EdgeInsets.all(16),
        child: 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'),
        ),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Form(
              key: _formKey,
              child: Column(
                children: [
                  TextFormField(
                    controller: _nameController,
                    decoration: const InputDecoration(labelText: 'Nom'),
                    validator: (value) => value == null || value.trim().isEmpty
                        ? 'Le nom est obligatoire.'
                        : null,
                  ),
                  const SizedBox(height: 12),
                  TextFormField(
                    controller: _defaultRestController,
                    decoration: const InputDecoration(
                      labelText: 'Repos par défaut (s)',
                    ),
                    keyboardType: TextInputType.number,
                    validator: _nonNegativeIntValidator,
                    onChanged: (_) => setState(() {}),
                  ),
                  const SizedBox(height: 12),
                  EditableTagsSection(
                    tags: _tags,
                    suggestions: widget.tagSuggestions,
                    onChanged: (tags) => setState(() => _tags = tags),
                  ),
                  const SizedBox(height: 12),
                  SizedBox(
                    width: double.infinity,
                    child: OutlinedButton.icon(
                      onPressed: _addExercise,
                      icon: const Icon(Icons.add),
                      label: const Text('Ajouter un exercice'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          Expanded(
            child: _exercises.isEmpty
                ? const _CenteredMessage(
                    title: 'Aucun exercice ajouté',
                    message: 'Ajoute un exercice depuis la bibliothèque.',
                  )
                : ReorderableListView.builder(
                    buildDefaultDragHandles: false,
                    padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
                    itemCount: _exercises.length,
                    onReorderItem: _reorderExercise,
                    itemBuilder: (context, index) {
                      final exercise = _exercises[index];
                      return _ProgramExerciseCard(
                        key: ValueKey(exercise.key),
                        draft: exercise,
                        defaultRestSeconds: _defaultRestSeconds,
                        onCustomize: () => _customizeExercise(exercise.key),
                        index: index,
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }

  Future<void> _addExercise() async {
    final exercise = await Navigator.of(context).push<Exercise>(
      MaterialPageRoute(
        builder: (context) =>
            ExerciseSelectionScreen(exerciseUseCases: widget.exerciseUseCases),
      ),
    );
    if (exercise == null) return;
    if (!mounted) return;
    final draft = _ProgramExerciseDraft.fromExercise(
      exercise,
      restSecondsOverride: _defaultRestSeconds,
    );
    setState(() {
      _exercises.add(draft);
    });
    final messenger = ScaffoldMessenger.of(context);
    _addedSnackBarTimer?.cancel();
    _addedSnackBarTimer = Timer(const Duration(seconds: 3), () {
      if (mounted) {
        messenger.hideCurrentSnackBar();
      }
    });
    messenger.showSnackBar(
      SnackBar(
        duration: const Duration(seconds: 3),
        content: const Text('Exercice ajouté'),
        action: SnackBarAction(
          label: 'Personnaliser',
          onPressed: () {
            _addedSnackBarTimer?.cancel();
            _addedSnackBarTimer = null;
            messenger.hideCurrentSnackBar();
            _customizeExercise(draft.key);
          },
        ),
      ),
    );
  }

  Future<void> _customizeExercise(String key) async {
    final index = _exercises.indexWhere((exercise) => exercise.key == key);
    if (index == -1) return;
    final result = await Navigator.of(context).push<_ProgramExerciseEditResult>(
      MaterialPageRoute(
        builder: (context) => _ProgramExerciseCustomizationScreen(
          draft: _exercises[index],
          defaultRestSeconds: _defaultRestSeconds,
        ),
      ),
    );
    if (result == null || !mounted) return;
    setState(() {
      final currentIndex = _exercises.indexWhere(
        (exercise) => exercise.key == key,
      );
      if (currentIndex == -1) return;
      if (result.deleted) {
        _exercises.removeAt(currentIndex);
      } else if (result.draft != null) {
        _exercises[currentIndex] = result.draft!;
      }
    });
  }

  void _reorderExercise(int oldIndex, int newIndex) {
    setState(() {
      if (newIndex > oldIndex) {
        newIndex -= 1;
      }
      final item = _exercises.removeAt(oldIndex);
      _exercises.insert(newIndex, item);
    });
  }

  Future<void> _save() async {
    if (!_formKey.currentState!.validate()) {
      return;
    }
    setState(() => _saving = true);
    try {
      await widget.programUseCases.saveConfigured(
        id: widget.program?.metadata.id,
        name: _nameController.text.trim(),
        defaultRestSeconds: _defaultRestSeconds,
        tags: _tags,
        exercises: _exercises.map((exercise) => exercise.toConfig()).toList(),
      );
      if (mounted) {
        Navigator.of(context).pop(true);
      }
    } on Exception catch (error) {
      _showSnackBar(error.toString());
    } finally {
      if (mounted) {
        setState(() => _saving = false);
      }
    }
  }

  Future<void> _shareProgram() async {
    final program = widget.program;
    final shareUseCases = widget.shareUseCases;
    if (program == null || shareUseCases == null) return;
    await Navigator.of(context).push<void>(
      MaterialPageRoute(
        builder: (context) => ShareFormScreen(
          shareUseCases: shareUseCases,
          authUseCases: widget.authUseCases,
          syncUseCases: widget.syncUseCases,
          resourceType: ShareResourceType.program,
          localResourceId: program.metadata.id,
          resourceName: program.name,
          resourceSummary: _programSummary(program),
        ),
      ),
    );
  }

  String? _nonNegativeIntValidator(String? value) {
    final number = int.tryParse(value?.trim() ?? '');
    if (number == null || number < 0) {
      return 'Saisis un nombre positif.';
    }
    return null;
  }

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

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

  final ExerciseUseCases exerciseUseCases;

  @override
  State<ExerciseSelectionScreen> createState() =>
      _ExerciseSelectionScreenState();
}

final class _ExerciseSelectionScreenState
    extends State<ExerciseSelectionScreen> {
  final _searchController = TextEditingController();
  var _selectedMeasures = <WorkoutMeasure>{};
  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('Ajouter un exercice')),
      body: FutureBuilder<List<Exercise>>(
        future: _exercises,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          final exercises = _filter(snapshot.data ?? const <Exercise>[]);
          return ListView(
            children: [
              Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  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(_measureLabel(measure)),
                          selected: _selectedMeasures.contains(measure),
                          onSelected: (selected) {
                            setState(() {
                              if (selected) {
                                _selectedMeasures = {
                                  ..._selectedMeasures,
                                  measure,
                                };
                              } else {
                                _selectedMeasures = {..._selectedMeasures}
                                  ..remove(measure);
                              }
                            });
                          },
                        );
                      }).toList(),
                    ),
                  ],
                ),
              ),
              if (exercises.isEmpty)
                const _CenteredMessage(
                  title: 'Aucun exercice disponible',
                  message: 'Crée un exercice dans la bibliothèque.',
                )
              else
                for (final exercise in exercises)
                  ExerciseListTile(
                    exercise: exercise,
                    onTap: () => Navigator.of(context).pop(exercise),
                  ),
            ],
          );
        },
      ),
    );
  }

  List<Exercise> _filter(List<Exercise> exercises) {
    final query = _searchController.text.trim().toLowerCase();
    return 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();
  }
}

final class _ProgramExerciseCard extends StatelessWidget {
  const _ProgramExerciseCard({
    required this.draft,
    required this.defaultRestSeconds,
    required this.onCustomize,
    required this.index,
    super.key,
  });

  final _ProgramExerciseDraft draft;
  final int defaultRestSeconds;
  final VoidCallback onCustomize;
  final int index;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.only(bottom: 8),
      child: ListTile(
        leading: ReorderableDragStartListener(
          index: index,
          child: const Icon(Icons.drag_handle),
        ),
        title: Text(draft.exerciseNameSnapshot),
        subtitle: Wrap(
          spacing: 8,
          runSpacing: 4,
          crossAxisAlignment: WrapCrossAlignment.center,
          children: [
            Text(_restLabel(draft, defaultRestSeconds)),
            if (draft.exerciseArchivedSnapshot)
              const Chip(label: Text('Exercice archivé')),
          ],
        ),
        trailing: IconButton(
          tooltip: "Personnaliser l'exercice",
          onPressed: onCustomize,
          icon: const Icon(Icons.tune),
        ),
      ),
    );
  }
}

final class _ProgramExerciseCustomizationScreen extends StatefulWidget {
  const _ProgramExerciseCustomizationScreen({
    required _ProgramExerciseDraft draft,
    required this.defaultRestSeconds,
  }) : _initialDraft = draft;

  final _ProgramExerciseDraft _initialDraft;
  final int defaultRestSeconds;

  @override
  State<_ProgramExerciseCustomizationScreen> createState() =>
      _ProgramExerciseCustomizationScreenState();
}

final class _ProgramExerciseCustomizationScreenState
    extends State<_ProgramExerciseCustomizationScreen> {
  final _formKey = GlobalKey<FormState>();
  late final _ProgramExerciseDraft _draft;
  late final TextEditingController _setsController;
  late final TextEditingController _restController;
  String? _measureError;

  @override
  void initState() {
    super.initState();
    _draft = widget._initialDraft.copy();
    _setsController = TextEditingController(text: '${_draft.setsCount}');
    _restController = TextEditingController(
      text: '${_draft.restSecondsOverride ?? widget.defaultRestSeconds}',
    );
  }

  @override
  void dispose() {
    _setsController.dispose();
    _restController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final scoreUnit = _draft.scoreUnitSnapshot;
    final hasStopwatchScore =
        _draft.scoreInputModeSnapshot == ScoreInputMode.stopwatch;
    final scoreTitle = hasStopwatchScore
        ? 'Score chrono'
        : scoreUnit == null
        ? 'Score'
        : 'Score ($scoreUnit)';
    final autoStartNextTimedStepCustomized =
        _draft.autoStartNextTimedStepOverride != null;
    final autoStartNextTimedStepStateLabel = autoStartNextTimedStepCustomized
        ? 'Personnalisé pour ce programme'
        : "Réglage de l'exercice";
    return Scaffold(
      appBar: AppBar(title: const Text("Personnaliser l'exercice")),
      bottomNavigationBar: SafeArea(
        minimum: const EdgeInsets.all(16),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            FilledButton.icon(
              onPressed: _save,
              icon: const Icon(Icons.check),
              label: const Text('Enregistrer'),
            ),
            const SizedBox(height: 8),
            TextButton.icon(
              onPressed: _confirmDelete,
              icon: const Icon(Icons.delete_outline),
              label: const Text('Supprimer du programme'),
            ),
          ],
        ),
      ),
      body: Form(
        key: _formKey,
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            Text(
              _draft.exerciseNameSnapshot,
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const SizedBox(height: 4),
            Text(_exerciseSummary(_draft, widget.defaultRestSeconds)),
            const SizedBox(height: 24),
            Text('Séries', style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 8),
            TextFormField(
              controller: _setsController,
              decoration: const InputDecoration(labelText: 'Nombre de séries'),
              keyboardType: TextInputType.number,
              validator: _positiveIntValidator,
            ),
            const SizedBox(height: 24),
            Text(
              'Mesures à suivre',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            if (_draft.availableTimeSnapshot)
              SwitchListTile(
                contentPadding: EdgeInsets.zero,
                title: const Text('Temps'),
                value: _draft.enabledMeasures.contains(WorkoutMeasure.time),
                onChanged: (value) => _toggle(WorkoutMeasure.time, value),
              ),
            if (_draft.availableRepsSnapshot)
              SwitchListTile(
                contentPadding: EdgeInsets.zero,
                title: const Text('Répétitions'),
                value: _draft.enabledMeasures.contains(WorkoutMeasure.reps),
                onChanged: (value) => _toggle(WorkoutMeasure.reps, value),
              ),
            if (_draft.availableScoreSnapshot)
              SwitchListTile(
                contentPadding: EdgeInsets.zero,
                title: Text(scoreTitle),
                value: _draft.enabledMeasures.contains(WorkoutMeasure.score),
                onChanged: (value) => _toggle(WorkoutMeasure.score, value),
              ),
            if (_draft.enabledMeasures.contains(WorkoutMeasure.time) &&
                _draft.enabledMeasures.contains(WorkoutMeasure.score) &&
                hasStopwatchScore) ...[
              const SizedBox(height: 8),
              const Text(
                "Temps sert d'objectif de durée ; Score chrono enregistre "
                'le temps réalisé.',
              ),
            ],
            if (_measureError != null)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: Text(
                  _measureError!,
                  style: TextStyle(color: Theme.of(context).colorScheme.error),
                ),
              ),
            const SizedBox(height: 24),
            Text('Objectifs', style: Theme.of(context).textTheme.titleMedium),
            if (_draft.enabledMeasures.contains(WorkoutMeasure.time))
              _NumberField(
                label: 'Cible temps (s)',
                initialValue: _draft.targetTimeSeconds,
                onChanged: (value) => _draft.targetTimeSeconds = value?.round(),
              ),
            if (_draft.enabledMeasures.contains(WorkoutMeasure.reps))
              _NumberField(
                label: 'Cible répétitions',
                initialValue: _draft.targetReps,
                onChanged: (value) => _draft.targetReps = value?.round(),
              ),
            if (_draft.enabledMeasures.contains(WorkoutMeasure.score))
              if (hasStopwatchScore)
                _NumberField(
                  label: 'Objectif de chrono',
                  helperText: 'Le résultat réel sera mesuré pendant la série.',
                  initialValue: _millisecondsToSeconds(
                    _draft.targetScoreTimeMs,
                  ),
                  onChanged: (value) {
                    _draft.targetScore = null;
                    _draft.targetScoreTimeMs = value == null
                        ? null
                        : (value * 1000).round();
                  },
                )
              else
                _NumberField(
                  label: scoreUnit == null
                      ? 'Cible score'
                      : 'Cible score ($scoreUnit)',
                  initialValue: _draft.targetScore,
                  onChanged: (value) {
                    _draft.targetScore = value;
                    _draft.targetScoreTimeMs = null;
                  },
                ),
            if (_draft.exerciseStepsSnapshot.isNotEmpty) ...[
              const SizedBox(height: 24),
              Text('Séquence', style: Theme.of(context).textTheme.titleMedium),
              const SizedBox(height: 8),
              SwitchListTile(
                contentPadding: EdgeInsets.zero,
                title: const Text(
                  'Enchaîner automatiquement les chronos consécutifs',
                ),
                subtitle: Text(
                  '$autoStartNextTimedStepStateLabel\n'
                  '${_autoStartHelpText(_draft.autoStartNextTimedStepEffective)}',
                ),
                value: _draft.autoStartNextTimedStepEffective,
                onChanged: (value) {
                  setState(() {
                    _draft.autoStartNextTimedStepOverride = value;
                  });
                },
              ),
              if (autoStartNextTimedStepCustomized)
                TextButton(
                  onPressed: () {
                    setState(() {
                      _draft.autoStartNextTimedStepOverride = null;
                    });
                  },
                  child: const Text("Revenir au réglage de l'exercice"),
                ),
            ],
            const SizedBox(height: 24),
            Text('Repos', style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 8),
            TextFormField(
              controller: _restController,
              decoration: const InputDecoration(
                labelText: 'Repos après chaque série (s)',
                helperText: 'Pas de repos après la toute dernière série.',
              ),
              keyboardType: TextInputType.number,
              validator: _positiveIntValidator,
            ),
          ],
        ),
      ),
    );
  }

  void _toggle(WorkoutMeasure measure, bool value) {
    setState(() {
      _measureError = null;
      if (value) {
        _draft.enabledMeasures = {..._draft.enabledMeasures, measure};
      } else {
        _draft.enabledMeasures = {..._draft.enabledMeasures}..remove(measure);
        if (measure == WorkoutMeasure.score) {
          _draft.targetScore = null;
          _draft.targetScoreTimeMs = null;
        }
      }
    });
  }

  String? _positiveIntValidator(String? value) {
    final number = int.tryParse(value?.trim() ?? '');
    if (number == null || number <= 0) {
      return 'Saisis un nombre supérieur à 0.';
    }
    return null;
  }

  void _save() {
    final hasMeasure = _draft.enabledMeasures.isNotEmpty;
    setState(() {
      _measureError = hasMeasure
          ? null
          : 'Active au moins une mesure pour enregistrer.';
    });
    if (!_formKey.currentState!.validate() || !hasMeasure) {
      return;
    }
    _draft.setsCount = int.parse(_setsController.text.trim());
    _draft.restSecondsOverride = int.parse(_restController.text.trim());
    Navigator.of(context).pop(_ProgramExerciseEditResult.saved(_draft));
  }

  Future<void> _confirmDelete() async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Supprimer cet exercice ?'),
        content: const Text('Il sera retiré de ce programme.'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(false),
            child: const Text('Annuler'),
          ),
          FilledButton(
            onPressed: () => Navigator.of(context).pop(true),
            child: const Text('Supprimer'),
          ),
        ],
      ),
    );
    if (confirmed == true && mounted) {
      Navigator.of(context).pop(const _ProgramExerciseEditResult.deleted());
    }
  }
}

final class _ProgramExerciseEditResult {
  const _ProgramExerciseEditResult._({this.draft, required this.deleted});

  const _ProgramExerciseEditResult.deleted()
    : this._(draft: null, deleted: true);

  const _ProgramExerciseEditResult.saved(_ProgramExerciseDraft draft)
    : this._(draft: draft, deleted: false);

  final _ProgramExerciseDraft? draft;
  final bool deleted;
}

final class _NumberField extends StatelessWidget {
  const _NumberField({
    required this.label,
    required this.initialValue,
    required this.onChanged,
    this.helperText,
  });

  final String label;
  final num? initialValue;
  final ValueChanged<double?> onChanged;
  final String? helperText;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 8),
      child: TextFormField(
        initialValue: initialValue?.toString(),
        decoration: InputDecoration(labelText: label, helperText: helperText),
        keyboardType: TextInputType.number,
        onChanged: (value) => onChanged(double.tryParse(value)),
      ),
    );
  }
}

final class _ProgramExerciseDraft {
  _ProgramExerciseDraft({
    required this.key,
    this.existingMetadata,
    this.sourceExerciseId,
    required this.exerciseNameSnapshot,
    this.exerciseDescriptionSnapshot,
    this.exerciseImageMediaIdSnapshot,
    this.exerciseImageMediaIdsSnapshot = const [],
    this.exerciseVideoMediaIdSnapshot,
    this.exerciseStepsSnapshot = const [],
    this.autoStartNextTimedStepSnapshot = true,
    this.autoStartNextTimedStepOverride,
    this.exerciseArchivedSnapshot = false,
    required this.availableTimeSnapshot,
    required this.availableRepsSnapshot,
    required this.availableScoreSnapshot,
    this.scoreInputModeSnapshot = ScoreInputMode.manual,
    this.scoreLabelSnapshot,
    this.scoreUnitSnapshot,
    required this.setsCount,
    required this.enabledMeasures,
    this.targetTimeSeconds,
    this.targetReps,
    this.targetScore,
    this.targetScoreTimeMs,
    this.restSecondsOverride,
  });

  factory _ProgramExerciseDraft.fromExercise(
    Exercise exercise, {
    int? restSecondsOverride,
  }) {
    return _ProgramExerciseDraft(
      key: UniqueKey().toString(),
      sourceExerciseId: exercise.metadata.id,
      exerciseNameSnapshot: exercise.name,
      exerciseDescriptionSnapshot: exercise.description,
      exerciseImageMediaIdSnapshot:
          exercise.iconMediaId ?? exercise.imageMediaId,
      exerciseImageMediaIdsSnapshot: exercise.imageMediaIds,
      exerciseVideoMediaIdSnapshot: exercise.videoMediaId,
      exerciseStepsSnapshot: exercise.steps,
      autoStartNextTimedStepSnapshot: exercise.autoStartNextTimedStep,
      exerciseArchivedSnapshot: exercise.archivedAt != null,
      availableTimeSnapshot: exercise.hasTimeMeasure,
      availableRepsSnapshot: exercise.hasRepsMeasure,
      availableScoreSnapshot: exercise.hasScoreMeasure,
      scoreInputModeSnapshot: exercise.scoreInputMode,
      scoreLabelSnapshot: exercise.scoreLabel,
      scoreUnitSnapshot: exercise.scoreUnit,
      setsCount: 3,
      enabledMeasures: exercise.availableMeasures,
      targetTimeSeconds: exercise.defaultTargetTimeSeconds,
      targetReps: exercise.defaultTargetReps,
      targetScore: exercise.scoreInputMode == ScoreInputMode.manual
          ? exercise.defaultTargetScore
          : null,
      targetScoreTimeMs: exercise.scoreInputMode == ScoreInputMode.stopwatch
          ? exercise.defaultTargetScoreTimeMs
          : null,
      restSecondsOverride: restSecondsOverride,
    );
  }

  factory _ProgramExerciseDraft.fromProgramExercise(ProgramExercise exercise) {
    return _ProgramExerciseDraft(
      key: exercise.metadata.id,
      existingMetadata: exercise.metadata,
      sourceExerciseId: exercise.sourceExerciseId,
      exerciseNameSnapshot: exercise.exerciseNameSnapshot,
      exerciseDescriptionSnapshot: exercise.exerciseDescriptionSnapshot,
      exerciseImageMediaIdSnapshot: exercise.exerciseImageMediaIdSnapshot,
      exerciseImageMediaIdsSnapshot: exercise.exerciseImageMediaIdsSnapshot,
      exerciseVideoMediaIdSnapshot: exercise.exerciseVideoMediaIdSnapshot,
      exerciseStepsSnapshot: exercise.exerciseStepsSnapshot,
      autoStartNextTimedStepSnapshot: exercise.autoStartNextTimedStepSnapshot,
      autoStartNextTimedStepOverride: exercise.autoStartNextTimedStepOverride,
      exerciseArchivedSnapshot: exercise.exerciseArchivedSnapshot,
      availableTimeSnapshot: exercise.availableTimeSnapshot,
      availableRepsSnapshot: exercise.availableRepsSnapshot,
      availableScoreSnapshot: exercise.availableScoreSnapshot,
      scoreInputModeSnapshot: exercise.scoreInputModeSnapshot,
      scoreLabelSnapshot: exercise.scoreLabelSnapshot,
      scoreUnitSnapshot: exercise.scoreUnitSnapshot,
      setsCount: exercise.setsCount,
      enabledMeasures: {
        if (exercise.timeEnabled) WorkoutMeasure.time,
        if (exercise.repsEnabled) WorkoutMeasure.reps,
        if (exercise.scoreEnabled) WorkoutMeasure.score,
      },
      targetTimeSeconds: exercise.targetTimeSeconds,
      targetReps: exercise.targetReps,
      targetScore: exercise.targetScore,
      targetScoreTimeMs: exercise.targetScoreTimeMs,
      restSecondsOverride: exercise.restSecondsOverride,
    );
  }

  final String key;
  final EntityMetadata? existingMetadata;
  final String? sourceExerciseId;
  final String exerciseNameSnapshot;
  final String? exerciseDescriptionSnapshot;
  final String? exerciseImageMediaIdSnapshot;
  final List<String> exerciseImageMediaIdsSnapshot;
  final String? exerciseVideoMediaIdSnapshot;
  final List<ExerciseStep> exerciseStepsSnapshot;
  final bool autoStartNextTimedStepSnapshot;
  final bool exerciseArchivedSnapshot;
  final bool availableTimeSnapshot;
  final bool availableRepsSnapshot;
  final bool availableScoreSnapshot;
  final ScoreInputMode scoreInputModeSnapshot;
  final String? scoreLabelSnapshot;
  final String? scoreUnitSnapshot;
  int setsCount;
  Set<WorkoutMeasure> enabledMeasures;
  int? targetTimeSeconds;
  int? targetReps;
  double? targetScore;
  int? targetScoreTimeMs;
  int? restSecondsOverride;
  bool? autoStartNextTimedStepOverride;

  bool get autoStartNextTimedStepEffective {
    return autoStartNextTimedStepOverride ?? autoStartNextTimedStepSnapshot;
  }

  _ProgramExerciseDraft copy() {
    return _ProgramExerciseDraft(
      key: key,
      existingMetadata: existingMetadata,
      sourceExerciseId: sourceExerciseId,
      exerciseNameSnapshot: exerciseNameSnapshot,
      exerciseDescriptionSnapshot: exerciseDescriptionSnapshot,
      exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot,
      exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot,
      exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot,
      exerciseStepsSnapshot: exerciseStepsSnapshot,
      autoStartNextTimedStepSnapshot: autoStartNextTimedStepSnapshot,
      autoStartNextTimedStepOverride: autoStartNextTimedStepOverride,
      exerciseArchivedSnapshot: exerciseArchivedSnapshot,
      availableTimeSnapshot: availableTimeSnapshot,
      availableRepsSnapshot: availableRepsSnapshot,
      availableScoreSnapshot: availableScoreSnapshot,
      scoreInputModeSnapshot: scoreInputModeSnapshot,
      scoreLabelSnapshot: scoreLabelSnapshot,
      scoreUnitSnapshot: scoreUnitSnapshot,
      setsCount: setsCount,
      enabledMeasures: {...enabledMeasures},
      targetTimeSeconds: targetTimeSeconds,
      targetReps: targetReps,
      targetScore: targetScore,
      targetScoreTimeMs: targetScoreTimeMs,
      restSecondsOverride: restSecondsOverride,
    );
  }

  ProgramExerciseConfig toConfig() {
    final scoreEnabled = enabledMeasures.contains(WorkoutMeasure.score);
    final stopwatchScore = scoreInputModeSnapshot == ScoreInputMode.stopwatch;
    return ProgramExerciseConfig(
      existingMetadata: existingMetadata,
      sourceExerciseId: sourceExerciseId,
      exerciseNameSnapshot: exerciseNameSnapshot,
      exerciseDescriptionSnapshot: exerciseDescriptionSnapshot,
      exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot,
      exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot,
      exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot,
      exerciseStepsSnapshot: exerciseStepsSnapshot,
      autoStartNextTimedStepSnapshot: autoStartNextTimedStepSnapshot,
      autoStartNextTimedStepOverride: autoStartNextTimedStepOverride,
      exerciseArchivedSnapshot: exerciseArchivedSnapshot,
      availableTimeSnapshot: availableTimeSnapshot,
      availableRepsSnapshot: availableRepsSnapshot,
      availableScoreSnapshot: availableScoreSnapshot,
      scoreInputModeSnapshot: scoreInputModeSnapshot,
      scoreLabelSnapshot: scoreLabelSnapshot,
      scoreUnitSnapshot: scoreUnitSnapshot,
      setsCount: setsCount,
      enabledMeasures: enabledMeasures,
      targetTimeSeconds: targetTimeSeconds,
      targetReps: targetReps,
      targetScore: scoreEnabled && !stopwatchScore ? targetScore : null,
      targetScoreTimeMs: scoreEnabled && stopwatchScore
          ? targetScoreTimeMs
          : null,
      restSecondsOverride: restSecondsOverride,
    );
  }
}

String _autoStartHelpText(bool active) {
  return active
      ? '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.";
}

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

String _programSummary(Program program) {
  final exerciseCount = program.exercises.length;
  final setsCount = program.exercises.fold<int>(
    0,
    (total, exercise) => total + exercise.setsCount,
  );
  return '$exerciseCount exercice${exerciseCount > 1 ? 's' : ''} · '
      '$setsCount série${setsCount > 1 ? 's' : ''}';
}

String _exerciseSummary(_ProgramExerciseDraft draft, int defaultRestSeconds) {
  final measures = draft.enabledMeasures
      .map((measure) {
        final unit = draft.scoreUnitSnapshot?.trim();
        if (measure == WorkoutMeasure.score &&
            draft.scoreInputModeSnapshot == ScoreInputMode.stopwatch) {
          return 'Score chrono';
        }
        if (measure == WorkoutMeasure.score &&
            unit != null &&
            unit.isNotEmpty) {
          return 'Score ($unit)';
        }
        return _measureLabel(measure);
      })
      .join(' + ');
  final rest = draft.restSecondsOverride ?? defaultRestSeconds;
  return '${draft.setsCount} séries · $measures · Repos $rest s';
}

String _restLabel(_ProgramExerciseDraft draft, int defaultRestSeconds) {
  final rest = draft.restSecondsOverride ?? defaultRestSeconds;
  return 'Repos $rest s';
}

String _measureLabel(WorkoutMeasure measure) {
  return switch (measure) {
    WorkoutMeasure.time => 'Temps',
    WorkoutMeasure.reps => 'Répétitions',
    WorkoutMeasure.score => 'Score',
  };
}

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!)),
            ],
          ],
        ),
      ),
    );
  }
}
