import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/phase_event.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/phase_state.dart';
import 'package:hl_lieferservice/feature/delivery/model/delivery_phase.dart';
import 'package:hl_lieferservice/feature/delivery/overview/service/phase_service.dart';

/// Liefert die Anzahl der dem aktuellen Fahrer-Team zugeordneten Fahrzeuge.
/// Wird vom [PhaseBloc] bei der Ermittlung der Eintrittsphase aufgerufen.
///
/// Optional: ist die Tour noch nicht geladen oder die Anzahl unbekannt,
/// liefert die Funktion `null` zurück — der BLoC verwendet dann den
/// Default-Eintritt [DeliveryPhase.sortieren].
typedef CarCountResolver = int? Function();

/// Liefert einen Token, der die aktuell geladene Tour-Version identifiziert
/// (typischerweise aus `Tour.syncedAt`). Der [PhaseService] bindet die
/// persistierten Phasen an diesen Token — ein neuer ERP-Sync / Demo-Seed
/// erzeugt einen neuen Token und damit einen frischen Phasen-Stand.
///
/// Liefert `null`, wenn (noch) keine Tour geladen ist; der BLoC nutzt dann
/// einen neutralen Fallback-Token.
typedef TourTokenResolver = String? Function();

/// Zentraler State für die aktuelle Phase je Fahrzeug. Persistiert über
/// [PhaseService] auf tour-token-bezogene SharedPreferences-Keys (siehe Service).
///
/// Eintrittsphase nach Fahrzeugauswahl:
/// * 1 Auto im Team  → [DeliveryPhase.sortieren] (bisheriges Verhalten).
/// * ≥2 Autos        → [DeliveryPhase.auswaehlen] (neuer Auswahl-Schritt).
///
/// Ist bereits eine Phase persistiert, wird diese verwendet (Resume nach
/// Neustart der App). Die Eintrittslogik greift also nur beim "ersten Load
/// des Tages" für ein Fahrzeug.
class PhaseBloc extends Bloc<PhaseEvent, PhaseState> {
  final PhaseService phaseService;

  /// Liefert die aktuelle Anzahl der Team-Fahrzeuge. Wird vom umgebenden
  /// Provider so verdrahtet, dass sie aus dem [TourBloc] kommt.
  final CarCountResolver? carCountResolver;

  /// Liefert den Tour-Token (aus `Tour.syncedAt`). Bindet die persistierten
  /// Phasen an die aktuelle Tour-Version.
  final TourTokenResolver? tourTokenResolver;

  PhaseBloc({
    PhaseService? phaseService,
    this.carCountResolver,
    this.tourTokenResolver,
  })  : phaseService = phaseService ?? PhaseService(),
        super(PhaseInitial()) {
    on<PhaseLoadForCar>(_load);
    on<PhaseLoaded>(_applyLoaded);
    on<PhaseSet>(_set);
  }

  /// Aktueller Tour-Token oder neutraler Fallback, falls noch keine Tour
  /// geladen ist (z. B. `TourEmpty`) — dort ist die Phase ohnehin
  /// bedeutungslos.
  String _token() => tourTokenResolver?.call() ?? 'no-tour';

  PhaseReady _ensureReady() {
    final current = state;
    return current is PhaseReady
        ? current
        : PhaseReady(phaseByCar: const {});
  }

  /// Bestimmt die initiale Phase für ein frisch ausgewähltes Fahrzeug.
  /// Sobald die Tour bekannt ist und ≥2 Fahrzeuge enthält, startet der
  /// Fahrer im Auswahl-Schritt; sonst direkt im Sortieren.
  DeliveryPhase _entryPhase() {
    final count = carCountResolver?.call();
    if (count != null && count >= 2) return DeliveryPhase.auswaehlen;
    return DeliveryPhase.sortieren;
  }

  Future<void> _load(PhaseLoadForCar event, Emitter<PhaseState> emit) async {
    final current = _ensureReady();
    // Wenn bereits geladen, nichts tun — der Stepper-Tap entscheidet aktiv.
    if (current.phaseByCar.containsKey(event.carId)) return;

    try {
      final token = _token();
      final persisted = await phaseService.load(event.carId, token);
      final persistedMax = await phaseService.loadMax(event.carId, token);
      final phase = persisted ?? _entryPhase();
      // Max ist mindestens die aktuelle Phase. Falls in der Persistenz ein
      // höherer Wert steht (Rücksprung), den nehmen.
      DeliveryPhase maxPhase = phase;
      if (persistedMax != null && persistedMax.index > maxPhase.index) {
        maxPhase = persistedMax;
      }

      if (persisted == null) {
        // Erste Phase nach Fahrzeugauswahl direkt persistieren, damit
        // ein Resume nach App-Neustart die Phase kennt.
        await phaseService.save(event.carId, token, phase);
        await phaseService.saveMax(event.carId, token, maxPhase);
      } else if (persistedMax == null) {
        // Migration: alte Tage ohne Max-Tracking → einmalig nachziehen.
        await phaseService.saveMax(event.carId, token, maxPhase);
      }

      add(PhaseLoaded(
        carId: event.carId,
        phase: phase,
        maxPhase: maxPhase,
      ));
    } catch (e, st) {
      debugPrint("PhaseBloc._load: $e $st");
      // Fail-soft: ohne Persistenz weiter, damit der Flow nicht hängen bleibt.
      final fallback = _entryPhase();
      add(PhaseLoaded(
        carId: event.carId,
        phase: fallback,
        maxPhase: fallback,
      ));
    }
  }

  void _applyLoaded(PhaseLoaded event, Emitter<PhaseState> emit) {
    final current = _ensureReady();
    emit(current.withLoaded(event.carId, event.phase, event.maxPhase));
  }

  Future<void> _set(PhaseSet event, Emitter<PhaseState> emit) async {
    final current = _ensureReady();
    final next = current.withPhase(event.carId, event.phase);
    emit(next);
    try {
      final token = _token();
      await phaseService.save(event.carId, token, event.phase);
      // withPhase hat das Max ggf. hochgezogen — persistieren, damit ein
      // Neustart die "höchste erreichte Phase" kennt.
      final newMax = next.maxPhaseFor(event.carId);
      if (newMax != null) {
        await phaseService.saveMax(event.carId, token, newMax);
      }
    } catch (e, st) {
      debugPrint("PhaseBloc._set: $e $st");
      // UI bleibt konsistent, Persistenz-Fehler ignorieren wir bewusst —
      // beim nächsten Setzen wird erneut versucht.
    }
  }
}
