import 'dart:async';

import 'package:watch_bridge_contract/watch_bridge_contract.dart';

abstract interface class SessionNotificationGateway {
  Future<void> show(SessionNotificationContent content);

  Future<void> clear();
}

final class SessionNotificationContent {
  const SessionNotificationContent({
    required this.title,
    required this.primaryLine,
    this.secondaryLine,
  });

  final String title;
  final String primaryLine;
  final String? secondaryLine;

  Map<String, Object?> toJson() {
    return {
      'title': title,
      'primaryLine': primaryLine,
      'secondaryLine': secondaryLine,
    };
  }
}

final class SessionNotificationCoordinator {
  SessionNotificationCoordinator({
    required Stream<WatchSessionProjection> projections,
    required SessionNotificationGateway gateway,
    Duration tickInterval = const Duration(seconds: 1),
  }) : _projections = projections,
       _gateway = gateway,
       _tickInterval = tickInterval;

  final Stream<WatchSessionProjection> _projections;
  final SessionNotificationGateway _gateway;
  final Duration _tickInterval;
  StreamSubscription<WatchSessionProjection>? _subscription;
  Timer? _timer;
  WatchSessionProjection? _latestProjection;

  void start() {
    if (_subscription != null) {
      return;
    }
    _subscription = _projections.listen(_handleProjection);
  }

  Future<void> dispose() async {
    _timer?.cancel();
    _timer = null;
    await _subscription?.cancel();
    _subscription = null;
  }

  void _handleProjection(WatchSessionProjection projection) {
    _latestProjection = projection;
    if (projection.phase == WatchSessionPhase.noActiveSession ||
        projection.deviceSessionId.isEmpty) {
      _timer?.cancel();
      _timer = null;
      unawaited(_gateway.clear().catchError((_) {}));
      return;
    }
    _show(projection);
    if (_timerShouldRun(projection)) {
      _timer ??= Timer.periodic(_tickInterval, (_) {
        final latest = _latestProjection;
        if (latest != null) {
          _show(latest);
        }
      });
    } else {
      _timer?.cancel();
      _timer = null;
    }
  }

  void _show(WatchSessionProjection projection) {
    unawaited(
      _gateway
          .show(buildSessionNotificationContent(projection))
          .catchError((_) {}),
    );
  }
}

SessionNotificationContent buildSessionNotificationContent(
  WatchSessionProjection projection, {
  DateTime? now,
}) {
  final phase = projection.phase;
  final paused =
      phase == WatchSessionPhase.paused ||
      phase == WatchSessionPhase.restPaused;
  final timer = projection.dominantTimer;
  final title =
      phase == WatchSessionPhase.restRunning ||
          phase == WatchSessionPhase.restPaused
      ? 'Repos'
      : projection.exerciseName.isEmpty
      ? 'Séance en cours'
      : projection.exerciseName;
  final primary = switch (phase) {
    WatchSessionPhase.restRunning ||
    WatchSessionPhase.restPaused => _restLine(projection, now: now),
    _ when timer != null => _timerText(timer, now: now),
    _ => _measureLine(projection),
  };
  return SessionNotificationContent(
    title: title,
    primaryLine: paused ? 'En pause · $primary' : primary,
    secondaryLine: _secondaryLine(projection),
  );
}

bool _timerShouldRun(WatchSessionProjection projection) {
  final timer = projection.dominantTimer;
  return timer != null && timer.runState == WatchTimerRunState.running;
}

String _restLine(WatchSessionProjection projection, {DateTime? now}) {
  final timer = projection.dominantTimer;
  final value = timer == null ? '--:--' : _timerText(timer, now: now);
  final next = projection.nextExerciseName;
  if (next == null || next.isEmpty) {
    return '$value restant';
  }
  return '$value restant · Ensuite : $next';
}

String _measureLine(WatchSessionProjection projection) {
  final score = projection.currentManualScoreValue;
  if (projection.hasManualScore && score != null) {
    return 'Série ${projection.seriesIndex}/${projection.seriesTotal} · ${_scoreText(score)}';
  }
  if (projection.stepName case final stepName? when stepName.isNotEmpty) {
    return stepName;
  }
  if (projection.seriesIndex > 0 && projection.seriesTotal > 0) {
    return 'Série ${projection.seriesIndex}/${projection.seriesTotal}';
  }
  return projection.statusLabel ?? 'Séance en cours';
}

String? _secondaryLine(WatchSessionProjection projection) {
  final parts = <String>[];
  if (projection.seriesIndex > 0 && projection.seriesTotal > 0) {
    parts.add('Série ${projection.seriesIndex}/${projection.seriesTotal}');
  }
  if (projection.stepIndex != null && projection.stepTotal != null) {
    parts.add('Étape ${projection.stepIndex}/${projection.stepTotal}');
  }
  return parts.isEmpty ? null : parts.join(' · ');
}

String _timerText(WatchTimerProjection timer, {DateTime? now}) {
  final duration = _displayDuration(timer, now: now);
  final totalSeconds = duration.inSeconds;
  final minutes = (totalSeconds ~/ 60).toString().padLeft(2, '0');
  final seconds = (totalSeconds % 60).toString().padLeft(2, '0');
  return '$minutes:$seconds';
}

Duration _displayDuration(WatchTimerProjection timer, {DateTime? now}) {
  final elapsed = _elapsedMs(timer, now: now);
  if (timer.displayMode == WatchTimerDisplayMode.countdown &&
      timer.targetMs != null) {
    return Duration(
      milliseconds: (timer.targetMs! - elapsed).clamp(0, 1 << 31).toInt(),
    );
  }
  return Duration(milliseconds: elapsed);
}

int _elapsedMs(WatchTimerProjection timer, {DateTime? now}) {
  if (timer.runState != WatchTimerRunState.running ||
      timer.startedAtEpochMs == null) {
    return timer.accumulatedMs;
  }
  final reference =
      now?.toUtc().millisecondsSinceEpoch ??
      DateTime.now().toUtc().millisecondsSinceEpoch;
  return timer.accumulatedMs +
      (reference - timer.startedAtEpochMs!).clamp(0, 1 << 31).toInt();
}

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