import 'dart:math' as math;

import 'package:flutter/material.dart';

import '../application/application.dart';
import 'history_screen.dart';
import 'theme.dart';

final class ProgressionScreen extends StatefulWidget {
  const ProgressionScreen({
    required this.progressionStatsUseCase,
    required this.historyUseCases,
    required this.workoutTemplateUseCases,
    required this.activeUseCases,
    required this.closeUseCase,
    this.mediaUseCases,
    this.stepUseCases,
    this.performanceReferenceUseCase,
    super.key,
  });

  final ProgressionStatsUseCase progressionStatsUseCase;
  final WorkoutHistoryUseCases historyUseCases;
  final WorkoutTemplateUseCases workoutTemplateUseCases;
  final ActiveWorkoutSessionUseCases activeUseCases;
  final ActiveExerciseStepUseCases? stepUseCases;
  final ExercisePerformanceReferenceUseCase? performanceReferenceUseCase;
  final CloseWorkoutSessionUseCase closeUseCase;
  final MediaUseCases? mediaUseCases;

  @override
  State<ProgressionScreen> createState() => _ProgressionScreenState();
}

final class _ProgressionScreenState extends State<ProgressionScreen> {
  var _period = ProgressionPeriod.fourWeeks;
  late Future<ProgressionOverview> _overview;
  String? _selectedExerciseKey;
  ProgressionMeasure? _selectedMeasure;
  Future<ProgressionExerciseSeries>? _series;

