import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:hl_lieferservice/domain/entity/article.dart';
import 'package:hl_lieferservice/domain/entity/customer.dart';
import 'package:hl_lieferservice/domain/entity/delivery.dart';
import 'package:hl_lieferservice/domain/entity/delivery_item.dart';
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
import 'package:hl_lieferservice/domain/entity/warehouse.dart';
import 'package:hl_lieferservice/feature/car_selection/bloc/bloc.dart';
import 'package:hl_lieferservice/feature/car_selection/bloc/state.dart';
import 'package:hl_lieferservice/feature/cars/bloc/cars_bloc.dart';
import 'package:hl_lieferservice/feature/cars/bloc/cars_state.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/phase_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/phase_event.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart';
import 'package:hl_lieferservice/feature/delivery/model/delivery_phase.dart';
import 'package:hl_lieferservice/feature/loading/widget/reason_catalog.dart';
import 'package:hl_lieferservice/feature/loading/widget/reason_picker_sheet.dart';
import 'package:hl_lieferservice/widget/scanner/article_scanner_stripe.dart';
import 'package:hl_lieferservice/widget/scanner/item_matcher.dart';
import 'package:hl_lieferservice/widget/scanner/manual_entry_dialog.dart';
import 'package:hl_lieferservice/widget/scanner/scan_code_parser.dart';

/// Vollbild-Sicht eines Kunden in der Beladen-Phase mit aktivem Scanner.
///
/// Aufbau:
/// 1. **Scanner-Stripe** oben — *außerhalb* des PageView. Damit gibt es
///    nur eine einzige Kamera-Instanz im Page-Lebenszyklus; das Wischen
///    zwischen Kunden lässt den Stream nicht abreißen. (Würde der Stripe
///    pro PageView-Page neu instanziiert, käme er sich mit den von
///    PageView vorgeladenen Nachbarseiten ins Gehege und der Viewport
///    würde weiß.)
/// 2. **Zoom-Bar** direkt unter dem Viewport — `-` / Slider / `+` für
///    Daumenbedienung. Gebunden an `MobileScannerController.setZoomScale`.
/// 3. **PageView** mit Kundenkopf + Item-Liste pro Lieferung.
///
/// Scan-Pipeline:
/// - Barcode = Artikelnummer.
/// - UI sucht in der **aktuell sichtbaren** Lieferung das erste nicht
///   fertig gescannte Item mit dieser Article-Nr und feuert `ScanItem`.
/// - Bloc inkrementiert lokal sofort, ruft das Backend, rollt bei
///   `rejected` zurück.
class LoadingCustomerPage extends StatefulWidget {
  const LoadingCustomerPage({super.key, this.initialIndex = 0});

  final int initialIndex;

  @override
  State<StatefulWidget> createState() => _LoadingCustomerPageState();
}

class _LoadingCustomerPageState extends State<LoadingCustomerPage> {
  late final PageController _pageController =
      PageController(initialPage: widget.initialIndex);

  /// Index der gerade sichtbaren Lieferung. Wird vom PageView gesetzt und
  /// vom (lebenslang einen) Scanner für die Barcode-Auflösung benutzt.
  int _currentIndex = 0;

  /// Verhindert, dass der „Alles gescannt"-Abschluss-Dialog mehrfach erscheint.
  /// Wird zurückgesetzt, sobald wieder etwas zu beladen offen ist.
  bool _completionPromptShown = false;

  @override
  void initState() {
    super.initState();
    _currentIndex = widget.initialIndex;
  }

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

  bool _multiCarTeam(BuildContext context) {
    final state = context.read<CarsBloc>().state;
    return state is CarsLoaded && state.cars.length >= 2;
  }

  List<Delivery> _ownInLoadingOrder(
    BuildContext context,
    TourDetails details,
    String carId,
  ) {
    final relevant = _multiCarTeam(context)
        ? details.deliveriesSorted
            .where((d) => d.assignedCarId == carId)
            .toList()
        : details.deliveriesSorted;
    return relevant.reversed.toList();
  }

  /// Verarbeitet einen Scan-Code (Kamera **oder** Manual-Entry).
  ///
  /// Validierung: das geparste Tripel **Artikelnummer × Kundennummer ×
  /// Belegnummer** muss *exakt* zur aktuell sichtbaren Lieferung passen.
  /// Schon ein Mismatch in einer der drei Dimensionen → Snackbar
  /// „nicht vorgesehen". Das schützt den Fahrer davor, versehentlich
  /// einen QR-Code für eine **andere** Lieferung anzuwenden — die alte
  /// Logik („alles, was die Artikelnummer kennt, wird gescannt") wäre
  /// gegen Verwechslungen wehrlos.
  ///
  /// `customer.erpCustomerId` = `Kunden.Kundennummer` aus dem ERP-Sync
  /// (entspricht der Kundennummer im QR-Mittelfeld).
  void _onBarcode({
    required String code,
    required List<Delivery> deliveries,
    required TourDetails details,
    required String carId,
  }) {
    if (deliveries.isEmpty) return;
    final safeIndex = _currentIndex.clamp(0, deliveries.length - 1);
    final delivery = deliveries[safeIndex];
    final customer = details.customerOf(delivery);

    final parsed = parseScanCode(code);
    // Format-Fehler oder Konstellation passt nicht zur aktuell sichtbaren
    // Lieferung → eindeutig „nicht vorgesehen".
    if (parsed == null ||
        customer?.erpCustomerId != parsed.customerErpId ||
        delivery.erpBelegnummer != parsed.beleg) {
      _showScanSnackbar(_notIntendedMessage);
      return;
    }

    final match = matchItem(
      delivery: delivery,
      details: details,
      articleNumber: parsed.articleNumber,
    );
    switch (match) {
      case ItemMatchOk(:final item):
        context
            .read<TourBloc>()
            .add(ScanItem(deliveryItemId: item.id, actorCarId: carId));
      case ItemMatchNotInDelivery():
        _showScanSnackbar(_notIntendedMessage);
      case ItemMatchNotScannable():
        _showScanSnackbar(
          'Diese Position ist nicht zum Scannen vorgesehen '
          '(Dienstleistung / Pauschale).',
        );
      case ItemMatchAllDone():
        _showScanSnackbar('Diese Position ist bereits vollständig gescannt.');
      case ItemMatchAllRemoved():
        _showScanSnackbar('Diese Position wurde aus der Lieferung entfernt.');
      case ItemMatchNotOpen():
        _showScanSnackbar('Diese Position ist nicht (mehr) offen.');
    }
  }

  static const String _notIntendedMessage =
      'Dieser Artikel ist für diese Lieferung nicht vorgesehen';

