import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';

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

final class WorkoutTemplateListScreen extends StatefulWidget {
  const WorkoutTemplateListScreen({
    required this.workoutTemplateUseCases,
    required this.programUseCases,
    required this.activeUseCases,
    required this.closeUseCase,
    required this.historyUseCases,
    this.mediaUseCases,
    this.stepUseCases,
    this.performanceReferenceUseCase,
    this.shareUseCases,
    this.authUseCases,
    this.syncUseCases,
    super.key,
  });

  final WorkoutTemplateUseCases workoutTemplateUseCases;
  final ProgramUseCases programUseCases;
  final ActiveWorkoutSessionUseCases activeUseCases;
  final ActiveExerciseStepUseCases? stepUseCases;
  final ExercisePerformanceReferenceUseCase? performanceReferenceUseCase;
  final CloseWorkoutSessionUseCase closeUseCase;
  final WorkoutHistoryUseCases historyUseCases;
  final MediaUseCases? mediaUseCases;
  final ShareUseCases? shareUseCases;
  final AuthUseCases? authUseCases;
  final SyncUseCases? syncUseCases;

  @override
  State<WorkoutTemplateListScreen> createState() =>
      _WorkoutTemplateListScreenState();
}

final class _WorkoutTemplateListScreenState
    extends State<WorkoutTemplateListScreen> {
  final _searchController = TextEditingController();
  var _selectedTags = <String>{};
  late Future<List<WorkoutTemplate>> _templates;

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Séances')),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () => _openForm(),
        icon: const Icon(Icons.add),
        label: const Text('Créer'),
      ),
      body: FutureBuilder<List<WorkoutTemplate>>(
        future: _templates,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          final templates = snapshot.data ?? const <WorkoutTemplate>[];
          final tagSuggestions = tagSuggestionsFor(
            templates,
            (template) => template.tags,
          ).map((usage) => usage.tag).toList();
          if (templates.isEmpty) {
            return _CenteredMessage(
              title: 'Aucune séance',
              message:
                  'Crée une séance-modèle pour lancer ton entraînement plus vite.',
              actionLabel: 'Créer une séance',
              onAction: () => _openForm(),
            );
          }
          final filtered = _filter(templates);
          return ListView.separated(
            itemCount: filtered.isEmpty ? 2 : filtered.length + 1,
            separatorBuilder: (context, index) => const Divider(height: 1),
            itemBuilder: (context, index) {
              if (index == 0) {
                return _WorkoutTemplateFilters(
                  controller: _searchController,
                  tagSuggestions: tagSuggestions,
                  selectedTags: _selectedTags,
                  filtersActive: _filtersActive,
                  onToggleTag: _toggleTag,
                  onClearFilters: _clearFilters,
                );
              }
              if (filtered.isEmpty) {
                return _CenteredMessage(
                  title: 'Aucune séance ne correspond',
                  message: 'Modifie la recherche ou les tags sélectionnés.',
                  actionLabel: 'Effacer les filtres',
                  onAction: _clearFilters,
                );
              }
              final template = filtered[index - 1];
              return ListTile(
                title: Wrap(
                  spacing: 8,
                  runSpacing: 4,
                  crossAxisAlignment: WrapCrossAlignment.center,
                  children: [
                    Text(template.name),
                    if (template.isExample) const ExampleBadge(),
                  ],
                ),
                subtitle: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(_templateSummary(template)),
                    if (template.tags.isNotEmpty) ...[
                      const SizedBox(height: 6),
                      TagBadgeWrap(tags: template.tags),
                    ],
                  ],
                ),
                trailing: Wrap(
                  spacing: 8,
                  children: [
                    TextButton(
                      onPressed: () => _start(template),
                      child: const Text('Lancer'),
                    ),
                    PopupMenuButton<_WorkoutTemplateItemAction>(
                      tooltip: 'Actions',
                      onSelected: (action) {
                        switch (action) {
                          case _WorkoutTemplateItemAction.duplicate:
                            unawaited(_duplicate(template));
                          case _WorkoutTemplateItemAction.delete:
                            unawaited(_confirmDelete(template));
                        }
                      },
                      itemBuilder: (context) => const [
                        PopupMenuItem(
                          value: _WorkoutTemplateItemAction.duplicate,
                          child: Text('Dupliquer'),
                        ),
                        PopupMenuItem(
                          value: _WorkoutTemplateItemAction.delete,
                          child: Text('Supprimer'),
                        ),
                      ],
                    ),
                    const Icon(Icons.chevron_right),
                  ],
                ),
                onTap: () => _openForm(template: template),
              );
            },
          );
        },
      ),
    );
  }

  void _reload() {
    setState(() {
      _templates = widget.workoutTemplateUseCases.listActive();
    });
  }

  List<WorkoutTemplate> _filter(List<WorkoutTemplate> templates) {
    final query = _searchController.text.trim().toLowerCase();
    final matchingQuery = templates.where((template) {
      return query.isEmpty ||
          template.name.toLowerCase().contains(query) ||
          _templateSummary(template).toLowerCase().contains(query);
    }).toList();
    return filterByRequiredTags(
      matchingQuery,
      _selectedTags,
      (template) => template.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({
    WorkoutTemplate? template,
    String? initialSnackBar,
  }) async {
    final templates = await _templates;
    if (!mounted) return;
    final changed = await Navigator.of(context).push<bool>(
      MaterialPageRoute(
        builder: (context) => WorkoutTemplateFormScreen(
          workoutTemplateUseCases: widget.workoutTemplateUseCases,
          programUseCases: widget.programUseCases,
          shareUseCases: widget.shareUseCases,
          authUseCases: widget.authUseCases,
          syncUseCases: widget.syncUseCases,
          template: template,
          tagSuggestions: tagSuggestionsFor(
            templates,
            (template) => template.tags,
          ).map((usage) => usage.tag).toList(),
          initialSnackBar: initialSnackBar,
        ),
      ),
    );
    if (changed == true) {
      if (!mounted) return;
      _reload();
    }
  }

  Future<void> _duplicate(WorkoutTemplate template) async {
    try {
      final copy = await widget.workoutTemplateUseCases.duplicate(
        template.metadata.id,
      );
      if (!mounted) return;
      await _openForm(
        template: copy,
        initialSnackBar:
            'Séance dupliquée. Ajuste la copie avant de la lancer.',
      );
      if (!mounted) return;
      _reload();
    } on Exception {
      _showSnackBar('Duplication impossible pour le moment.');
    }
  }

  Future<void> _start(WorkoutTemplate template) async {
    final openSession = await widget.activeUseCases.findOpen();
    if (!mounted) return;
    if (openSession != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text(
            'Une séance est déjà en cours. Termine-la ou reprends-la avant '
            'd’en lancer une nouvelle.',
          ),
        ),
      );
      return;
    }
    final session = await widget.activeUseCases.startFromTemplate(
      template.metadata.id,
    );
    if (!mounted) return;
    await Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => WorkoutExecutionScreen(
          initialSession: session,
          activeUseCases: widget.activeUseCases,
          stepUseCases: widget.stepUseCases,
          closeUseCase: widget.closeUseCase,
          historyUseCases: widget.historyUseCases,
          workoutTemplateUseCases: widget.workoutTemplateUseCases,
          performanceReferenceUseCase: widget.performanceReferenceUseCase,
          mediaUseCases: widget.mediaUseCases,
        ),
      ),
    );
  }

  Future<void> _confirmDelete(WorkoutTemplate template) async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Supprimer ${template.name} ?'),
        content: const Text('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.workoutTemplateUseCases.delete(template.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 _WorkoutTemplateItemAction { duplicate, delete }

final class _WorkoutTemplateFilters extends StatelessWidget {
  const _WorkoutTemplateFilters({
    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 WorkoutTemplateFormScreen extends StatefulWidget {
  const WorkoutTemplateFormScreen({
    required this.workoutTemplateUseCases,
    required this.programUseCases,
    this.shareUseCases,
    this.authUseCases,
    this.syncUseCases,
    this.template,
    this.tagSuggestions = const [],
    this.initialSnackBar,
    super.key,
  });

  final WorkoutTemplateUseCases workoutTemplateUseCases;
  final ProgramUseCases programUseCases;
  final ShareUseCases? shareUseCases;
  final AuthUseCases? authUseCases;
  final SyncUseCases? syncUseCases;
  final WorkoutTemplate? template;
  final List<String> tagSuggestions;
  final String? initialSnackBar;

  @override
  State<WorkoutTemplateFormScreen> createState() =>
      _WorkoutTemplateFormScreenState();
}

final class _WorkoutTemplateFormScreenState
    extends State<WorkoutTemplateFormScreen> {
  final _formKey = GlobalKey<FormState>();
  late final TextEditingController _nameController;
  late final List<WorkoutTemplateProgramDraft> _programs;
  late List<String> _tags;
  var _saving = false;

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

  @override
  void initState() {
    super.initState();
    final template = widget.template;
    _nameController = TextEditingController(text: template?.name);
    _programs = [
      for (final program
          in template?.programs ?? const <WorkoutTemplateProgram>[])
        WorkoutTemplateProgramDraft.fromTemplateProgram(
          program,
          template?.overrides ?? const <WorkoutTemplateExerciseOverride>[],
        ),
    ];
    _tags = List<String>.of(template?.tags ?? const []);
    final initialSnackBar = widget.initialSnackBar;
    if (initialSnackBar != null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) _showSnackBar(initialSnackBar);
      });
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_isEditing ? 'Modifier la séance' : 'Créer une séance'),
        actions: [
          if (_isEditing && widget.shareUseCases != null)
            IconButton(
              tooltip: 'Partager',
              icon: const Icon(Icons.ios_share),
              onPressed: _shareTemplate,
            ),
        ],
      ),
      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),
                  EditableTagsSection(
                    tags: _tags,
                    suggestions: widget.tagSuggestions,
                    onChanged: (tags) => setState(() => _tags = tags),
                  ),
                  const SizedBox(height: 12),
                  SizedBox(
                    width: double.infinity,
                    child: OutlinedButton.icon(
                      onPressed: _addProgram,
                      icon: const Icon(Icons.add),
                      label: const Text('Ajouter un programme'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          Expanded(
            child: _programs.isEmpty
                ? const _CenteredMessage(
                    title: 'Aucun programme ajouté',
                    message: 'Ajoute un programme pour composer la séance.',
                  )
                : ReorderableListView.builder(
                    buildDefaultDragHandles: false,
                    padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
                    itemCount: _programs.length,
                    onReorder: _reorderProgram,
                    itemBuilder: (context, index) {
                      final program = _programs[index];
                      return Card(
                        key: ValueKey(program.clientKey),
                        child: ListTile(
                          leading: ReorderableDragStartListener(
                            index: index,
                            child: const Icon(Icons.drag_handle),
                          ),
                          title: Text(program.programNameSnapshot),
                          subtitle: Text(_programDraftSummary(program)),
                          trailing: const Icon(Icons.chevron_right),
                          onTap: () => _openProgramDetail(program),
                        ),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }

  Future<void> _addProgram() async {
    final program = await Navigator.of(context).push<Program>(
      MaterialPageRoute(
        builder: (context) =>
            ProgramSelectionScreen(programUseCases: widget.programUseCases),
      ),
    );
    if (program == null) return;
    if (!mounted) return;
    final shouldExplainSnapshot = _programs.isEmpty;
    setState(() {
      _programs.add(WorkoutTemplateProgramDraft.fromProgram(program));
    });
    if (shouldExplainSnapshot) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text(
            'Une copie de ce programme sera enregistrée dans la séance. '
            'Les futures modifications du programme original ne changeront '
            'pas cette séance.',
          ),
        ),
      );
    }
  }

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

  Future<void> _openProgramDetail(WorkoutTemplateProgramDraft program) async {
    await Navigator.of(context).push<void>(
      MaterialPageRoute(
        builder: (context) =>
            WorkoutTemplateProgramDetailScreen(program: program),
      ),
    );
    if (!mounted) return;
    setState(() {});
  }

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

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

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

final class ProgramSelectionScreen extends StatelessWidget {
  const ProgramSelectionScreen({required this.programUseCases, super.key});

  final ProgramUseCases programUseCases;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Ajouter un programme')),
      body: FutureBuilder<List<Program>>(
        future: programUseCases.listActive(),
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          final programs = snapshot.data ?? const <Program>[];
          if (programs.isEmpty) {
            return const _CenteredMessage(
              title: 'Aucun programme disponible',
              message: 'Crée un programme avant de composer une séance.',
            );
          }
          return ListView.separated(
            itemCount: programs.length,
            separatorBuilder: (context, index) => const Divider(height: 1),
            itemBuilder: (context, index) {
              final program = programs[index];
              return ListTile(
                title: Text(program.name),
                subtitle: Text(_programSummary(program)),
                onTap: () => Navigator.of(context).pop(program),
              );
            },
          );
        },
      ),
    );
  }
}

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

  final WorkoutTemplateProgramDraft program;

  @override
  State<WorkoutTemplateProgramDetailScreen> createState() =>
      _WorkoutTemplateProgramDetailScreenState();
}