  @override
  void initState() {
    super.initState();
    _overview = _loadOverview();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Progression')),
      body: FutureBuilder<ProgressionOverview>(
        future: _overview,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return _CenteredProgressionMessage(
              title: 'Progression indisponible',
              message: snapshot.error.toString(),
            );
          }
          final overview = snapshot.data!;
          return ListView(
            padding: const EdgeInsets.all(16),
            children: [
              _PeriodSelector(
                period: _period,
                onChanged: (period) => _changePeriod(period),
              ),
              const SizedBox(height: 16),
              if (!overview.hasAnyCompletedHistory)
                _GlobalEmptyState(onOpenHistory: _openHistoryList)
              else ...[
                _VolumeSection(overview: overview),
                const SizedBox(height: 12),
                _RegularitySection(overview: overview),
                const SizedBox(height: 12),
                _ExerciseProgressionSection(
                  overview: overview,
                  selectedExerciseKey: _selectedExerciseKey,
                  selectedMeasure: _selectedMeasure,
                  series: _series,
                  onExerciseChanged: (exercise) =>
                      _selectExercise(overview, exercise),
                  onMeasureChanged: (measure) =>
                      _selectMeasure(overview, measure),
                  onPointTap: _showPointDetail,
                ),
              ],
            ],
          );
        },
      ),
    );
  }

  Future<ProgressionOverview> _loadOverview() async {
    final overview = await widget.progressionStatsUseCase.getOverview(_period);
    _syncSelection(overview);
    return overview;
  }

  void _changePeriod(ProgressionPeriod period) {
    if (period == _period) return;
    setState(() {
      _period = period;
      _selectedExerciseKey = null;
      _selectedMeasure = null;
      _series = null;
      _overview = _loadOverview();
    });
  }

  void _syncSelection(ProgressionOverview overview) {
    if (overview.exerciseOptions.isEmpty) {
      _selectedExerciseKey = null;
      _selectedMeasure = null;
      _series = null;
      return;
    }
    final exercise = overview.exerciseOptions.firstWhere(
      (option) => option.exerciseKey == _selectedExerciseKey,
      orElse: () => overview.exerciseOptions.first,
    );
    _selectedExerciseKey = exercise.exerciseKey;
    _selectedMeasure = _resolveMeasure(exercise, _selectedMeasure);
    final measure = _selectedMeasure;
    if (measure == null) {
      _series = null;
      return;
    }
    _series = widget.progressionStatsUseCase.getExerciseSeries(
      period: _period,
      exerciseKey: exercise.exerciseKey,
      measure: measure,
    );
  }

  void _selectExercise(
    ProgressionOverview overview,
    ProgressionExerciseOption exercise,
  ) {
    setState(() {
      _selectedExerciseKey = exercise.exerciseKey;
      _selectedMeasure = _resolveMeasure(exercise, null);
      final measure = _selectedMeasure;
      _series = measure == null
          ? null
          : widget.progressionStatsUseCase.getExerciseSeries(
              period: _period,
              exerciseKey: exercise.exerciseKey,
              measure: measure,
            );
    });
  }

  void _selectMeasure(
    ProgressionOverview overview,
    ProgressionMeasure measure,
  ) {
    final exercise = _selectedExercise(overview);
    if (exercise == null) return;
    setState(() {
      _selectedMeasure = measure;
      _series = widget.progressionStatsUseCase.getExerciseSeries(
        period: _period,
        exerciseKey: exercise.exerciseKey,
        measure: measure,
      );
    });
  }

  ProgressionMeasure? _resolveMeasure(
    ProgressionExerciseOption exercise,
    ProgressionMeasure? preferred,
  ) {
    final options = exercise.measures;
    if (options.isEmpty) return null;
    if (preferred != null &&
        options.any((option) => option.measure == preferred)) {
      return preferred;
    }
    return options.first.measure;
  }

  ProgressionExerciseOption? _selectedExercise(ProgressionOverview overview) {
    final selectedKey = _selectedExerciseKey;
    if (selectedKey == null) return null;
    for (final option in overview.exerciseOptions) {
      if (option.exerciseKey == selectedKey) {
        return option;
      }
    }
    return null;
  }

  Future<void> _showPointDetail(ProgressionExerciseSeries series, int index) {
    final point = series.points[index];
    return showModalBottomSheet<void>(
      context: context,
      showDragHandle: true,
      builder: (context) {
        return SafeArea(
          child: Padding(
            padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  _formatDate(point.startedAt),
                  style: Theme.of(context).textTheme.titleMedium,
                ),
                const SizedBox(height: 8),
                Text('Séance : ${point.workoutNameSnapshot}'),
                const SizedBox(height: 8),
                Text(_pointDetailLabel(series, point)),
                const SizedBox(height: 16),
                Align(
                  alignment: Alignment.centerRight,
                  child: TextButton(
                    onPressed: () => _openHistoryDetailFromPoint(
                      context,
                      point.workoutHistoryId,
                    ),
                    child: const Text('Voir la séance'),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  Future<void> _openHistoryList() {
    return Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => HistoryListScreen(
          historyUseCases: widget.historyUseCases,
          workoutTemplateUseCases: widget.workoutTemplateUseCases,
          activeUseCases: widget.activeUseCases,
          stepUseCases: widget.stepUseCases,
          performanceReferenceUseCase: widget.performanceReferenceUseCase,
          closeUseCase: widget.closeUseCase,
          mediaUseCases: widget.mediaUseCases,
        ),
      ),
    );
  }

  Future<void> _openHistoryDetailFromPoint(
    BuildContext bottomSheetContext,
    String workoutHistoryId,
  ) async {
    final navigator = Navigator.of(context);
    Navigator.of(bottomSheetContext).pop();
    final history = await widget.historyUseCases.findById(workoutHistoryId);
    if (history == null) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("Cette séance n'est plus disponible.")),
      );
      return;
    }
    if (!mounted) return;
    await navigator.push(
      MaterialPageRoute(
        builder: (context) => HistoryDetailScreen(
          history: history,
          historyUseCases: widget.historyUseCases,
          workoutTemplateUseCases: widget.workoutTemplateUseCases,
          activeUseCases: widget.activeUseCases,
          stepUseCases: widget.stepUseCases,
          performanceReferenceUseCase: widget.performanceReferenceUseCase,
          closeUseCase: widget.closeUseCase,
          mediaUseCases: widget.mediaUseCases,
        ),
      ),
    );
  }
}

final class _PeriodSelector extends StatelessWidget {
  const _PeriodSelector({required this.period, required this.onChanged});