  void _showScanSnackbar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        duration: const Duration(seconds: 2),
      ),
    );
  }

  Future<void> _openManualEntry({
    required List<Delivery> deliveries,
    required TourDetails details,
    required String carId,
  }) async {
    final code = await showManualEntryDialog(context);
    if (code == null || code.isEmpty) return;
    if (!mounted) return;
    _onBarcode(
      code: code,
      deliveries: deliveries,
      details: details,
      carId: carId,
    );
  }

  // ─── Delivery-Lifecycle-Aktionen ──────────────────────────────────────

  Future<void> _onHoldDelivery(Delivery delivery) async {
    final result = await showReasonPickerSheet(
      context: context,
      title: 'Lieferung pausieren',
      presets: ReasonCatalog.deliveryHold,
      confirmLabel: 'Pausieren',
    );
    if (result == null || !mounted) return;
    context
        .read<TourBloc>()
        .add(HoldDelivery(deliveryId: delivery.id, reason: result.reason));
  }

  Future<void> _onCancelDelivery(Delivery delivery) async {
    // Cancel ist endgültig — Bestätigungsschritt vor dem Reason-Picker.
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Lieferung abbrechen?'),
        content: const Text(
          'Eine abgebrochene Lieferung kann nicht wieder aktiviert werden.',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(ctx).pop(false),
            child: const Text('Zurück'),
          ),
          FilledButton(
            style: FilledButton.styleFrom(
              backgroundColor: Theme.of(ctx).colorScheme.error,
              foregroundColor: Theme.of(ctx).colorScheme.onError,
            ),
            onPressed: () => Navigator.of(ctx).pop(true),
            child: const Text('Weiter'),
          ),
        ],
      ),
    );
    if (confirmed != true || !mounted) return;

    final result = await showReasonPickerSheet(
      context: context,
      title: 'Grund für Abbruch',
      presets: ReasonCatalog.deliveryCancel,
      confirmLabel: 'Lieferung abbrechen',
    );
    if (result == null || !mounted) return;
    context
        .read<TourBloc>()
        .add(CancelDelivery(deliveryId: delivery.id, reason: result.reason));
  }

  Future<void> _onResumeDelivery(Delivery delivery) async {
    // Bei einer abgebrochenen Lieferung ist die Wiederherstellung
    // semantisch riskanter als ein normales Hold-Resume — der Cancel
    // wurde vom Fahrer aktiv bestätigt. Wir holen deshalb eine zweite
    // Zustimmung ein, bevor wir das Resume feuern. Beim Hold-Resume
    // sparen wir den Schritt: alltäglich und reversibel.
    if (delivery.state == DeliveryState.canceled) {
      final confirmed = await showDialog<bool>(
        context: context,
        builder: (ctx) => AlertDialog(
          title: const Text('Lieferung wiederherstellen?'),
          content: Text(
            'Die Lieferung wurde abgebrochen'
            '${delivery.stateReason != null ? ' (Grund: ${delivery.stateReason})' : ''}.'
            '\n\nWiederhergestellte Lieferungen erscheinen wieder in der '
            'Beladen-Phase. Die ursprüngliche Cancel-Begründung wird dabei '
            'gelöscht.',
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(ctx).pop(false),
              child: const Text('Abbrechen'),
            ),
            FilledButton(
              onPressed: () => Navigator.of(ctx).pop(true),
              child: const Text('Wiederherstellen'),
            ),
          ],
        ),
      );
      if (confirmed != true || !mounted) return;
    }
    context.read<TourBloc>().add(ResumeDelivery(deliveryId: delivery.id));
  }

  // ─── Item-Aktionen ────────────────────────────────────────────────────

  /// Wird vom `PopupMenuButton` der jeweiligen Item-Row angeruft. Welche
  /// Optionen verfügbar sind, entscheidet die Row selbst über den
  /// aktuellen `ScanStatus` — hier landet nur die schon ausgewählte
  /// Aktion zur Verarbeitung.
  Future<void> _onItemAction({
    required DeliveryItem item,
    required _ItemAction action,
    required String carId,
  }) async {
    switch (action) {
      case _ItemAction.remove:
        // Restmenge, die noch gutgeschrieben/entfernt werden kann.
        final remaining =
            item.requiredQuantity - item.scanProgress.creditedQuantity;
        final result = await showReasonPickerSheet(
          context: context,
          title: 'Grund für das Entfernen',
          presets: ReasonCatalog.itemRemove,
          confirmLabel: 'Entfernen',
          maxQuantity: remaining,
        );
        if (result == null || !mounted) return;
        context.read<TourBloc>().add(RemoveItem(
              deliveryItemId: item.id,
              actorCarId: carId,
              reason: result.reason,
              quantity: result.quantity,
            ));
      case _ItemAction.unremove:
        context.read<TourBloc>().add(UnremoveItem(
              deliveryItemId: item.id,
              actorCarId: carId,
            ));
      case _ItemAction.manualConfirm:
        // Fallback ohne Barcode: die ganze Restmenge manuell als geladen
        // bestätigen. Bewusste Aussage → kurzer Bestätigungs-Dialog; das
        // Backend protokolliert den Scan als `manual`.
        final confirmed = await showDialog<bool>(
          context: context,
          builder: (ctx) => AlertDialog(
            title: const Text('Manuell bestätigen'),
            content: const Text(
              'Diese Position ohne Scan als vollständig geladen markieren? '
              'Das wird als manuelle Bestätigung protokolliert.',
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.of(ctx).pop(false),
                child: const Text('Abbrechen'),
              ),
              FilledButton(
                onPressed: () => Navigator.of(ctx).pop(true),
                child: const Text('Als geladen bestätigen'),
              ),
            ],
          ),
        );
        if (confirmed != true || !mounted) return;
        context.read<TourBloc>().add(ScanItem(
              deliveryItemId: item.id,
              actorCarId: carId,
              manual: true,
            ));
    }
  }

  /// Reagiert auf jedes TourBloc-Update: Sind ALLE eigenen, **aktiven**
  /// Lieferungen im Standardlager fertig beladen (dieselbe Bedingung wie der
  /// „Auslieferungs-Phase starten"-Gate der Beladen-Übersicht)? Genau beim
  /// Übergang „noch offen → alles fertig" (also nach dem letzten Pflicht-Scan)
  /// erscheint einmalig der Abschluss-Dialog.
  void _maybePromptLoadingComplete(
    BuildContext context,
    TourState state,
    String carId,
  ) {
    if (state is! TourLoaded) return;
    final deliveries = _ownInLoadingOrder(context, state.details, carId);
    final active =
        deliveries.where((d) => d.state == DeliveryState.active).toList();
    final allDone = active.isNotEmpty &&
        active.every(state.details.standardWarehouseLoadingDone);

    if (!allDone) {
      // Wieder etwas offen (Item entfernt/zurückgesetzt) → Dialog darf erneut.
      _completionPromptShown = false;
      return;
    }
    if (_completionPromptShown) return;
    _completionPromptShown = true;
    _showLoadingCompleteDialog(carId);
  }

  /// Abschluss-Dialog der Beladung: weist darauf hin, dass alles gescannt ist,
  /// und fragt, ob die Auslieferung gestartet werden soll. Bei „Ja" wird die
  /// Phase auf `ausliefern` gesetzt (PhaseBloc, app-weit) und der Scanner
  /// geschlossen — der Phasen-Router (`home`) zeigt dann die Auslieferungs-
  /// Übersicht. „Später" schließt nur den Dialog (Start wie bisher über die
  /// Übersicht möglich).
  Future<void> _showLoadingCompleteDialog(String carId) async {
    final start = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        icon: const Icon(Icons.check_circle, color: Colors.green, size: 40),
        title: const Text('Alles gescannt'),
        content: const Text(
          'Alle zu beladenden Artikel sind gescannt. '
          'Möchten Sie die Auslieferung starten?',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(ctx).pop(false),
            child: const Text('Später'),
          ),
          FilledButton(
            onPressed: () => Navigator.of(ctx).pop(true),
            child: const Text('Auslieferung starten'),
          ),
        ],
      ),
    );
    if (start != true || !mounted) return;
    context
        .read<PhaseBloc>()
        .add(PhaseSet(carId: carId, phase: DeliveryPhase.ausliefern));
    // Scanner schließen → zurück zum Phasen-Router, der nun 'ausliefern' zeigt.
    Navigator.of(context).pop();
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CarSelectBloc, CarSelectState>(
      builder: (context, carState) {
        final carId =
            carState is CarSelectComplete ? carState.selectedCar.id : '';
        return BlocConsumer<TourBloc, TourState>(
          listener: (context, tourState) =>
              _maybePromptLoadingComplete(context, tourState, carId),
          builder: (context, tourState) {
            if (tourState is! TourLoaded) {
              return const Scaffold(
                body: Center(child: CircularProgressIndicator()),
              );
            }
            final deliveries =
                _ownInLoadingOrder(context, tourState.details, carId);
            if (deliveries.isEmpty) {
              return Scaffold(
                appBar: AppBar(title: const Text('Beladung')),
                body: const Center(
                  child: Text('Keine Lieferungen zugewiesen.'),
                ),
              );
            }

            return Scaffold(
              // AppBar bewusst schlicht — die Lieferungs-Lifecycle-Aktionen
              // (Pausieren / Abbrechen / Wiederherstellen) sind in den
              // Kunden-Header gewandert, vertikal mittig rechts. So
              // hat der Fahrer den Bezug „Aktion betrifft DIESEN Kunden"
              // direkt vor Augen.
              //
              // Farbe primary statt M3-Default `surface`: die Loading-
              // Customer-Page ist eine Sub-Page der LoadingOverviewPage,
              // deren `PhaseStepper`-AppBar primary ist. Eine weiße
              // AppBar hier bricht den Flow visuell — primary stellt die
              // Konsistenz wieder her.
              appBar: AppBar(
                title: const Text('Beladung'),
                backgroundColor: Theme.of(context).colorScheme.primary,
                foregroundColor: Theme.of(context).colorScheme.onPrimary,
              ),
              body: Column(
                children: [
                  ArticleScannerStripe(
                    onBarcode: (code) => _onBarcode(
                      code: code,
                      deliveries: deliveries,
                      details: tourState.details,
                      carId: carId,
                    ),
                    onManualEntry: () => _openManualEntry(
                      deliveries: deliveries,
                      details: tourState.details,
                      carId: carId,
                    ),
                  ),
                  Expanded(
                    child: PageView.builder(
                      controller: _pageController,
                      onPageChanged: (i) =>
                          setState(() => _currentIndex = i),
                      itemCount: deliveries.length,
                      itemBuilder: (context, index) {
                        final delivery = deliveries[index];
                        return _CustomerBody(
                          delivery: delivery,
                          details: tourState.details,
                          position: index + 1,
                          totalCount: deliveries.length,
                          onItemAction: (item, action) => _onItemAction(
                            item: item,
                            action: action,
                            carId: carId,
                          ),
                          onHoldDelivery: () => _onHoldDelivery(delivery),
                          onCancelDelivery: () => _onCancelDelivery(delivery),
                          onResumeDelivery: () => _onResumeDelivery(delivery),
                        );
                      },
                    ),
                  ),
                ],
              ),
            );
          },
        );
      },
    );
  }
}

