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';

/// Ergebnis der Item-Auflösung beim Scan. Sealed, damit das UI per
/// `switch` exhaustiv pro Fall eine passende Meldung zeigt.
sealed class ItemMatch {
  const ItemMatch();
  const factory ItemMatch.ok(DeliveryItem item) = ItemMatchOk;
  const factory ItemMatch.notInDelivery() = ItemMatchNotInDelivery;
  const factory ItemMatch.notScannable() = ItemMatchNotScannable;
  const factory ItemMatch.allDone() = ItemMatchAllDone;
  const factory ItemMatch.allRemoved() = ItemMatchAllRemoved;
  const factory ItemMatch.notOpen() = ItemMatchNotOpen;
}

class ItemMatchOk extends ItemMatch {
  const ItemMatchOk(this.item);
  final DeliveryItem item;
}

class ItemMatchNotInDelivery extends ItemMatch {
  const ItemMatchNotInDelivery();
}

class ItemMatchNotScannable extends ItemMatch {
  const ItemMatchNotScannable();
}

class ItemMatchAllDone extends ItemMatch {
  const ItemMatchAllDone();
}

class ItemMatchAllRemoved extends ItemMatch {
  const ItemMatchAllRemoved();
}

class ItemMatchNotOpen extends ItemMatch {
  const ItemMatchNotOpen();
}

/// Findet in der Lieferung das erste **nicht fertig** gescannte Item mit der
/// gegebenen Article-Nummer. „Top-down"-Strategie: hat eine Lieferung zwei
/// Belegzeilen mit demselben Artikel (z. B. 20 + 10), wird zuerst die
/// niedrigere Belegzeile gefüllt. Erst wenn diese fertig ist, „rollt" der
/// nächste Scan auf die zweite Zeile weiter.
///
/// [itemFilter] schränkt die betrachteten Positionen ein — z. B. nur
/// Filial-Items in der Abhol-Phase. Ohne Filter werden alle Positionen
/// berücksichtigt (Beladen-Phase).
///
/// Klassifiziert den Misserfolgs-Grund (nicht scanbar / bereits fertig /
/// entfernt …), damit das UI dem Fahrer eine sinnvolle Meldung zeigt.
ItemMatch matchItem({
  required Delivery delivery,
  required TourDetails details,
  required String articleNumber,
  bool Function(DeliveryItem item)? itemFilter,
}) {
  final normalized = articleNumber.trim();
  final candidates = delivery.items
      .where((it) => itemFilter?.call(it) ?? true)
      .toList()
    ..sort((a, b) => a.belegzeilenNr.compareTo(b.belegzeilenNr));
  final matchingArticle = candidates.where((it) {
    final art = details.articleOf(it.articleId);
    return art?.articleNumber == normalized;
  }).toList();

  if (matchingArticle.isEmpty) {
    return const ItemMatch.notInDelivery();
  }

  // Erstes Item, das wir tatsächlich scannen können.
  for (final item in matchingArticle) {
    if (item.isDone || item.isRemoved) continue;
    final article = details.articleOf(item.articleId);
    if (article == null || !article.scannable) continue;
    return ItemMatch.ok(item);
  }

  // Kein scannbares offenes Item — Grund anhand der Item-Verteilung
  // klassifizieren, damit das UI eine sinnvolle Meldung zeigt.
  final allNotScannable = matchingArticle.every((it) {
    final art = details.articleOf(it.articleId);
    return art == null || !art.scannable;
  });
  if (allNotScannable) return const ItemMatch.notScannable();

  final allRemoved = matchingArticle.every((it) => it.isRemoved);
  if (allRemoved) return const ItemMatch.allRemoved();

  final allDone = matchingArticle.every((it) => it.isDone);
  if (allDone) return const ItemMatch.allDone();

  // Gemischte Konstellation (z. B. eine Zeile entfernt, eine fertig) —
  // praktisch selten; konservativ als „nicht (mehr) offen" melden.
  return const ItemMatch.notOpen();
}