  final ProgressionPeriod period;
  final ValueChanged<ProgressionPeriod> onChanged;

  @override
  Widget build(BuildContext context) {
    return SegmentedButton<ProgressionPeriod>(
      segments: const [
        ButtonSegment(
          value: ProgressionPeriod.fourWeeks,
          label: Text('4 semaines'),
        ),
        ButtonSegment(
          value: ProgressionPeriod.threeMonths,
          label: Text('3 mois'),
        ),
        ButtonSegment(value: ProgressionPeriod.all, label: Text('Tout')),
      ],
      selected: {period},
      onSelectionChanged: (selection) => onChanged(selection.first),
    );
  }
}

final class _VolumeSection extends StatelessWidget {
  const _VolumeSection({required this.overview});

  final ProgressionOverview overview;

  @override
  Widget build(BuildContext context) {
    return CourtBlazerAccentPanel(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _SectionTitle('Volume'),
          const SizedBox(height: 12),
          Row(
            children: [
              Expanded(
                child: _MetricTile(
                  label: 'Séances',
                  value: overview.completedSessionCount.toString(),
                ),
              ),
              const SizedBox(width: 10),
              Expanded(
                child: _MetricTile(
                  label: 'Temps total',
                  value: _formatVolumeDuration(overview.totalActiveMs),
                ),
              ),
            ],
          ),
          if (overview.rangeLabelKind ==
              ProgressionRangeLabelKind.sinceFirstSession) ...[
            const SizedBox(height: 12),
            Text(
              'Depuis ta première séance',
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ],
        ],
      ),
    );
  }
}

final class _RegularitySection extends StatelessWidget {
  const _RegularitySection({required this.overview});

  final ProgressionOverview overview;

  @override
  Widget build(BuildContext context) {
    final text = overview.totalWeekCount == null
        ? '${overview.activeWeekCount} semaines actives au total'
        : overview.activeWeekCount == 1
        ? '1 semaine active'
        : '${overview.activeWeekCount} / ${overview.totalWeekCount} semaines actives';
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _SectionTitle('Régularité'),
            const SizedBox(height: 10),
            Text(
              'Rythme récent',
              style: Theme.of(context).textTheme.titleSmall,
            ),
            const SizedBox(height: 6),
            Text(text, style: AppTextStyles.scoreNumber(context)),
            const SizedBox(height: 6),
            Text(
              'Une semaine active contient au moins une séance terminée.',
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ],
        ),
      ),
    );
  }
}

final class _ExerciseProgressionSection extends StatelessWidget {
  const _ExerciseProgressionSection({
    required this.overview,
    required this.selectedExerciseKey,
    required this.selectedMeasure,
    required this.series,
    required this.onExerciseChanged,
    required this.onMeasureChanged,
    required this.onPointTap,
  });

  final ProgressionOverview overview;
  final String? selectedExerciseKey;
  final ProgressionMeasure? selectedMeasure;
  final Future<ProgressionExerciseSeries>? series;
  final ValueChanged<ProgressionExerciseOption> onExerciseChanged;
  final ValueChanged<ProgressionMeasure> onMeasureChanged;
  final void Function(ProgressionExerciseSeries series, int index) onPointTap;