class _CustomerBody extends StatelessWidget {
  const _CustomerBody({
    required this.delivery,
    required this.details,
    required this.position,
    required this.totalCount,
    required this.onItemAction,
    required this.onHoldDelivery,
    required this.onCancelDelivery,
    required this.onResumeDelivery,
  });

  final Delivery delivery;
  final TourDetails details;

  /// 1-basierte Position dieser Lieferung in der Belade-Reihenfolge.
  final int position;

  /// Gesamtanzahl der Lieferungen — für „X von Y".
  final int totalCount;

  final void Function(DeliveryItem item, _ItemAction action) onItemAction;
  final VoidCallback onHoldDelivery;
  final VoidCallback onCancelDelivery;
  final VoidCallback onResumeDelivery;

  @override
  Widget build(BuildContext context) {
    final customer = details.customerOf(delivery);
    // Items werden vom Aggregat-Helper schon nach Lager gruppiert
    // geliefert: Standardlager zuerst, danach Filiale alphabetisch.
    // Nicht-scanbare Positionen und `removed`-Items sind dabei schon
    // ausgefiltert.
    final groups = details.itemsGroupedByWarehouse(delivery);

    // Nicht-scanbare Positionen (Dienstleistung / Pauschale / Fracht) — die
    // werden NICHT beladen/gescannt, sollen aber sichtbar sein, damit der
    // Fahrer auch eine reine Dienstleistungs-Lieferung als „echte Anfahrt"
    // erkennt. Liegen außerhalb von `groups` (die nur scanbare Items führen).
    final serviceItems = details.nonScannableItems(delivery).toList();

    // ── Set-Köpfe (Parent-Komponenten) immer mit ihrem Set anzeigen ──────
    // Ein nicht-scanbarer Set-Kopf (Stücklisten-Oberartikel, z. B. als
    // Pauschale geführt) soll NICHT isoliert unter „Dienstleistungen"
    // erscheinen, sondern als Kopf über seinen (scanbaren) Komponenten in der
    // jeweiligen Lagergruppe. Ein Item ist Set-Kopf, wenn seine Artikelnummer
    // von einer Komponente als parentArtikelNr referenziert wird.
    String? artNrOf(DeliveryItem it) =>
        details.articleOf(it.articleId)?.articleNumber;
    // Alle Set-Köpfe (Parent-Artikel): ein Item, dessen Artikelnummer von einer
    // Komponente als parentArtikelNr referenziert wird. Für nicht-scanbare
    // Set-Köpfe wird der „kein Scanvorgang notwendig"-Hinweis unterdrückt.
    final componentParentNrs = delivery.items
        .where((it) => it.isComponent)
        .map((it) => it.parentArtikelNr)
        .whereType<String>()
        .toSet();
    final setParentIds = delivery.items
        .where((it) {
          final nr = artNrOf(it);
          return nr != null && componentParentNrs.contains(nr);
        })
        .map((it) => it.id)
        .toSet();
    // Set-Kopf „komplett": alle scanbaren, nicht entfernten Komponenten fertig.
    // Dann wird auch der (selbst nicht scanbare) Kopf grün dargestellt.
    bool setParentComplete(DeliveryItem parent) {
      final nr = artNrOf(parent);
      if (nr == null) return false;
      final comps = delivery.items.where((c) =>
          c.parentArtikelNr == nr &&
          !c.isRemoved &&
          (details.articleOf(c.articleId)?.scannable ?? false));
      return comps.isNotEmpty && comps.every((c) => c.isDone);
    }
    // Set-Köpfe je Lagergruppe (warehouseId → einzuhängende Köpfe) +
    // gesammelte IDs, um sie aus der Dienstleistungs-Sektion zu entfernen.
    final injectedParentsByWarehouseId = <String, List<DeliveryItem>>{};
    final injectedParentIds = <String>{};
    for (final group in groups) {
      final groupParentNrs = group.items
          .where((it) => it.isComponent)
          .map((it) => it.parentArtikelNr)
          .whereType<String>()
          .toSet();
      if (groupParentNrs.isEmpty) continue;
      final parents = serviceItems
          .where((s) => groupParentNrs.contains(artNrOf(s)))
          .toList();
      if (parents.isEmpty) continue;
      injectedParentsByWarehouseId[group.warehouse.id] = parents;
      injectedParentIds.addAll(parents.map((p) => p.id));
    }
    // Set-Köpfe, die in eine Gruppe eingehängt wurden, nicht doppelt unter
    // „Dienstleistungen" zeigen. Set-Köpfe ohne scanbare Komponenten (kommen
    // hier nicht vor) blieben weiterhin in der Liste.
    final serviceItemsView = serviceItems
        .where((s) => !injectedParentIds.contains(s.id))
        .toList();

    return Column(
      children: [
        // Header bekommt einen eigenen, leicht abgehobenen Hintergrund
        // aus dem Material-3-Surface-Stack (`surfaceContainerHigh`).
        // Das hebt den Kunden-/Lieferungs-Block visuell vom Item-Body
        // ab — Dark-Mode-tauglich, ohne dass eine Hardcodierte Farbe
        // bei einem späteren Theme-Wechsel verkehrt aussieht.
        Container(
          width: double.infinity,
          color: Theme.of(context).colorScheme.surfaceContainerHigh,
          padding: const EdgeInsets.fromLTRB(16, 16, 4, 16),
          // Vertikal zentriert: Avatar links, Kunden-/Lieferungs-Info in
          // der Mitte, Aktions-Menü ganz rechts (statt wie früher im
          // AppBar oben). Der Bezug „diese Aktion betrifft genau DIESEN
          // Kunden" wird dadurch räumlich klar.
          child: Row(
            children: [
              _CustomerAvatar(customer: customer),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Lieferung $position von $totalCount',
                      style: Theme.of(context)
                          .textTheme
                          .labelMedium
                          ?.copyWith(
                            color: Theme.of(context).colorScheme.primary,
                            fontWeight: FontWeight.w600,
                            letterSpacing: 0.3,
                          ),
                    ),
                    const SizedBox(height: 2),
                    Text(
                      customer?.name ?? '⟨Unbekannter Kunde⟩',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    const SizedBox(height: 4),
                    Text(
                      delivery.deliveryAddressSnapshot.oneLine,
                      style: Theme.of(context).textTheme.bodyMedium,
                    ),
                    if (delivery.state != DeliveryState.active) ...[
                      const SizedBox(height: 8),
                      _DeliveryStateBadge(delivery: delivery),
                    ],
                  ],
                ),
              ),
              _DeliveryActionsMenu(
                delivery: delivery,
                onHold: onHoldDelivery,
                onCancel: onCancelDelivery,
                onResume: onResumeDelivery,
              ),
            ],
          ),
        ),
        // Kein expliziter Divider mehr — der farbige Header-Block trennt
        // sich schon visuell vom Item-Body.
        Expanded(
          // Eine einzige Scroll-Liste: zuerst die scanbaren Items je Lager
          // (Standardlager, dann Filiale), darunter — sofern vorhanden — die
          // gebuchten Dienstleistungen (nicht-scanbare Positionen). Die
          // Dienstleistungen werden GENAU SO wie Standardlager-Artikel
          // dargestellt, nur mit dem Hinweis „kein Scanvorgang notwendig". So
          // wirkt auch eine reine Dienstleistungs-Lieferung nicht leer.
          //
          // Wenig Items pro Lieferung → `ListView` mit children-Liste reicht
          // performant aus und ist lesbarer als ein Index-Builder.
          //
          // Bottom-Inset: kein Bottom-Bar in dieser Page → die System-
          // Navigationsleiste selbst freihalten.
          child: ListView(
            padding: EdgeInsets.fromLTRB(
              0,
              4,
              0,
              4 + MediaQuery.viewPaddingOf(context).bottom,
            ),
            children: [
              // Nur wenn es WEDER scanbare Ware NOCH eine Dienstleistung gibt,
              // ist wirklich nichts zu tun.
              if (groups.isEmpty && serviceItems.isEmpty)
                const _NothingToLoadHint(),
              for (final group in groups) ...[
                _WarehouseSectionHeader(
                  warehouse: group.warehouse,
                  items: group.items,
                ),
                // Nicht-scanbare Set-Köpfe dieser Gruppe vor ihre Komponenten
                // einhängen; `_parentFirst` ordnet Kopf vor Komponenten.
                for (final item in _parentFirst([
                  ...?injectedParentsByWarehouseId[group.warehouse.id],
                  ...group.items,
                ]))
                  _ItemRow(
                    item: item,
                    details: details,
                    onAction: (action) => onItemAction(item, action),
                    suppressScanHint: setParentIds.contains(item.id),
                    setParentComplete: setParentIds.contains(item.id) &&
                        setParentComplete(item),
                  ),
              ],
              // Gebuchte Dienstleistungen (nicht-scanbare Positionen ohne
              // eigene Set-Komponenten): eigene Zwischenüberschrift
              // „Dienstleistungen" (optisch wie Standardlager) und dieselbe
              // Item-Card wie scanbare Artikel — einziger Unterschied ist der
              // Hinweis, dass kein Scanvorgang nötig ist.
              if (serviceItemsView.isNotEmpty) ...[
                const _ServiceSectionHeader(),
                for (final item in _parentFirst(serviceItemsView))
                  _ItemRow(
                    item: item,
                    details: details,
                    onAction: (action) => onItemAction(item, action),
                    suppressScanHint: setParentIds.contains(item.id),
                    setParentComplete: setParentIds.contains(item.id) &&
                        setParentComplete(item),
                  ),
              ],
            ],
          ),
        ),
      ],
    );
  }
}

