import 'address.dart';
import 'delivery_item.dart';

/// Lebenszyklus einer Lieferung.
///
/// - `active`: Standard nach Anlage; Fahrer kann scannen/ausliefern.
/// - `held`: Pausiert (Kunde nicht da, Termin verschoben) — kein Bearbeitungsfortschritt.
/// - `canceled`: Abgebrochen — wird nicht mehr ausgeliefert.
/// - `completed`: Abgeschlossen — Signatur und Notizen sind hinterlegt.
enum DeliveryState { active, held, canceled, completed }

/// Eine einzelne Auslieferung an einen Kunden innerhalb einer Tour.
///
/// Anders als im alten Modell trägt `Delivery` hier ausschließlich
/// Logistik-Daten — keine Preise, keine Rabatte, keine Zahlungsoptionen.
/// Diese ERP-Themen sind in Phase C+D-2 absichtlich nicht migriert und
/// hängen hinter `FeatureFlags`.
class Delivery {
  const Delivery({
    required this.id,
    required this.tourId,
    required this.customerId,
    required this.contactPersonIds,
    required this.deliveryAddressSnapshot,
    required this.erpBelegartId,
    required this.erpBelegnummer,
    required this.state,
    required this.sortOrder,
    required this.items,
    required this.prepaidAmount,
    required this.paymentMethodId,
    this.assignedCarId,
    this.desiredTime,
    this.specialAgreements,
    this.stateReason,
  });

  final String id;
  final String tourId;
  final String customerId;

  /// 0..n Kontakte am Kunden, die für diese Lieferung relevant sind.
  /// Lookup über `TourDetails.contactById`.
  final List<String> contactPersonIds;

  /// Eingefrorene Lieferadresse zum Zeitpunkt der Belegerzeugung — bleibt
  /// stabil, auch wenn die Stammadresse am Kunden später geändert wird.
  final Address deliveryAddressSnapshot;

  /// ERP-Belegart (Lieferschein, Rechnung, …) und -Nummer. Für die App nur
  /// informativ; in Notizen/Reklamationen ist die Belegnummer der vom
  /// Kunden verständliche Bezugspunkt.
  final int erpBelegartId;
  final String erpBelegnummer;

  final DeliveryState state;

  /// Optionaler Klartext, warum `state` auf `held`/`canceled` steht. Vom
  /// Backend nicht-leer erzwungen, sobald ein Reason-pflichtiger Zustand
  /// gesetzt wird.
  final String? stateReason;

  /// Sortier-Reihenfolge innerhalb der Tour, gesetzt durch
  /// `PUT /tours/{id}/delivery-order`. Niedriger = früher.
  final int sortOrder;

  /// UUID des Fahrzeugs, dem diese Lieferung beim Laden zugewiesen wurde.
  /// `null` = noch nicht zugewiesen.
  final String? assignedCarId;

  /// Bei Bestellung schon bezahlter Betrag in EUR. `0.0` wenn der Kunde
  /// alles bei Lieferung zahlt. Wird vom ERP-Sync gesetzt.
  final double prepaidAmount;

  /// FK auf eine `PaymentMethod` (UUID). Auflösung zu Display-Name und
  /// Aktiv-Status geht über die Stammdaten-Liste, die die App separat
  /// lädt — nicht hier embeddet, damit das Tour-Aggregat klein bleibt.
  final String paymentMethodId;

  final String? desiredTime;
  final String? specialAgreements;

  final List<DeliveryItem> items;

  // ─── Abgeleitete Sicht-Eigenschaften ──────────────────────────────────

  /// Nur Items, die der Treiber tatsächlich scannen muss. Nicht-scanbare
  /// Artikel (Dienstleistungen, Versand) sowie bereits entfernte Items
  /// werden nicht mitgezählt.
  Iterable<DeliveryItem> scannableItems(
    bool Function(String articleId) isScannable,
  ) sync* {
    for (final item in items) {
      if (item.isRemoved) continue;
      if (!isScannable(item.articleId)) continue;
      yield item;
    }
  }

  /// `true`, sobald *alle* scanbaren Items dieser Lieferung als `done`
  /// markiert sind. Wird in der Loading-Übersicht angezeigt und
  /// kontrolliert in der Detail-Phase den Übergang zur Signatur.
  bool allScannableItemsDone(bool Function(String articleId) isScannable) {
    final scannables = scannableItems(isScannable).toList();
    if (scannables.isEmpty) return false;
    return scannables.every((item) => item.isDone);
  }

  Delivery copyWith({
    String? id,
    String? tourId,
    String? customerId,
    List<String>? contactPersonIds,
    Address? deliveryAddressSnapshot,
    int? erpBelegartId,
    String? erpBelegnummer,
    DeliveryState? state,
    String? stateReason,
    int? sortOrder,
    String? assignedCarId,
    Object? desiredTime = _sentinel,
    Object? specialAgreements = _sentinel,
    List<DeliveryItem>? items,
    double? prepaidAmount,
    String? paymentMethodId,
  }) {
    return Delivery(
      id: id ?? this.id,
      tourId: tourId ?? this.tourId,
      customerId: customerId ?? this.customerId,
      contactPersonIds: contactPersonIds ?? this.contactPersonIds,
      deliveryAddressSnapshot: deliveryAddressSnapshot ?? this.deliveryAddressSnapshot,
      erpBelegartId: erpBelegartId ?? this.erpBelegartId,
      erpBelegnummer: erpBelegnummer ?? this.erpBelegnummer,
      state: state ?? this.state,
      stateReason: stateReason ?? this.stateReason,
      sortOrder: sortOrder ?? this.sortOrder,
      assignedCarId: assignedCarId ?? this.assignedCarId,
      desiredTime: identical(desiredTime, _sentinel)
          ? this.desiredTime
          : desiredTime as String?,
      specialAgreements: identical(specialAgreements, _sentinel)
          ? this.specialAgreements
          : specialAgreements as String?,
      items: items ?? this.items,
      prepaidAmount: prepaidAmount ?? this.prepaidAmount,
      paymentMethodId: paymentMethodId ?? this.paymentMethodId,
    );
  }
}

const Object _sentinel = Object();