  @override
  Widget build(BuildContext context) {
    final exercise = _selectedExercise;
    return CourtBlazerAccentPanel(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _SectionTitle('Par exercice'),
          const SizedBox(height: 12),
          if (overview.exerciseOptions.isEmpty)
            const _InlineProgressionMessage(
              title: 'Pas encore de mesure exploitable',
              message:
                  'Les graphiques apparaissent quand un exercice contient des répétitions, du temps ou un score numérique enregistré.',
            )
          else ...[
            DropdownButtonFormField<String>(
              initialValue: exercise?.exerciseKey,
              decoration: const InputDecoration(
                labelText: 'Choisir un exercice',
              ),
              items: [
                for (final option in overview.exerciseOptions)
                  DropdownMenuItem(
                    value: option.exerciseKey,
                    child: _ExerciseOptionLabel(option: option),
                  ),
              ],
              onChanged: (value) {
                final selected = overview.exerciseOptions.firstWhere(
                  (option) => option.exerciseKey == value,
                );
                onExerciseChanged(selected);
              },
            ),
            if (exercise != null) ...[
              const SizedBox(height: 12),
              _MeasureSelector(
                exercise: exercise,
                selectedMeasure: selectedMeasure,
                onChanged: onMeasureChanged,
              ),
              const SizedBox(height: 12),
              if (series == null)
                const _InlineProgressionMessage(
                  title: 'Pas encore de mesure exploitable',
                  message:
                      'Les graphiques apparaissent quand un exercice contient des répétitions, du temps ou un score numérique enregistré.',
                )
              else
                FutureBuilder<ProgressionExerciseSeries>(
                  future: series,
                  builder: (context, snapshot) {
                    if (snapshot.connectionState != ConnectionState.done) {
                      return const SizedBox(
                        height: 220,
                        child: Center(child: CircularProgressIndicator()),
                      );
                    }
                    if (snapshot.hasError) {
                      return _InlineProgressionMessage(
                        title: 'Progression indisponible',
                        message: snapshot.error.toString(),
                      );
                    }
                    return _SeriesBody(
                      series: snapshot.data!,
                      onPointTap: onPointTap,
                    );
                  },
                ),
            ],
          ],
        ],
      ),
    );
  }

  ProgressionExerciseOption? get _selectedExercise {
    final key = selectedExerciseKey;
    if (key == null) return null;
    for (final option in overview.exerciseOptions) {
      if (option.exerciseKey == key) return option;
    }
    return null;
  }
}

final class _MeasureSelector extends StatelessWidget {
  const _MeasureSelector({
    required this.exercise,
    required this.selectedMeasure,
    required this.onChanged,
  });

  final ProgressionExerciseOption exercise;
  final ProgressionMeasure? selectedMeasure;
  final ValueChanged<ProgressionMeasure> onChanged;

  @override
  Widget build(BuildContext context) {
    final options = exercise.measures;
    if (options.length <= 1) {
      final option = options.firstOrNull;
      if (option == null) {
        return const SizedBox.shrink();
      }
      return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(option.label, style: Theme.of(context).textTheme.labelLarge),
          _MeasureHint(option: option),
        ],
      );
    }
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SegmentedButton<ProgressionMeasure>(
          segments: [
            for (final option in options)
              ButtonSegment(value: option.measure, label: Text(option.label)),
          ],
          selected: {selectedMeasure ?? options.first.measure},
          onSelectionChanged: (selection) => onChanged(selection.first),
        ),
        const SizedBox(height: 8),
        _MeasureHint(
          option: options.firstWhere(
            (option) =>
                option.measure == (selectedMeasure ?? options.first.measure),
            orElse: () => options.first,
          ),
        ),
      ],
    );
  }
}

final class _MeasureHint extends StatelessWidget {
  const _MeasureHint({required this.option});

  final ProgressionMeasureOption option;

  @override
  Widget build(BuildContext context) {
    if (!option.lowerIsBetter) {
      return const SizedBox.shrink();
    }
    return Text(
      'Plus bas = mieux',
      style: Theme.of(context).textTheme.bodySmall,
    );
  }
}

final class _SeriesBody extends StatelessWidget {
  const _SeriesBody({required this.series, required this.onPointTap});

  final ProgressionExerciseSeries series;
  final void Function(ProgressionExerciseSeries series, int index) onPointTap;

  @override
  Widget build(BuildContext context) {
    final stateMessage = _stateMessage(series.state);
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        if (stateMessage != null) ...[
          _InlineProgressionMessage(
            title: stateMessage.$1,
            message: stateMessage.$2,
          ),
          const SizedBox(height: 12),
        ],
        if (series.state != ProgressionSeriesState.noGraphableMeasure &&
            series.state != ProgressionSeriesState.textScoreOnly) ...[
          _ProgressionChart(series: series, onPointTap: onPointTap),
          const SizedBox(height: 12),
          _SeriesSummaryView(series: series),
        ],
      ],
    );
  }
}