/// Sektions-Kopf vor den gebuchten Dienstleistungen (nicht-scanbare
/// Positionen) in der Beladen-Ansicht. Bewusst optisch identisch zum
/// `_WarehouseSectionHeader` („Standardlager"): gleiche Maße, gleiche
/// neutrale Farbe — die Dienstleistungen reihen sich damit nahtlos in die
/// Lager-Gliederung ein.
///
/// Anders als der Lager-Kopf trägt dieser KEINEN „fertig/gesamt"-Zähler
/// rechts: Dienstleistungen werden nicht gescannt, ein Fortschrittszähler
/// wäre irreführend. Stattdessen sagt der Subtitle direkt, dass hier kein
/// Scanvorgang nötig ist.
class _ServiceSectionHeader extends StatelessWidget {
  const _ServiceSectionHeader();

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final color = theme.colorScheme.onSurfaceVariant;

    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Icon(Icons.handyman_outlined, size: 18, color: color),
          const SizedBox(width: 6),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Dienstleistungen',
                  style: TextStyle(
                    fontSize: 13,
                    fontWeight: FontWeight.bold,
                    color: color,
                    letterSpacing: 0.4,
                  ),
                ),
                Text(
                  'Kein Scanvorgang notwendig',
                  style: TextStyle(
                    fontSize: 11,
                    color: theme.colorScheme.onSurfaceVariant,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

/// Vollbreiter Hinweis am unteren Rand einer Dienstleistungs-Card: diese
/// Position wird NICHT gescannt/beladen. Sitzt an genau der Stelle, an der
/// bei scanbaren Artikeln der „Manuell als geladen bestätigen"-Button steht
/// (der bei nicht-scanbaren Positionen entfällt) — so bleibt die Card-Geometrie
/// identisch zu den Standardlager-Artikeln, nur die Aussage ist eine andere.
class _ScanNotRequiredHint extends StatelessWidget {
  const _ScanNotRequiredHint();

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final color = theme.colorScheme.onSurfaceVariant;

    return Container(
      width: double.infinity,
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      decoration: BoxDecoration(
        color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.info_outline, size: 16, color: color),
          const SizedBox(width: 6),
          Flexible(
            child: Text(
              'Kein Scanvorgang notwendig',
              style: TextStyle(
                fontSize: 13,
                color: color,
                fontWeight: FontWeight.w500,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Grünes „Komplett geladen" für einen Set-Kopf (Parent-Artikel), dessen
/// (scanbare) Komponenten alle geladen sind. Ersetzt an dieser Stelle den
/// „Kein Scanvorgang notwendig"-Hinweis.
class _SetCompleteHint extends StatelessWidget {
  const _SetCompleteHint();

  @override
  Widget build(BuildContext context) {
    final color = Colors.green.shade700;
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      decoration: BoxDecoration(
        color: Colors.green.withValues(alpha: 0.10),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.check_circle, size: 16, color: color),
          const SizedBox(width: 6),
          Flexible(
            child: Text(
              'Komplett geladen',
              style: TextStyle(
                fontSize: 13,
                color: color,
                fontWeight: FontWeight.w600,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Sektions-Kopf vor den Items eines Lagers. Visuell klar getrennt
/// (Standardlager vs. Filiale): Standardlager ist der primäre
/// Arbeitsplatz und damit neutral koloriert; Filial-Sections
/// bekommen den Orange-Akzent und einen Hinweis-Text, dass sie nicht
/// am aktuellen Standort geladen werden.
///
/// Counter rechts zeigt „fertige Items / Items in dieser Sektion".
class _WarehouseSectionHeader extends StatelessWidget {
  const _WarehouseSectionHeader({
    required this.warehouse,
    required this.items,
  });

  final Warehouse warehouse;
  final List<DeliveryItem> items;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final isStandard = warehouse.isStandard;
    // Counter zählt nur **aktive** (nicht entfernte) Positionen — sonst
    // wäre eine Lieferung mit allen Items entfernt nie „fertig" (0 / N),
    // obwohl es nichts mehr zu beladen gibt. Entfernte Zeilen bleiben in
    // der Liste sichtbar (durchgestrichen), zählen aber nicht.
    final activeItems = items.where((it) => !it.isRemoved).toList();
    final doneCount = activeItems.where((it) => it.isDone).length;
    final color = isStandard
        ? theme.colorScheme.onSurfaceVariant
        : Colors.amber.shade800;

    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Icon(
            isStandard ? Icons.inventory_outlined : Icons.warehouse_outlined,
            size: 18,
            color: color,
          ),
          const SizedBox(width: 6),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  isStandard
                      ? 'Standardlager'
                      : 'Filiale: ${warehouse.name}',
                  style: TextStyle(
                    fontSize: 13,
                    fontWeight: FontWeight.bold,
                    color: color,
                    letterSpacing: 0.4,
                  ),
                ),
                Text(
                  isStandard
                      ? 'Hier wird jetzt beladen'
                      : 'Wird in der Filiale separat geholt',
                  style: TextStyle(
                    fontSize: 11,
                    color: theme.colorScheme.onSurfaceVariant,
                  ),
                ),
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(top: 1),
            child: Text(
              '$doneCount / ${activeItems.length}',
              style: TextStyle(
                fontSize: 12,
                fontWeight: FontWeight.w600,
                color: color,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Wird angezeigt, wenn eine Lieferung in der Beladen-Phase WEDER scanbare
/// Ware NOCH eine gebuchte Dienstleistung hat — also wirklich nichts zu tun
/// ist. Macht dem Fahrer klar, dass das ein normaler Zustand ist und kein
/// Datenfehler. (Gibt es eine Dienstleistung, erscheint stattdessen der
/// Abschnitt „Dienstleistungen" mit der echten Position.)
class _NothingToLoadHint extends StatelessWidget {
  const _NothingToLoadHint();

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Padding(
      padding: const EdgeInsets.fromLTRB(32, 32, 32, 16),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.inbox_outlined, size: 56, color: theme.colorScheme.outline),
          const SizedBox(height: 12),
          Text(
            'Für diesen Kunden ist nichts zu beladen.',
            style: theme.textTheme.titleMedium,
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 6),
          Text(
            'Die Lieferung enthält keine zu ladende Ware.',
            style: theme.textTheme.bodySmall,
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}

/// Runder Initialen-Avatar links neben dem Kundennamen. Macht den
/// sonst sehr text-lastigen Header griffiger und liefert dem Fahrer
/// einen visuellen Anker: gleicher Kunde = gleiche Farbe.
///
/// Farbe ist deterministisch aus der `customer.id` abgeleitet, damit
/// Re-Builds und Page-Wechsel den Avatar nicht „flackern" lassen.
/// Initialen kommen aus dem Namen — Vor- und Zunamen kombiniert,
/// einzelne Worte mit einem Buchstaben.
class _CustomerAvatar extends StatelessWidget {
  const _CustomerAvatar({required this.customer});

  final Customer? customer;

  /// Kleine kuratierte Palette — kräftig genug zum Erkennen, aber nicht
  /// schreiend. Reihenfolge ist Absicht: die ersten Farben fallen am
  /// stärksten auf und sind damit für die häufigsten Kunden „zuerst
  /// dran" (Hash-Modulo verteilt — über die Zeit ausgeglichen).
  static const List<Color> _palette = [
    Color(0xFF1976D2), // blue 700
    Color(0xFF388E3C), // green 700
    Color(0xFFEF6C00), // orange 800
    Color(0xFF7B1FA2), // purple 700
    Color(0xFF00838F), // cyan 800
    Color(0xFF5D4037), // brown 700
    Color(0xFF455A64), // blueGrey 700
  ];

  String get _initials {
    final name = customer?.name.trim() ?? '';
    if (name.isEmpty) return '?';
    final parts = name
        .split(RegExp(r'\s+'))
        .where((p) => p.isNotEmpty)
        .toList(growable: false);
    if (parts.isEmpty) return '?';
    if (parts.length == 1) {
      return parts.first.characters.first.toUpperCase();
    }
    final first = parts.first.characters.first;
    final last = parts.last.characters.first;
    return '$first$last'.toUpperCase();
  }

  Color get _backgroundColor {
    final seed = customer?.id.hashCode ?? 0;
    return _palette[seed.abs() % _palette.length];
  }

  @override
  Widget build(BuildContext context) {
    if (customer == null) {
      // Fallback: graues Person-Icon, weil kein Name → keine Initialen.
      return const CircleAvatar(
        radius: 26,
        backgroundColor: Colors.grey,
        child: Icon(Icons.person_outline, color: Colors.white),
      );
    }
    return CircleAvatar(
      radius: 26,
      backgroundColor: _backgroundColor,
      child: Text(
        _initials,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 18,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

/// Farbiger Status-Badge unterhalb der Adresse — sichtbar bei `held`,
/// `canceled`, `completed`. Bei `active` blendet der Aufrufer den Badge
/// ganz aus, damit der Default-Fall die UI nicht zumüllt.
class _DeliveryStateBadge extends StatelessWidget {
  const _DeliveryStateBadge({required this.delivery});

  final Delivery delivery;

  @override
  Widget build(BuildContext context) {
    final (color, label, icon) = switch (delivery.state) {
      DeliveryState.active => (Colors.blue, 'Aktiv', Icons.local_shipping),
      DeliveryState.held => (Colors.orange, 'Pausiert', Icons.pause_circle),
      DeliveryState.canceled => (Colors.red, 'Abgebrochen', Icons.cancel),
      DeliveryState.completed =>
        (Colors.green, 'Abgeschlossen', Icons.check_circle),
    };
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.15),
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: color),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(icon, size: 14, color: color),
          const SizedBox(width: 6),
          Text(
            label,
            style: TextStyle(color: color, fontWeight: FontWeight.w600),
          ),
          if (delivery.stateReason != null &&
              delivery.stateReason!.isNotEmpty) ...[
            const SizedBox(width: 8),
            Flexible(
              child: Text(
                '· ${delivery.stateReason}',
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ],
        ],
      ),
    );
  }
}

/// AppBar-3-Punkte-Menü mit Delivery-Lifecycle-Aktionen. Optionen sind
/// kontextabhängig vom aktuellen `state` — endgültige Zustände
/// (canceled, completed) bieten keine Aktion mehr; das Icon verschwindet
/// dann ganz, damit der Fahrer keine Sackgasse antippt.
class _DeliveryActionsMenu extends StatelessWidget {
  const _DeliveryActionsMenu({
    required this.delivery,
    required this.onHold,
    required this.onCancel,
    required this.onResume,
  });

  final Delivery delivery;
  final VoidCallback onHold;
  final VoidCallback onCancel;
  final VoidCallback onResume;

  @override
  Widget build(BuildContext context) {
    final items = <PopupMenuEntry<_DeliveryAction>>[];
    switch (delivery.state) {
      case DeliveryState.active:
        items.add(const PopupMenuItem(
          value: _DeliveryAction.hold,
          child: ListTile(
            leading: Icon(Icons.pause_circle_outline),
            title: Text('Lieferung pausieren'),
            contentPadding: EdgeInsets.zero,
          ),
        ));
        items.add(const PopupMenuItem(
          value: _DeliveryAction.cancel,
          child: ListTile(
            leading: Icon(Icons.cancel_outlined, color: Colors.red),
            title: Text('Lieferung abbrechen'),
            contentPadding: EdgeInsets.zero,
          ),
        ));
      case DeliveryState.held:
        items.add(const PopupMenuItem(
          value: _DeliveryAction.resume,
          child: ListTile(
            leading: Icon(Icons.play_circle_outline),
            title: Text('Lieferung fortsetzen'),
            contentPadding: EdgeInsets.zero,
          ),
        ));
        items.add(const PopupMenuItem(
          value: _DeliveryAction.cancel,
          child: ListTile(
            leading: Icon(Icons.cancel_outlined, color: Colors.red),
            title: Text('Lieferung abbrechen'),
            contentPadding: EdgeInsets.zero,
          ),
        ));
      case DeliveryState.canceled:
        items.add(const PopupMenuItem(
          value: _DeliveryAction.resume,
          child: ListTile(
            leading: Icon(Icons.restore, color: Colors.green),
            title: Text('Lieferung wiederherstellen'),
            contentPadding: EdgeInsets.zero,
          ),
        ));
      case DeliveryState.completed:
        return const SizedBox.shrink();
    }

    return PopupMenuButton<_DeliveryAction>(
      tooltip: 'Lieferungs-Aktionen',
      onSelected: (action) {
        switch (action) {
          case _DeliveryAction.hold:
            onHold();
          case _DeliveryAction.cancel:
            onCancel();
          case _DeliveryAction.resume:
            onResume();
        }
      },
      itemBuilder: (_) => items,
    );
  }
}

enum _DeliveryAction { hold, cancel, resume }

enum _ItemAction { remove, unremove, manualConfirm }

/// Karten-Darstellung einer Item-Position in der Beladen-Phase.
///
/// Visueller Stil orientiert sich an `_OverviewTile` (Lieferungs-Karten
/// in der Beladen-Übersicht): semi-transparente Hintergrundfarbe + Border
/// codieren den Status, links ein Status-Icon, rechts die Mengen-Anzeige.
/// Bei `isDone` ein zusätzliches grünes Häkchen, damit „fertig" ohne
/// Mengenrechnung auf einen Blick erkennbar ist.
/// Sortiert Items so, dass innerhalb einer Belegzeile der Oberartikel VOR
/// seinen Komponenten steht (für die eingerückte Darstellung).
List<DeliveryItem> _parentFirst(List<DeliveryItem> items) {
  final sorted = List<DeliveryItem>.of(items);
  sorted.sort((a, b) {
    final byLine = a.belegzeilenNr.compareTo(b.belegzeilenNr);
    if (byLine != 0) return byLine;
    final byParent = (a.isComponent ? 1 : 0).compareTo(b.isComponent ? 1 : 0);
    if (byParent != 0) return byParent;
    return (a.komponentenArtikelNr ?? '').compareTo(b.komponentenArtikelNr ?? '');
  });
  return sorted;
}

class _ItemRow extends StatelessWidget {
  const _ItemRow({
    required this.item,
    required this.details,
    required this.onAction,
    this.suppressScanHint = false,
    this.setParentComplete = false,
  });

  final DeliveryItem item;
  final TourDetails details;
  final void Function(_ItemAction action) onAction;

  /// Unterdrückt den „Kein Scanvorgang notwendig"-Hinweis. Für nicht-scanbare
  /// **Set-Köpfe** (Parent-Artikel): dort ist der Hinweis irreführend, weil die
  /// (scanbaren) Komponenten darunter sehr wohl gescannt werden.
  final bool suppressScanHint;

  /// `true`, wenn dies ein Set-Kopf ist, dessen Komponenten alle fertig
  /// geladen sind. Dann wird der (selbst nicht scanbare) Kopf grün dargestellt.
  final bool setParentComplete;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final article = details.articleOf(item.articleId);
    final warehouse = details.warehouseOf(item.warehouseId);
    // `true` für nicht-scanbare Positionen (Dienstleistungen / Pauschalen /
    // Set-Köpfe): die Card wird GENAU SO wie ein scanbarer Artikel gerendert,
    // bekommt aber unten den Hinweis „Kein Scanvorgang notwendig" und keinen
    // Mengen-Zähler. Aus dem Artikel abgeleitet, damit ein in eine Lagergruppe
    // eingehängter nicht-scanbarer Set-Kopf automatisch korrekt rendert.
    final scanNotRequired = !(article?.scannable ?? false);
    // Effektiv „fertig": eigener Scan-Status ODER (Set-Kopf, dessen Komponenten
    // alle geladen sind) → der Kopf wird dann ebenfalls grün.
    final effectiveDone = item.isDone || setParentComplete;
    final isExternalWarehouse = warehouse != null && !warehouse.isStandard;
    // Manueller Fallback-Button: nur für scanbare, noch offene Positionen
    // (nicht done/entfernt/pausiert) — analog dazu, was ein Barcode-Scan
    // dieser Zeile tun würde.
    final canManualConfirm = article != null &&
        article.scannable &&
        !item.isDone &&
        !item.isRemoved &&
        !item.isHeld;

    // Status-abhängiger Style — gleiches Farbschema wie `_OverviewTile`,
    // damit Übersicht und Detail visuell zusammenpassen.
    final Color cardColor;
    final Color borderColor;
    final Color titleColor;
    final Color quantityColor;
    final IconData leadingIcon;
    final Color leadingIconColor;
    final String? statusBadgeLabel;

    if (item.isRemoved) {
      cardColor = Colors.grey.withValues(alpha: 0.08);
      borderColor = Colors.grey.withValues(alpha: 0.35);
      titleColor = Colors.grey.shade700;
      quantityColor = Colors.grey;
      leadingIcon = Icons.delete_outline;
      leadingIconColor = Colors.grey.shade700;
      statusBadgeLabel = 'Entfernt';
    } else if (item.isHeld) {
      cardColor = Colors.orange.withValues(alpha: 0.07);
      borderColor = Colors.orange.withValues(alpha: 0.35);
      titleColor = Colors.orange.shade800;
      quantityColor = Colors.orange.shade800;
      leadingIcon = Icons.pause_circle_outline;
      leadingIconColor = Colors.orange.shade800;
      statusBadgeLabel = 'Pausiert';
    } else if (effectiveDone) {
      cardColor = Colors.green.withValues(alpha: 0.07);
      borderColor = Colors.green.withValues(alpha: 0.35);
      titleColor = Colors.green.shade700;
      quantityColor = Colors.green.shade700;
      leadingIcon = Icons.inventory_2_outlined;
      leadingIconColor = Colors.green.shade700;
      statusBadgeLabel = null;
    } else if (item.scanProgress.scannedQuantity > 0) {
      cardColor = Colors.orange.withValues(alpha: 0.07);
      borderColor = Colors.orange.withValues(alpha: 0.35);
      titleColor = Colors.orange.shade800;
      quantityColor = Colors.orange.shade800;
      leadingIcon = Icons.inventory_2_outlined;
      leadingIconColor = Colors.orange.shade800;
      statusBadgeLabel = null;
    } else {
      cardColor = theme.colorScheme.surfaceContainerLow;
      borderColor = Colors.transparent;
      titleColor = theme.colorScheme.onSurface;
      quantityColor = theme.colorScheme.onSurface;
      leadingIcon = Icons.inventory_2_outlined;
      leadingIconColor = theme.colorScheme.onSurfaceVariant;
      statusBadgeLabel = null;
    }

    return Opacity(
      opacity: item.isRemoved ? 0.55 : 1.0,
      child: Card(
        // Komponenten weiter links eingerückt → gehören zum Oberartikel darüber.
        margin: EdgeInsets.only(
          left: item.isComponent ? 32 : 12,
          right: 12,
          top: 4,
          bottom: 4,
        ),
        elevation: 0,
        color: cardColor,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
          side: BorderSide(color: borderColor),
        ),
        child: Padding(
          padding: const EdgeInsets.fromLTRB(12, 12, 4, 12),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisSize: MainAxisSize.min,
            children: [
              Row(
            // Default `crossAxisAlignment: center` — Icon links, Mengen-
            // Anzeige rechts und der Aktions-Menü-Button sitzen damit
            // vertikal mittig zum Inhalts-Block in der Mitte. Wirkt
            // ausgeglichener, wenn der Subtitle mehrere Zeilen hat
            // (Art-Nr, Lager, Komponente, Reason).
            children: [
              Icon(leadingIcon, color: leadingIconColor, size: 28),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        Expanded(
                          child: Text(
                            '${item.isComponent ? '↳ ' : ''}${_articleTitle(article)}',
                            style: TextStyle(
                              fontSize: 15,
                              fontWeight: FontWeight.w600,
                              color: titleColor,
                              // Entfernte Positionen werden im Detail-Screen
                              // **nicht** rausgefiltert, sondern bleiben
                              // sichtbar — durchgestrichen, damit der Fahrer
                              // erkennt „hatten wir, ist raus" und über das
                              // Aktions-Menü ggf. wiederherstellen kann.
                              decoration: item.isRemoved
                                  ? TextDecoration.lineThrough
                                  : null,
                            ),
                          ),
                        ),
                        if (statusBadgeLabel != null)
                          Container(
                            margin: const EdgeInsets.only(left: 8),
                            padding: const EdgeInsets.symmetric(
                                horizontal: 6, vertical: 2),
                            decoration: BoxDecoration(
                              color: titleColor.withValues(alpha: 0.15),
                              borderRadius: BorderRadius.circular(4),
                              border: Border.all(color: titleColor),
                            ),
                            child: Text(
                              statusBadgeLabel,
                              style: TextStyle(
                                color: titleColor,
                                fontSize: 11,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                          ),
                      ],
                    ),
                    const SizedBox(height: 2),
                    Text(
                      'Art.-Nr.: ${article?.articleNumber ?? '⟨unbekannt⟩'}',
                      style: TextStyle(
                        fontSize: 12,
                        color: theme.colorScheme.onSurfaceVariant,
                      ),
                    ),
                    // Lager-Angabe nur für scanbare Artikel. Dienstleistungen
                    // (`scanNotRequired`) werden nicht aus einem Lager beladen
                    // → die Lager-Zeile wäre irrelevant und entfällt.
                    if (warehouse != null && !scanNotRequired)
                      Text(
                        'Lager: ${warehouse.name}'
                        '${isExternalWarehouse ? ' (Filiale)' : ''}',
                        style: TextStyle(
                          fontSize: 12,
                          color: isExternalWarehouse
                              ? Colors.amber.shade800
                              : theme.colorScheme.onSurfaceVariant,
                          fontWeight: isExternalWarehouse
                              ? FontWeight.w600
                              : FontWeight.normal,
                        ),
                      ),
                    if (item.isHeld &&
                        item.scanProgress.heldReason != null &&
                        item.scanProgress.heldReason!.isNotEmpty)
                      Text(
                        'Grund: ${item.scanProgress.heldReason}',
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.orange.shade800,
                        ),
                      ),
                  ],
                ),
              ),
              // Mengen-Zähler nur für scanbare Artikel. Dienstleistungen
              // (`scanNotRequired`) werden nicht gescannt → eine „X / Y"-
              // Anzeige wäre dort sinnlos und wird weggelassen.
              if (!scanNotRequired) ...[
                const SizedBox(width: 8),
                Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Text(
                          '${item.scanProgress.scannedQuantity} / ${item.requiredQuantity}',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 16,
                            color: quantityColor,
                          ),
                        ),
                        // Grünes Häkchen sobald die Soll-Menge erreicht ist —
                        // schneller visueller Anker als die Zahlen-Differenz.
                        if (item.isDone) ...[
                          const SizedBox(width: 4),
                          Icon(
                            Icons.check_circle,
                            size: 20,
                            color: Colors.green.shade700,
                          ),
                        ],
                      ],
                    ),
                  ],
                ),
              ],
              _ItemActionMenu(item: item, onSelected: onAction),
                ],
              ),
              if (canManualConfirm) ...[
                const SizedBox(height: 8),
                SizedBox(
                  width: double.infinity,
                  child: OutlinedButton.icon(
                    onPressed: () => onAction(_ItemAction.manualConfirm),
                    icon: const Icon(Icons.check_circle_outline, size: 18),
                    label: const Text('Manuell als geladen bestätigen'),
                  ),
                ),
              ],
              // Set-Kopf, dessen Komponenten alle geladen sind → grünes
              // „Komplett geladen". Sonst (nicht-scanbare Position, KEIN
              // Set-Kopf) der normale „Kein Scanvorgang notwendig"-Hinweis;
              // bei Set-Köpfen ist der unterdrückt (irreführend).
              if (scanNotRequired && setParentComplete) ...[
                const SizedBox(height: 8),
                const _SetCompleteHint(),
              ] else if (scanNotRequired && !suppressScanHint) ...[
                const SizedBox(height: 8),
                const _ScanNotRequiredHint(),
              ],
            ],
          ),
        ),
      ),
    );
  }

  String _articleTitle(Article? article) {
    if (article == null) return item.articleId;
    return article.name;
  }
}

/// 3-Punkte-Menü am Ende einer Item-Row. Welche Aktion sichtbar ist,
/// hängt vom aktuellen ScanStatus ab:
/// * `removed` → „Wiederherstellen"
/// * sonst → „Entfernen"
///
/// Item-Pausieren wird vom UI bewusst **nicht** angeboten — das Backend
/// kennt die Hold-Action zwar weiterhin, in der Fahrer-App ist sie aber
/// nicht mehr Teil des Workflows.
class _ItemActionMenu extends StatelessWidget {
  const _ItemActionMenu({required this.item, required this.onSelected});

  final DeliveryItem item;
  final void Function(_ItemAction) onSelected;

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_ItemAction>(
      tooltip: 'Artikel-Aktionen',
      icon: const Icon(Icons.more_vert),
      onSelected: onSelected,
      itemBuilder: (_) {
        if (item.isRemoved) {
          return const [
            PopupMenuItem(
              value: _ItemAction.unremove,
              child: ListTile(
                leading: Icon(Icons.restore, color: Colors.green),
                title: Text('Wiederherstellen'),
                contentPadding: EdgeInsets.zero,
              ),
            ),
          ];
        }
        return const [
          PopupMenuItem(
            value: _ItemAction.remove,
            child: ListTile(
              leading: Icon(Icons.delete_outline, color: Colors.red),
              title: Text('Entfernen'),
              contentPadding: EdgeInsets.zero,
            ),
          ),
        ];
      },
    );
  }
}