final class _WorkoutTemplateProgramDetailScreenState
    extends State<WorkoutTemplateProgramDetailScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.program.programNameSnapshot)),
      body: ListView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: widget.program.exercises.length,
        itemBuilder: (context, index) {
          final exercise = widget.program.exercises[index];
          final autoStartNextTimedStepCustomized =
              exercise.autoStartNextTimedStepOverride != null;
          final autoStartNextTimedStepStateLabel =
              autoStartNextTimedStepCustomized
              ? 'Personnalisé pour cette séance'
              : 'Réglage du programme';
          return Card(
            child: Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    exercise.exerciseNameSnapshot,
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 4),
                  Text(_exerciseDraftSummary(exercise)),
                  const SizedBox(height: 12),
                  TextFormField(
                    initialValue:
                        '${exercise.setsCountOverride ?? exercise.setsCount}',
                    decoration: const InputDecoration(
                      labelText: 'Nombre de séries',
                    ),
                    keyboardType: TextInputType.number,
                    onChanged: (value) {
                      setState(() {
                        exercise.setsCountOverride = int.tryParse(value);
                      });
                    },
                  ),
                  if (exercise.timeEnabled)
                    _NumberField(
                      label: 'Cible temps (s)',
                      initialValue:
                          exercise.targetTimeSecondsOverride ??
                          exercise.targetTimeSeconds,
                      onChanged: (value) {
                        setState(() {
                          exercise.targetTimeSecondsOverride = value?.round();
                        });
                      },
                    ),
                  if (exercise.repsEnabled)
                    _NumberField(
                      label: 'Cible répétitions',
                      initialValue:
                          exercise.targetRepsOverride ?? exercise.targetReps,
                      onChanged: (value) {
                        setState(() {
                          exercise.targetRepsOverride = value?.round();
                        });
                      },
                    ),
                  if (exercise.scoreEnabled)
                    _NumberField(
                      label: exercise.scoreUnitSnapshot == null
                          ? 'Cible score'
                          : 'Cible score (${exercise.scoreUnitSnapshot})',
                      initialValue:
                          exercise.targetScoreOverride ?? exercise.targetScore,
                      onChanged: (value) {
                        setState(() {
                          exercise.targetScoreOverride = value;
                        });
                      },
                    ),
                  if (exercise.exerciseStepsSnapshot.isNotEmpty) ...[
                    const SizedBox(height: 16),
                    Text(
                      'Séquence',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    SwitchListTile(
                      contentPadding: EdgeInsets.zero,
                      title: const Text(
                        'Enchaîner automatiquement les chronos consécutifs',
                      ),
                      subtitle: Text(
                        '$autoStartNextTimedStepStateLabel\n'
                        '${_autoStartHelpText(exercise.autoStartNextTimedStepEffective)}',
                      ),
                      value: exercise.autoStartNextTimedStepEffective,
                      onChanged: (value) {
                        setState(() {
                          exercise.autoStartNextTimedStepOverride = value;
                        });
                      },
                    ),
                    if (autoStartNextTimedStepCustomized)
                      TextButton(
                        onPressed: () {
                          setState(() {
                            exercise.autoStartNextTimedStepOverride = null;
                          });
                        },
                        child: const Text('Revenir au réglage du programme'),
                      ),
                  ],
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

final class WorkoutTemplateProgramDraft {
  WorkoutTemplateProgramDraft({
    required this.clientKey,
    this.existingMetadata,
    this.sourceProgramId,
    required this.programNameSnapshot,
    required this.defaultRestSecondsSnapshot,
    required this.programSnapshotJson,
    required this.exercises,
  });

  factory WorkoutTemplateProgramDraft.fromProgram(Program program) {
    final snapshot = {
      'programId': program.metadata.id,
      'name': program.name,
      'defaultRestSeconds': program.defaultRestSeconds,
      'exercises': program.exercises
          .map((exercise) => exercise.toSnapshotJson())
          .toList(),
    };
    return WorkoutTemplateProgramDraft(
      clientKey: UniqueKey().toString(),
      sourceProgramId: program.metadata.id,
      programNameSnapshot: program.name,
      defaultRestSecondsSnapshot: program.defaultRestSeconds,
      programSnapshotJson: jsonEncode(snapshot),
      exercises: _parseExercises(snapshot),
    );
  }

  factory WorkoutTemplateProgramDraft.fromTemplateProgram(
    WorkoutTemplateProgram program,
    List<WorkoutTemplateExerciseOverride> overrides,
  ) {
    final snapshot =
        jsonDecode(program.programSnapshotJson) as Map<String, dynamic>;
    final exercises = _parseExercises(snapshot);
    for (final exercise in exercises) {
      for (final override in overrides) {
        if (override.workoutTemplateProgramId == program.metadata.id &&
            override.snapshotProgramExerciseId == exercise.snapshotExerciseId) {
          exercise.existingOverrideMetadata = override.metadata;
          exercise.setsCountOverride = override.setsCountOverride;
          exercise.targetTimeSecondsOverride =
              override.targetTimeSecondsOverride;
          exercise.targetRepsOverride = override.targetRepsOverride;
          exercise.targetScoreOverride = override.targetScoreOverride;
          exercise.autoStartNextTimedStepOverride =
              override.autoStartNextTimedStepOverride;
        }
      }
    }
    return WorkoutTemplateProgramDraft(
      clientKey: program.metadata.id,
      existingMetadata: program.metadata,
      sourceProgramId: program.sourceProgramId,
      programNameSnapshot: program.programNameSnapshot,
      defaultRestSecondsSnapshot: program.defaultRestSecondsSnapshot,
      programSnapshotJson: program.programSnapshotJson,
      exercises: exercises,
    );
  }

  final String clientKey;
  final EntityMetadata? existingMetadata;
  final String? sourceProgramId;
  final String programNameSnapshot;
  final int defaultRestSecondsSnapshot;
  final String programSnapshotJson;
  final List<WorkoutTemplateExerciseDraft> exercises;

  WorkoutTemplateProgramConfig toConfig() {
    return WorkoutTemplateProgramConfig(
      clientKey: clientKey,
      existingMetadata: existingMetadata,
      sourceProgramId: sourceProgramId,
      programNameSnapshot: programNameSnapshot,
      defaultRestSecondsSnapshot: defaultRestSecondsSnapshot,
      programSnapshotJson: programSnapshotJson,
    );
  }

  Iterable<WorkoutTemplateExerciseOverrideConfig> toOverrides() sync* {
    for (final exercise in exercises) {
      if (!exercise.hasOverride) {
        continue;
      }
      yield WorkoutTemplateExerciseOverrideConfig(
        existingMetadata: exercise.existingOverrideMetadata,
        workoutTemplateProgramClientKey: clientKey,
        snapshotProgramExerciseId: exercise.snapshotExerciseId,
        setsCountOverride: exercise.setsCountOverride,
        targetTimeSecondsOverride: exercise.targetTimeSecondsOverride,
        targetRepsOverride: exercise.targetRepsOverride,
        targetScoreOverride: exercise.targetScoreOverride,
        autoStartNextTimedStepOverride: exercise.autoStartNextTimedStepOverride,
      );
    }
  }
}

final class WorkoutTemplateExerciseDraft {
  WorkoutTemplateExerciseDraft({
    required this.snapshotExerciseId,
    required this.exerciseNameSnapshot,
    required this.setsCount,
    required this.timeEnabled,
    required this.repsEnabled,
    required this.scoreEnabled,
    this.exerciseStepsSnapshot = const [],
    this.autoStartNextTimedStepSnapshot = true,
    this.scoreUnitSnapshot,
    this.targetTimeSeconds,
    this.targetReps,
    this.targetScore,
    this.existingOverrideMetadata,
    this.setsCountOverride,
    this.targetTimeSecondsOverride,
    this.targetRepsOverride,
    this.targetScoreOverride,
    this.autoStartNextTimedStepOverride,
  });

  final String snapshotExerciseId;
  final String exerciseNameSnapshot;
  final int setsCount;
  final bool timeEnabled;
  final bool repsEnabled;
  final bool scoreEnabled;
  final List<ExerciseStep> exerciseStepsSnapshot;
  final bool autoStartNextTimedStepSnapshot;
  final String? scoreUnitSnapshot;
  final int? targetTimeSeconds;
  final int? targetReps;
  final double? targetScore;
  EntityMetadata? existingOverrideMetadata;
  int? setsCountOverride;
  int? targetTimeSecondsOverride;
  int? targetRepsOverride;
  double? targetScoreOverride;
  bool? autoStartNextTimedStepOverride;

  bool get autoStartNextTimedStepEffective {
    return autoStartNextTimedStepOverride ?? autoStartNextTimedStepSnapshot;
  }

  bool get hasOverride {
    return setsCountOverride != null ||
        targetTimeSecondsOverride != null ||
        targetRepsOverride != null ||
        targetScoreOverride != null ||
        autoStartNextTimedStepOverride != null;
  }
}

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

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

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

List<WorkoutTemplateExerciseDraft> _parseExercises(
  Map<String, dynamic> snapshot,
) {
  final rawExercises = snapshot['exercises'] as List<dynamic>? ?? const [];
  return rawExercises.map((raw) {
    final exercise = raw as Map<String, dynamic>;
    final autoStartNextTimedStepSnapshot =
        (exercise['autoStartNextTimedStepOverride'] as bool?) ??
        (exercise['autoStartNextTimedStepSnapshot'] as bool?) ??
        true;
    return WorkoutTemplateExerciseDraft(
      snapshotExerciseId: exercise['id'] as String,
      exerciseNameSnapshot: exercise['exerciseNameSnapshot'] as String,
      setsCount: exercise['setsCount'] as int,
      timeEnabled: exercise['timeEnabled'] as bool,
      repsEnabled: exercise['repsEnabled'] as bool,
      scoreEnabled: exercise['scoreEnabled'] as bool,
      exerciseStepsSnapshot: _parseExerciseSteps(
        exercise['exerciseStepsSnapshot'],
      ),
      autoStartNextTimedStepSnapshot: autoStartNextTimedStepSnapshot,
      scoreUnitSnapshot: exercise['scoreUnitSnapshot'] as String?,
      targetTimeSeconds: exercise['targetTimeSeconds'] as int?,
      targetReps: exercise['targetReps'] as int?,
      targetScore: (exercise['targetScore'] as num?)?.toDouble(),
    );
  }).toList();
}

List<ExerciseStep> _parseExerciseSteps(Object? value) {
  final rawSteps = value as List<dynamic>? ?? const [];
  return rawSteps.map((raw) {
    final step = raw as Map<String, dynamic>;
    return ExerciseStep(
      id: step['id'] as String,
      position: step['position'] as int,
      name: step['name'] as String,
      type: ExerciseStepType.values.byName(step['type'] as String),
      defaultTargetValue: step['defaultTargetValue'] as int,
      hasScore: step['hasScore'] as bool? ?? false,
      scoreInputMode: ScoreInputMode.values.byName(
        step['scoreInputMode'] as String? ?? ScoreInputMode.manual.name,
      ),
      scoreLabel: step['scoreLabel'] as String?,
      scoreUnit: step['scoreUnit'] as String?,
      defaultTargetScore: (step['defaultTargetScore'] as num?)?.toDouble(),
      defaultTargetScoreTimeMs: step['defaultTargetScoreTimeMs'] as int?,
    );
  }).toList();
}

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.";
}

String _templateSummary(WorkoutTemplate template) {
  final programCount = template.programs.length;
  final exerciseCount = template.programs.fold<int>(
    0,
    (total, program) => total + _exerciseCount(program.programSnapshotJson),
  );
  return '$programCount programme${programCount > 1 ? 's' : ''} · '
      '$exerciseCount exercice${exerciseCount > 1 ? 's' : ''}';
}

String _programDraftSummary(WorkoutTemplateProgramDraft program) {
  final exerciseCount = program.exercises.length;
  return '$exerciseCount exercice${exerciseCount > 1 ? 's' : ''}';
}

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 _exerciseDraftSummary(WorkoutTemplateExerciseDraft exercise) {
  final measures = [
    if (exercise.timeEnabled) 'Temps',
    if (exercise.repsEnabled) 'Répétitions',
    if (exercise.scoreEnabled)
      exercise.scoreUnitSnapshot == null
          ? 'Score'
          : 'Score (${exercise.scoreUnitSnapshot})',
  ].join(' + ');
  final sets = exercise.setsCountOverride ?? exercise.setsCount;
  return '$sets séries · $measures';
}

int _exerciseCount(String programSnapshotJson) {
  final snapshot = jsonDecode(programSnapshotJson) as Map<String, dynamic>;
  return (snapshot['exercises'] as List<dynamic>? ?? const []).length;
}

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