final class _ProgressionChart extends StatelessWidget {
  const _ProgressionChart({required this.series, required this.onPointTap});

  final ProgressionExerciseSeries series;
  final void Function(ProgressionExerciseSeries series, int index) onPointTap;

  @override
  Widget build(BuildContext context) {
    final tokens = courtBlazerTokensOf(context);
    return SizedBox(
      height: 220,
      child: LayoutBuilder(
        builder: (context, constraints) {
          return Stack(
            children: [
              CustomPaint(
                size: Size(constraints.maxWidth, constraints.maxHeight),
                painter: _ProgressionChartPainter(
                  series: series,
                  theme: Theme.of(context),
                ),
              ),
              for (var index = 0; index < series.points.length; index++)
                Positioned.fromRect(
                  rect: _pointHitRect(
                    constraints.biggest,
                    series.points,
                    index,
                  ),
                  child: Tooltip(
                    message: _formatDate(series.points[index].startedAt),
                    child: InkWell(
                      key: ValueKey('progression-point-$index'),
                      borderRadius: BorderRadius.circular(18),
                      onTap: () => onPointTap(series, index),
                      child: DecoratedBox(
                        decoration: BoxDecoration(
                          shape: BoxShape.circle,
                          border: Border.all(color: tokens.border),
                        ),
                      ),
                    ),
                  ),
                ),
            ],
          );
        },
      ),
    );
  }
}

final class _ProgressionChartPainter extends CustomPainter {
  const _ProgressionChartPainter({required this.series, required this.theme});

  final ProgressionExerciseSeries series;
  final ThemeData theme;

  @override
  void paint(Canvas canvas, Size size) {
    final tokens =
        theme.extension<CourtBlazerTokens>() ??
        CourtBlazerTokens(
          accent: theme.colorScheme.secondary,
          border: theme.dividerColor,
          mutedText: theme.colorScheme.onSurfaceVariant,
          success: Colors.green,
        );
    final chartRect = Rect.fromLTWH(8, 10, size.width - 16, size.height - 28);
    final gridPaint = Paint()
      ..color = tokens.border
      ..strokeWidth = 1;
    for (var index = 0; index < 4; index++) {
      final y = chartRect.top + chartRect.height * index / 3;
      canvas.drawLine(
        Offset(chartRect.left, y),
        Offset(chartRect.right, y),
        gridPaint,
      );
    }
    if (series.points.isEmpty) return;
    final values = series.points.map((point) => point.value).toList();
    final minValue = values.reduce(math.min);
    final maxValue = values.reduce(math.max);
    final range = maxValue == minValue ? 1 : maxValue - minValue;
    final offsets = <Offset>[
      for (var index = 0; index < series.points.length; index++)
        Offset(
          chartRect.left +
              (series.points.length == 1
                  ? chartRect.width / 2
                  : chartRect.width * index / (series.points.length - 1)),
          chartRect.bottom -
              ((series.points[index].value - minValue) / range) *
                  chartRect.height,
        ),
    ];
    if (offsets.length > 1) {
      final path = Path()..moveTo(offsets.first.dx, offsets.first.dy);
      for (final offset in offsets.skip(1)) {
        path.lineTo(offset.dx, offset.dy);
      }
      canvas.drawPath(
        path,
        Paint()
          ..color = theme.colorScheme.primary
          ..strokeWidth = 2.5
          ..style = PaintingStyle.stroke,
      );
    }
    for (final offset in offsets) {
      canvas.drawCircle(offset, 5, Paint()..color = tokens.accent);
      canvas.drawCircle(
        offset,
        5,
        Paint()
          ..color = theme.colorScheme.surface
          ..strokeWidth = 1.5
          ..style = PaintingStyle.stroke,
      );
    }
  }

  @override
  bool shouldRepaint(covariant _ProgressionChartPainter oldDelegate) {
    return oldDelegate.series != series || oldDelegate.theme != theme;
  }
}

final class _SeriesSummaryView extends StatelessWidget {
  const _SeriesSummaryView({required this.series});

  final ProgressionExerciseSeries series;

  @override
  Widget build(BuildContext context) {
    final labels = switch (series.summary.kind) {
      ProgressionSeriesSummaryKind.bestLast => [
        ('Meilleur', series.summary.best),
        ('Dernier', series.summary.last),
      ],
      ProgressionSeriesSummaryKind.totals => [
        ('Total récent', series.summary.recentTotal),
        ('Dernière séance', series.summary.lastSessionTotal),
      ],
      ProgressionSeriesSummaryKind.none => const <(String, Object?)>[],
    };
    if (labels.isEmpty) return const SizedBox.shrink();
    return Wrap(
      spacing: 10,
      runSpacing: 10,
      children: [
        for (final item in labels)
          _SummaryPill(
            label: item.$1,
            value: _formatMeasureValue(series.measure, item.$2),
          ),
      ],
    );
  }
}

final class _MetricTile extends StatelessWidget {
  const _MetricTile({required this.label, required this.value});

  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    final tokens = courtBlazerTokensOf(context);
    return DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(color: tokens.border),
        borderRadius: BorderRadius.circular(6),
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(label, style: Theme.of(context).textTheme.labelMedium),
            const SizedBox(height: 6),
            Text(
              value,
              style: AppTextStyles.scoreNumber(
                context,
              ).copyWith(color: Theme.of(context).colorScheme.primary),
            ),
          ],
        ),
      ),
    );
  }
}

final class _SummaryPill extends StatelessWidget {
  const _SummaryPill({required this.label, required this.value});

  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    final tokens = courtBlazerTokensOf(context);
    return DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(color: tokens.border),
        borderRadius: BorderRadius.circular(6),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
        child: Text('$label : $value'),
      ),
    );
  }
}

final class _ExerciseOptionLabel extends StatelessWidget {
  const _ExerciseOptionLabel({required this.option});

  final ProgressionExerciseOption option;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Flexible(child: Text(option.nameSnapshot)),
        if (option.isArchived) ...[
          const SizedBox(width: 8),
          const Chip(
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
            visualDensity: VisualDensity(horizontal: -4, vertical: -4),
            label: Text('Exercice archivé'),
          ),
        ],
      ],
    );
  }
}

final class _SectionTitle extends StatelessWidget {
  const _SectionTitle(this.label);

  final String label;

  @override
  Widget build(BuildContext context) {
    return Text(label, style: Theme.of(context).textTheme.titleMedium);
  }
}

final class _GlobalEmptyState extends StatelessWidget {
  const _GlobalEmptyState({required this.onOpenHistory});

  final VoidCallback onOpenHistory;

  @override
  Widget build(BuildContext context) {
    return _CenteredProgressionMessage(
      title: 'Aucune progression à afficher',
      message:
          'Termine une séance pour voir ton volume, ton rythme et tes résultats évoluer ici.',
      action: OutlinedButton(
        onPressed: onOpenHistory,
        child: const Text('Voir les séances'),
      ),
    );
  }
}

final class _CenteredProgressionMessage extends StatelessWidget {
  const _CenteredProgressionMessage({
    required this.title,
    required this.message,
    this.action,
  });

  final String title;
  final String message;
  final Widget? action;

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

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

  final String title;
  final String message;

  @override
  Widget build(BuildContext context) {
    final tokens = courtBlazerTokensOf(context);
    return DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(color: tokens.border),
        borderRadius: BorderRadius.circular(6),
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: Theme.of(context).textTheme.titleSmall),
            const SizedBox(height: 6),
            Text(message, style: Theme.of(context).textTheme.bodySmall),
          ],
        ),
      ),
    );
  }
}

(String, String)? _stateMessage(ProgressionSeriesState state) {
  return switch (state) {
    ProgressionSeriesState.ready => null,
    ProgressionSeriesState.singlePoint => (
      'Encore un point de départ',
      'Termine cet exercice dans une autre séance pour voir son évolution.',
    ),
    ProgressionSeriesState.emptyForPeriod => (
      'Pas encore de mesure exploitable',
      'Les graphiques apparaissent quand un exercice contient des répétitions, du temps ou un score numérique enregistré.',
    ),
    ProgressionSeriesState.emptyButHasAllTimeData => (
      'Peu de données sur cette période',
      'Passe sur "Tout" pour voir plus d\'historique.',
    ),
    ProgressionSeriesState.noGraphableMeasure => (
      'Pas encore de mesure exploitable',
      'Les graphiques apparaissent quand un exercice contient des répétitions, du temps ou un score numérique enregistré.',
    ),
    ProgressionSeriesState.textScoreOnly => (
      'Score non graphique',
      'Ce score est enregistré comme texte. Tu peux le retrouver dans le détail de l\'historique.',
    ),
  };
}

Rect _pointHitRect(Size size, List<ProgressionPoint> points, int index) {
  const hitSize = 44.0;
  final chartRect = Rect.fromLTWH(8, 10, size.width - 16, size.height - 28);
  if (points.isEmpty) return Rect.zero;
  final values = points.map((point) => point.value).toList();
  final minValue = values.reduce(math.min);
  final maxValue = values.reduce(math.max);
  final range = maxValue == minValue ? 1 : maxValue - minValue;
  final x =
      chartRect.left +
      (points.length == 1
          ? chartRect.width / 2
          : chartRect.width * index / (points.length - 1));
  final y =
      chartRect.bottom -
      ((points[index].value - minValue) / range) * chartRect.height;
  return Rect.fromCenter(center: Offset(x, y), width: hitSize, height: hitSize);
}

String _pointDetailLabel(
  ProgressionExerciseSeries series,
  ProgressionPoint point,
) {
  final label = switch (series.measure.measure) {
    ProgressionMeasure.manualScore => 'Meilleur score',
    ProgressionMeasure.stopwatchScore => 'Meilleur temps',
    ProgressionMeasure.reps => 'Répétitions',
    ProgressionMeasure.time => 'Temps',
  };
  return '$label : ${_formatMeasureValue(series.measure, point.rawValue)}';
}

String _formatMeasureValue(ProgressionMeasureOption measure, Object? rawValue) {
  if (rawValue == null) return '-';
  return switch (measure.measure) {
    ProgressionMeasure.manualScore =>
      '${_formatScore(rawValue as double)}${_scoreSuffix(measure.scoreUnit)}',
    ProgressionMeasure.stopwatchScore => _formatStopwatch(rawValue as int),
    ProgressionMeasure.reps => '${rawValue as int} répétitions',
    ProgressionMeasure.time => _formatVolumeDuration(rawValue as int),
  };
}

String _formatScore(double value) {
  if (value == value.roundToDouble()) {
    return value.round().toString();
  }
  return value.toStringAsFixed(1);
}

String _scoreSuffix(String? unit) {
  final value = unit?.trim();
  if (value == null || value.isEmpty) return '';
  return ' $value';
}

String _formatVolumeDuration(int milliseconds) {
  final duration = Duration(milliseconds: milliseconds);
  final hours = duration.inHours;
  final minutes = duration.inMinutes.remainder(60);
  if (hours > 0) {
    return minutes == 0
        ? '$hours h'
        : '$hours h ${minutes.toString().padLeft(2, '0')}';
  }
  return '${minutes.toString().padLeft(2, '0')} min';
}

String _formatStopwatch(int milliseconds) {
  final totalTenths = (milliseconds / 100).floor();
  final minutes = (totalTenths ~/ 600).toString().padLeft(2, '0');
  final seconds = ((totalTenths ~/ 10) % 60).toString().padLeft(2, '0');
  final tenths = totalTenths % 10;
  return '$minutes:$seconds.$tenths';
}

String _formatDate(DateTime value) {
  const months = [
    'janvier',
    'février',
    'mars',
    'avril',
    'mai',
    'juin',
    'juillet',
    'août',
    'septembre',
    'octobre',
    'novembre',
    'décembre',
  ];
  return '${value.day} ${months[value.month - 1]}';
}
