import { useState, useEffect, useRef } from 'react';
import { json } from '@remix-run/node';
import { useRouteError } from '@remix-run/react';
import { boundary } from '@shopify/shopify-app-remix/server';
import { authenticate } from '../shopify.server.js';

// Anmeldung nur hier im Route-Loader (nicht in root/Layout): gültige Shopify-Sitzung ist Pflicht.
export async function loader({ request }: { request: Request }) {
  await authenticate.admin(request);
  return json({ ok: true });
}

import { shopifyQuery } from '../lib/shopify-client';
import ManualPriceView from '../components/ManualPriceView';
import BackupView from '../components/BackupView';
import { saveBackupEntry, numId, shorten } from '../lib/price-backup';
import { preOpenPrintWindow } from '../lib/print';
import {
  parsePriceListXlsx,
  comparePriceList,
  parseMoneyMetafield,
  parseFactor,
  direction,
  computeSalePrice,
  withSalePrice,
  DEFAULT_SALE_FACTOR,
  type PriceRowView,
  type PriceCompareRow,
  type OnlyInShopRow,
  type OnlyInListRow,
  type ShopVariant,
} from '../lib/price-list';
import {
  backend,
  BackendError,
  type ApiHistoryEntry,
  type ApiOrder,
  type ApiOrderItem,
  type ImportReport,
  type MetafieldSources,
  type OrderStatus,
  type ReceivingProgress,
  type ReceivingResult,
} from '../lib/backend';

// Polaris-Webkomponenten unter React 18: onChange wird nur für echte input/select/textarea gebunden.
// Diese Hilfe hängt das native "change"-Ereignis per ref an und ruft immer den neuesten Handler auf.
type ChangeHandler = (e: any) => void;
const listen = (type: string, handler: ChangeHandler) => (el: HTMLElement | null) => {
  if (!el) return;
  const holder = ((el as any).__handlers ??= {}) as Record<string, ChangeHandler>;
  if (!holder[type]) el.addEventListener(type, (e) => (el as any).__handlers[type](e));
  holder[type] = handler;
};


// Leuchtende Signalfarben für höhere (rot) bzw. tiefere (grün) neue Preise in der Preislisten-Ansicht
const CHANGE_UP = { color: '#ff1a1a', fontWeight: 700 } as const;
const CHANGE_DOWN = { color: '#00b34a', fontWeight: 700 } as const;

// SKUs, die aus der Verkaufs-/Bestandsstatistik ausgeschlossen werden
// (wie bisher im manuellen Analytics-Bericht admin/analytics/reports/223052040)
const EXCLUDED_STATS_PRODUCT_TITLES = [
  'Quille H 13mm, 38x38 mm',
  'Quille H 26mm, 38x38 mm',
  'Quille H 52mm, 38x38 mm',
];

interface ShopifyQlSkuStats {
  quantityOrdered: number;
  quantityReturned: number;
  daysOfInventoryRemaining: number | null;
  sellThroughRate: number | null;
}

// Holt die Verkaufs-/Rückgabe-/Bestandsreichweite-Statistik der letzten 365 Tage
// per shopifyqlQuery (GraphQL Admin API, Scope "read_reports"). Läuft wie die
// übrigen Abfragen dieser Datei über shopifyQuery (Direct API Access).
// Bei Fehlern (z. B. Scope noch nicht autorisiert) wird eine leere Map
// zurückgegeben, damit der Export selbst nicht blockiert wird.
const fetchShopifyQlStats = async (): Promise<Map<string, ShopifyQlSkuStats>> => {
  const statsBySku = new Map<string, ShopifyQlSkuStats>();

  const excludeClause = EXCLUDED_STATS_PRODUCT_TITLES.map(
    (title) => `product_title != '${title.replace(/'/g, "\\'")}'`,
  ).join('\n    AND ');

  const shopifyQlQueryText = `
FROM sales, inventory
  SHOW quantity_ordered, reversed_quantity, ending_inventory_units,
    days_of_inventory_remaining, sell_through_rate, net_items_sold, net_sales
  WHERE ${excludeClause}
  GROUP BY product_variant_sku, product_title WITH GROUP_TOTALS, TOTALS,
    CURRENCY 'EUR'
  SINCE startOfDay(-365d) UNTIL today
  ORDER BY quantity_ordered__product_variant_sku_totals DESC, quantity_ordered
    DESC, product_variant_sku ASC, product_title ASC
`.trim();

  try {
    const { data, errors } = await shopifyQuery(
      `query ShopifyQlInventoryStats($query: String!) {
        shopifyqlQuery(query: $query) {
          tableData {
            columns { name dataType displayName }
            rows
          }
          parseErrors
        }
      }`,
      { variables: { query: shopifyQlQueryText } },
    );

    if (errors?.length > 0) {
      // eslint-disable-next-line no-console
      console.error('ShopifyQL Diagnose – GraphQL-Fehler:', errors);
      return statsBySku;
    }
    if (!data?.shopifyqlQuery) {
      // eslint-disable-next-line no-console
      console.error('ShopifyQL Diagnose – keine shopifyqlQuery in der Antwort:', data);
      return statsBySku;
    }

    const result = data.shopifyqlQuery;
    if (result.parseErrors?.length > 0) {
      // eslint-disable-next-line no-console
      console.error('ShopifyQL Diagnose – parseErrors:', result.parseErrors);
      return statsBySku;
    }

    // Wichtig: die Zeilen sind Objekte, geschlüsselt nach Spaltenname
    // (nicht Arrays, die man per Spalten-Index ansprechen müsste).
    const num = (row: Record<string, unknown>, name: string): number => {
      const n = Number(row[name]);
      return Number.isFinite(n) ? n : 0;
    };
    const numOrNull = (row: Record<string, unknown>, name: string): number | null => {
      const v = row[name];
      if (v === null || v === undefined || v === '') return null;
      const n = Number(v);
      return Number.isFinite(n) ? n : null;
    };

    for (const row of result.tableData.rows as Record<string, unknown>[]) {
      const sku = row['product_variant_sku'];
      if (!sku) continue; // Zeilen ohne SKU (z. B. Varianten ohne SKU) überspringen

      statsBySku.set(String(sku), {
        quantityOrdered: num(row, 'quantity_ordered'),
        quantityReturned: num(row, 'reversed_quantity'), // vormals quantity_returned (von Shopify ersetzt)
        daysOfInventoryRemaining: numOrNull(row, 'days_of_inventory_remaining'),
        sellThroughRate: numOrNull(row, 'sell_through_rate'),
      });
    }
  } catch (err) {
    // Statistik ist eine Ergänzung, kein Pflichtfeld: Export nicht blockieren,
    // aber Fehler zur Diagnose sichtbar machen
    // eslint-disable-next-line no-console
    console.error('ShopifyQL Diagnose – unerwarteter Fehler:', err);
  }

  return statsBySku;
};

const toApiItem = (item: OrderItem): ApiOrderItem => {
  const out: ApiOrderItem = { sku: item.sku, quantity: item.quantity, title: item.title };
  if (item.lagerstandort) out.lagerstandort = item.lagerstandort;
  return out;
};

// Alle Backend-Aufrufe laufen über dieses Objekt (Namen wie deleteOrder/startReceiving gibt es auch in der Komponente).

let isProcessingReceiving = false;
let idempotencyCounter = 0;

const generateIdempotencyKey = (): string => {
  idempotencyCounter += 1;
  return `wareneingang-${Date.now()}-${idempotencyCounter}-${Math.random()
    .toString(36)
    .substring(2, 12)}`;
};

interface OrderItem {
  sku: string;
  quantity: number;
  title: string;
  lagerstandort?: string;
  // Diese beiden Felder werden nur im Arbeitsspeicher gehalten und bei Bedarf
  // per SKU aus Shopify nachgeladen. Sie werden derzeit NICHT im Metafield gespeichert.
  inventoryItemId?: string;
  imageUrl?: string;
}

interface PurchaseOrder {
  id: string;
  date: string;
  items: OrderItem[];
  status: 'open' | 'processing' | 'completed';
  trackingNumber?: string;
  verbuchtAm?: string;
}

interface ReceivingItem extends OrderItem {
  receivedQuantity: number;
  orderId: string;
}

type PriceUpdateResult = PriceCompareRow;

interface HistoryEntry {
  datum: string;
  uhrzeit: string;
  ereignis: string;
  bestelldatum: string;
  sku: string;
  bezeichnung: string;
  mengeBestellt: number;
  mengeVerbucht: number;
  details: string;
}

type View =
  | 'menu'
  | 'order'
  | 'receiving'
  | 'receiving-details'
  | 'completed-orders'
  | 'view-orders'
  | 'search-orders'
  | 'history'
  | 'export'
  | 'inventory'
  | 'price-update'
  | 'manual-price'
  | 'backups'
  | 'migration';

export default function Bestellverwaltung() {
  const [view, setView] = useState<View>('menu');
  const [purchaseOrders, setPurchaseOrders] = useState<PurchaseOrder[]>([]);
  const [shopId, setShopId] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string>('');
  const [success, setSuccess] = useState<string>('');

  const [orderInput, setOrderInput] = useState<string>('');

  const [importedItems, setImportedItems] = useState<OrderItem[]>([]);
  const [importWarning, setImportWarning] = useState<string>('');

  const [selectedOrderId, setSelectedOrderId] = useState<string>('');
  const [receivingReturnView, setReceivingReturnView] = useState<'receiving' | 'completed-orders'>(
    'receiving',
  );
  const [receivingItems, setReceivingItems] = useState<ReceivingItem[]>([]);
  const [alreadyBookedBySku, setAlreadyBookedBySku] = useState<{ [sku: string]: number }>({});
  const [applyPreviousBookings, setApplyPreviousBookings] = useState<boolean>(false);
  const [receivingPreparing, setReceivingPreparing] = useState<boolean>(false);
  const [showReceivingConfirm, setShowReceivingConfirm] = useState<boolean>(false);
  const [showIdempotencyWarning, setShowIdempotencyWarning] = useState<boolean>(false);
  // Betriebszustand (Diagnose des Servers): hängende Buchungen, Ergebnis der Überwachung, letztes Backup
  const [systemStatus, setSystemStatus] = useState<any>(null);
  // Standort für Bestandsbuchungen: gespeicherte Einstellung (settings/location_id) mit Rückfall auf den
  // ersten aktiven Standort, wie es die Vorgänger-App fest gemacht hat.
  const [availableLocations, setAvailableLocations] = useState<Array<{ id: string; name: string }>>([]);
  const [configuredLocationId, setConfiguredLocationId] = useState<string>('');
  const [locationSaving, setLocationSaving] = useState<boolean>(false);
  // Artikel einer unterbrochenen Buchung, bei denen unklar ist, ob Shopify den Bestand schon erhöht hat.
  const [unclearSkus, setUnclearSkus] = useState<Array<{ sku: string; receivedQuantity: number }>>([]);
  const [processing, setProcessing] = useState<boolean>(false);
  const [receivingProgress, setReceivingProgress] = useState<ReceivingProgress>({
    current: 0,
    total: 0,
    sku: '',
    phase: '',
  });
  // Ergebnis einer Buchung, dessen Historie noch nicht gespeichert werden konnte (erneut sendbar).
  const [pendingFinish, setPendingFinish] = useState<{
    orderId: string;
    bookingId: string;
    results: ReceivingResult[];
  } | null>(null);

  const [migrationBusy, setMigrationBusy] = useState<boolean>(false);
  const [migrationLog, setMigrationLog] = useState<string[]>([]);
  const [migrationSources, setMigrationSources] = useState<MetafieldSources | null>(null);
  const [migrationBackupUrl, setMigrationBackupUrl] = useState<string>('');
  const [migrationReport, setMigrationReport] = useState<ImportReport | null>(null);

  const [expandedOrders, setExpandedOrders] = useState<string[]>([]);
  const [editingOrders, setEditingOrders] = useState<{ [key: string]: PurchaseOrder }>({});
  const [newItemInputs, setNewItemInputs] = useState<{
    [key: string]: { sku: string; qty: string };
  }>({});

  const [exportData, setExportData] = useState<string>('');
  const [inventoryData, setInventoryData] = useState<any[]>([]);
  const [selectedLocation, setSelectedLocation] = useState<string>('');
  const [showNoLocation, setShowNoLocation] = useState<boolean>(false);
  const [hideZeroStock, setHideZeroStock] = useState<boolean>(false);
  const [locations, setLocations] = useState<string[]>([]);

  const [searchQuery, setSearchQuery] = useState<string>('');
  const [searchResults, setSearchResults] = useState<any[]>([]);
  const [searchOrderCount, setSearchOrderCount] = useState<number>(0);
  const [searchExecuted, setSearchExecuted] = useState<boolean>(false);
  const [confirmDeleteOrderId, setConfirmDeleteOrderId] = useState<string | null>(null);

  const getTodayDisplay = (): string => {
    const now = new Date();
    return `${String(now.getDate()).padStart(2, '0')}.${String(now.getMonth() + 1).padStart(2, '0')}.${now.getFullYear()}`;
  };

  const getTodayISO = (): string => {
    const now = new Date();
    return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
  };

  const [orderDateDisplay, setOrderDateDisplay] = useState<string>(getTodayDisplay());
  const [orderDate, setOrderDate] = useState<string>(getTodayISO());


  const [printLink, setPrintLink] = useState<string>('');

  const [priceUpdateStep, setPriceUpdateStep] = useState<1 | 2>(1);
  const [priceUpdateFile, setPriceUpdateFile] = useState<File | null>(null);
  const [priceUpdateResults, setPriceUpdateResults] = useState<PriceUpdateResult[]>([]);
  const [priceChoice, setPriceChoice] = useState<Record<string, { p?: boolean; s?: boolean; v?: boolean }>>({});
  const [priceFactor, setPriceFactor] = useState<string>(DEFAULT_SALE_FACTOR.replace('.', ','));
  const [priceUpdateLoading, setPriceUpdateLoading] = useState<boolean>(false);
  const [priceUpdateSuccess, setPriceUpdateSuccess] = useState<string>('');
  const [priceListInfo, setPriceListInfo] = useState<string>('');
  // Vorauswahl Verkaufspreis nach Tag „Aufschlag": all | without | with
  const [priceAufMode, setPriceAufMode] = useState<string>('without');
  const [priceOnlyShop, setPriceOnlyShop] = useState<OnlyInShopRow[]>([]);
  const [priceOnlyList, setPriceOnlyList] = useState<OnlyInListRow[]>([]);
  const [showOnlyShop, setShowOnlyShop] = useState<boolean>(false);
  const [showOnlyList, setShowOnlyList] = useState<boolean>(false);

  const [historyData, setHistoryData] = useState<HistoryEntry[]>([]);
  const [historyYear, setHistoryYear] = useState<string>(new Date().getFullYear().toString());
  const [historyYears, setHistoryYears] = useState<string[]>([]);
  const [historyLoading, setHistoryLoading] = useState<boolean>(false);

  const [inventoryEditMode, setInventoryEditMode] = useState<boolean>(false);
  const [inventoryEdits, setInventoryEdits] = useState<{
    [sku: string]: { newStock?: number; newLagerstandort?: string };
  }>({});
  const [inventorySaving, setInventorySaving] = useState<boolean>(false);
  const [inventoryFreeTextRows, setInventoryFreeTextRows] = useState<{
    [sku: string]: boolean;
  }>({});
  const [inventoryLoaded, setInventoryLoaded] = useState<boolean>(false);

  useEffect(() => {
    initializeApp();
  }, []);

  const initializeApp = async (): Promise<void> => {
    setLoading(true);
    setError('');

    try {
      const shopQuery = `query { shop { id } }`;
      const { data: shopData, errors: shopErrors } = await shopifyQuery(shopQuery);

      if (shopErrors?.length > 0) {
        setError(shopErrors.map((e: any) => e.message).join(', '));
        setLoading(false);
        return;
      }

      const sid = shopData.shop.id;
      setShopId(sid);

      // Betriebszustand im Hintergrund laden; ein Fehler dabei blockiert nichts.
      backend
        .getDiag()
        .then(setSystemStatus)
        .catch(() => setSystemStatus(null));
      void loadAvailableLocations();

      try {
        const orders = await backend.listOrders('open,processing');
        setPurchaseOrders(orders as PurchaseOrder[]);
      } catch (backendError: any) {
        setError(
          `Bestelldaten konnten nicht geladen werden: ${backendError?.message || 'Backend nicht erreichbar'}. Bitte Seite neu laden.`,
        );
      }
    } catch (err: any) {
      setError(err.message || 'Fehler beim Initialisieren der App');
    }

    setLoading(false);
  };

  // Zentrale Sperre gegen Mehrfachklicks: Solange eine Aktion läuft, wird jede weitere ignoriert
  // (das Ref greift sofort, noch vor dem nächsten Render) und die Schaltflächen zeigen den Ladezustand.
  const [busyAction, setBusyAction] = useState<string>('');
  const activeActionRef = useRef<string>('');
  const runExclusive = async (name: string, action: () => Promise<void>): Promise<void> => {
    if (activeActionRef.current) return;
    activeActionRef.current = name;
    setBusyAction(name);
    try {
      await action();
    } finally {
      activeActionRef.current = '';
      setBusyAction('');
    }
  };
  const busyProps = (name: string) => ({
    disabled: busyAction ? true : undefined,
    loading: busyAction === name ? true : undefined,
  });

  /**
   * Standort für Bestandsbuchungen: zuerst die gespeicherte Einstellung (settings/location_id), sonst wie
   * bisher der erste aktive Standort. Gibt { id, error } zurück statt zu werfen, damit die Aufrufer ihre
   * bestehende Fehlerbehandlung unverändert weiterverwenden können.
   */
  const resolveActiveLocation = async (): Promise<{ id: string; error: string }> => {
    try {
      const setting = await backend.getSetting('location_id');
      if (setting?.value) return { id: setting.value, error: '' };
    } catch {
      // Ohne gespeicherte Einstellung (oder bei einem Backend-Fehler) auf den ersten aktiven Standort zurückfallen.
    }
    const { data, errors } = await shopifyQuery(
      `query { locations(first: 1, query: "active:true") { edges { node { id name } } } }`,
    );
    if (errors && errors.length > 0) {
      return { id: '', error: `Standort-Abfrage fehlgeschlagen: ${errors.map((e: any) => e.message).join(', ')}` };
    }
    const id = data?.locations?.edges?.[0]?.node?.id || '';
    return id ? { id, error: '' } : { id: '', error: 'Kein aktiver Lagerstandort gefunden.' };
  };

  const loadAvailableLocations = async (): Promise<void> => {
    try {
      const [{ data }, setting] = await Promise.all([
        shopifyQuery(`query { locations(first: 50, query: "active:true") { edges { node { id name } } } }`),
        backend.getSetting('location_id').catch(() => null),
      ]);
      setAvailableLocations((data?.locations?.edges || []).map((e: any) => e.node));
      setConfiguredLocationId(setting?.value || '');
    } catch {
      // Die Einstellung bleibt dann leer; die Buchungen fallen weiterhin auf den ersten aktiven Standort zurück.
    }
  };

  const saveConfiguredLocation = (id: string): void => {
    void runExclusive('saveLocation', async () => {
      setLocationSaving(true);
      try {
        await backend.setSetting('location_id', id);
        setConfiguredLocationId(id);
        setSuccess('Standort für Bestandsbuchungen gespeichert.');
      } catch (e: any) {
        setError(`Standort konnte nicht gespeichert werden: ${e?.message || 'Backend-Fehler'}`);
      } finally {
        setLocationSaving(false);
      }
    });
  };

  const parseOrderInput = async (): Promise<void> => {
    setImportWarning('');
    setError('');

    const lines = orderInput
      .trim()
      .split('\n')
      .filter((l) => l.trim());
    const items: OrderItem[] = [];
    const notFoundSkus: string[] = [];
    const invalidLines: string[] = [];

    for (const line of lines) {
      const lineLower = line.trim().toLowerCase();
      if (lineLower.includes('artikel') || lineLower.includes('menge')) {
        continue;
      }

      const parts = line.trim().split(/\s+/);
      if (parts.length < 3) {
        invalidLines.push(line);
        continue;
      }

      const sku = parts[0];
      const units = parseInt(parts[1]);
      const piecesPerUnit = parseInt(parts[2]);

      if (isNaN(units) || isNaN(piecesPerUnit)) {
        invalidLines.push(line);
        continue;
      }

      const quantity = units * piecesPerUnit;

      const query = `query ProductVariantBySku($sku: String!) {
        productVariants(first: 1, query: $sku) {
          edges {
            node {
              id
              sku
              title
              displayName
              inventoryItem {
                id
              }
              media(first: 1) {
                edges {
                  node {
                    ... on MediaImage {
                      image {
                        url
                      }
                    }
                  }
                }
              }
              product {
                id
                title
                featuredMedia {
                  preview {
                    image {
                      url
                    }
                  }
                }
                lagerstandort: metafield(namespace: "custom", key: "lagerstandort") {
                  value
                }
              }
            }
          }
        }
      }`;

      const { data, errors } = await shopifyQuery(query, {
        variables: { sku: `sku:${sku}` },
      });

      if (errors?.length > 0) {
        setError(errors.map((e: any) => e.message).join(', '));
        return;
      }

      if (data?.productVariants?.edges?.length > 0) {
        const variant = data.productVariants.edges[0].node;
        const imageUrl =
          variant.media?.edges?.[0]?.node?.image?.url ||
          variant.product?.featuredMedia?.preview?.image?.url ||
          '';
        items.push({
          sku: variant.sku,
          quantity,
          inventoryItemId: variant.inventoryItem.id,
          title: variant.displayName || variant.title,
          imageUrl,
          lagerstandort: variant.product?.lagerstandort?.value || '',
        });
      } else {
        notFoundSkus.push(sku);
      }
    }

    if (invalidLines.length > 0) {
      setImportWarning('Ungültige Zeilen übersprungen');
    }

    if (notFoundSkus.length > 0) {
      setImportWarning(
        (prev) =>
          (prev ? prev + '; ' : '') +
          `Folgende SKUs wurden nicht gefunden: ${notFoundSkus.join(', ')}`,
      );
    }

    setImportedItems(items);
  };

  const saveOrder = async (): Promise<void> => {
    setError('');
    setSuccess('');

    if (!orderDate) {
      setError('Bitte Bestelldatum eingeben (TT.MM.JJJJ)');
      return;
    }

    if (importedItems.length === 0) {
      setError('Keine gültigen Artikel gefunden');
      return;
    }

    const generateId = (): string => {
      return `order-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
    };

    const newOrder: PurchaseOrder = {
      id: generateId(),
      date: orderDate,
      items: importedItems,
      status: 'open',
    };

    let created: ApiOrder;
    try {
      // Das Backend speichert die Bestellung und schreibt die Historie („Bestellung erfasst") in einer Transaktion.
      created = await backend.createOrder({
        id: newOrder.id,
        date: newOrder.date,
        items: newOrder.items.map(toApiItem),
      });
    } catch (saveError: any) {
      setError(
        `Bestellung konnte nicht gespeichert werden: ${saveError?.message || 'Unbekannter Fehler'}`,
      );
      return;
    }

    // Bild-URL und inventoryItemId bleiben nur im Arbeitsspeicher.
    setPurchaseOrders([...purchaseOrders, { ...(created as PurchaseOrder), items: newOrder.items }]);

    setSuccess('Bestellung erfolgreich gespeichert');
    setOrderInput('');
    setOrderDate(getTodayISO());
    setOrderDateDisplay(getTodayDisplay());
    setImportedItems([]);
    setImportWarning('');
    setTimeout(() => {
      setView('menu');
      setSuccess('');
    }, 1500);
  };

  // Lädt inventoryItemId, Bild-URL und Lagerstandort per SKU nach. Diese Felder werden
  // nicht im Metafield gespeichert und müssen deshalb bei Bedarf ergänzt werden.
  const enrichOrderItems = async (items: OrderItem[]): Promise<OrderItem[]> => {
    const uniqueSkus = Array.from(new Set(items.map((i) => i.sku).filter((s) => !!s)));
    if (uniqueSkus.length === 0) return items;

    const variantBySku: {
      [sku: string]: { inventoryItemId: string; imageUrl: string; lagerstandort: string };
    } = {};

    const enrichWarnings: string[] = [];

    const enrichBatchSize = 15;
    for (let i = 0; i < uniqueSkus.length; i += enrichBatchSize) {
      const batch = uniqueSkus.slice(i, i + enrichBatchSize);
      const batchQuery = batch.map((s) => `sku:"${s}"`).join(' OR ');

      const query = `query VariantsBySkuBatch($query: String!) {
  productVariants(first: 25, query: $query) {
    edges {
      node {
        sku
        inventoryItem {
          id
        }
        product {
          featuredMedia {
            preview {
              image {
                url
              }
            }
          }
          lagerstandort: metafield(
            namespace: "custom",
            key: "lagerstandort"
          ) {
            value
          }
        }
      }
    }
  }
}`;

      // Jede Teilabfrage wird einzeln abgesichert. Schlägt sie fehl (z. B. Zeitüberschreitung
      // oder API-Limit), wird sie einmal wiederholt und danach übersprungen, damit der
      // Ladevorgang niemals stehen bleibt.
      let batchLoaded: boolean = false;
      let batchErrorMessage = '';

      for (let attempt = 1; attempt <= 2 && !batchLoaded; attempt++) {
        try {
          const { data, errors } = await shopifyQuery(query, {
            variables: { query: batchQuery },
          });

          if (errors?.length > 0) {
            batchErrorMessage = errors.map((e: any) => e.message).join(', ');
            continue;
          }

          for (const edge of data?.productVariants?.edges ?? []) {
            const node = edge.node;
            if (!node?.sku) continue;
            variantBySku[node.sku] = {
              inventoryItemId: node.inventoryItem?.id || '',
              imageUrl:
                node.media?.edges?.[0]?.node?.image?.url ||
                node.product?.featuredMedia?.preview?.image?.url ||
                '',
              lagerstandort: node.product?.lagerstandort?.value || '',
            };
          }

          batchLoaded = true;
        } catch (batchError: any) {
          batchErrorMessage = batchError?.message || 'Unbekannter Fehler';
        }
      }

      if (!batchLoaded && batchErrorMessage) {
        enrichWarnings.push(batchErrorMessage);
      }
    }

    if (enrichWarnings.length > 0) {
      setError(
        `Einige Artikeldaten (Bilder/Standorte) konnten nicht geladen werden: ${Array.from(
          new Set(enrichWarnings),
        ).join(', ')}. Das Verbuchen ist trotzdem möglich.`,
      );
    }

    return items.map((item) => {
      const info = variantBySku[item.sku];
      if (!info) return item;
      return {
        ...item,
        inventoryItemId: info.inventoryItemId || item.inventoryItemId,
        imageUrl: info.imageUrl || item.imageUrl,
        lagerstandort: item.lagerstandort || info.lagerstandort,
      };
    });
  };

  const resolveInventoryItemIdBySku = async (sku: string): Promise<string> => {
    const query = `query ProductVariantBySku($sku: String!) {
      productVariants(first: 1, query: $sku) {
        edges {
          node {
            inventoryItem { id }
          }
        }
      }
    }`;
    const { data } = await shopifyQuery(query, { variables: { sku: `sku:${sku}` } });
    return data?.productVariants?.edges?.[0]?.node?.inventoryItem?.id || '';
  };

  // Liefert null, wenn die bereits verbuchten Mengen nicht geladen werden konnten. Dann wird die
  // Maske nicht geöffnet, damit nicht versehentlich die volle Menge erneut vorbelegt wird.
  const loadBookedQuantitiesForOrder = async (
    orderId: string,
  ): Promise<{ [sku: string]: number } | null> => {
    try {
      return await backend.getBookedQuantities(orderId);
    } catch (bookedError: any) {
      setError(
        `Bereits verbuchte Mengen konnten nicht geladen werden: ${bookedError?.message || 'Unbekannter Fehler'}. Die Maske wurde nicht geöffnet.`,
      );
      return null;
    }
  };

  const logReceivingAbort = async (orderDateIso: string, errorText: string): Promise<void> => {
    try {
      await backend.addHistoryEvents([
        {
          ereignis: 'Wareneingang abgebrochen',
          bestelldatum: orderDateIso ? formatDateDisplay(orderDateIso) : '',
          details: errorText,
        },
      ]);
    } catch {
      // Ein Fehler beim Protokollieren darf den Abbruch nicht zusätzlich stören.
    }
  };

  const startReceiving = async (
    orderId: string,
    returnView: 'receiving' | 'completed-orders' = 'receiving',
  ): Promise<void> => {
    const order = purchaseOrders.find((o) => o.id === orderId);
    if (!order) return;

    setError('');
    setSuccess('');
    setReceivingPreparing(true);
    setReceivingReturnView(returnView);
    setSelectedOrderId(orderId);

    const useHistoryPrefill: boolean = returnView === 'completed-orders';
    let bookedBySku: { [sku: string]: number } = {};

    // Bei Problemfällen zuerst bereits gebuchte Mengen laden.
    if (useHistoryPrefill) {
      const loadedBooked = await loadBookedQuantitiesForOrder(order.id);
      if (!loadedBooked) {
        setReceivingPreparing(false);
        return;
      }
      bookedBySku = loadedBooked;
    }

    // Unterbrochene Buchung? Dann ist bei einzelnen Artikeln unklar, ob Shopify den Bestand schon erhöht hat.
    // Diese Artikel werden mit 0 vorbelegt (sicherer Standard) und im Hinweis genannt.
    let unclear: Array<{ sku: string; receivedQuantity: number }> = [];
    if (order.status !== 'open') {
      try {
        unclear = await backend.getUnclearReceiving(order.id);
      } catch {
        // Ohne diese Angabe bleibt die bisherige Vorbelegung; die Historie schützt weiterhin vor Doppelbuchung.
      }
    }
    setUnclearSkus(unclear);
    const unclearSet = new Set(unclear.map((u) => u.sku));

    // Die Maske sofort mit den gespeicherten Bestelldaten öffnen.
    const initialReceivingItems: ReceivingItem[] = order.items.map((item) => ({
      ...item,
      receivedQuantity: unclearSet.has(item.sku)
        ? 0
        : useHistoryPrefill
          ? Math.max(0, item.quantity - (bookedBySku[item.sku] || 0))
          : item.quantity,
      orderId,
    }));

    setAlreadyBookedBySku(bookedBySku);
    setApplyPreviousBookings(useHistoryPrefill);
    setReceivingItems(initialReceivingItems);
    setReceivingPreparing(false);
    setShowReceivingConfirm(false);
    setShowIdempotencyWarning(!!order.verbuchtAm || order.status !== 'open');

    // Nicht auf Zusatzdaten warten.
    setView('receiving-details');

    // Bilder, Lagerstandorte und Inventar-IDs danach im Hintergrund nachladen.
    try {
      const enrichedItems = await enrichOrderItems(order.items);

      setPurchaseOrders((prev) =>
        prev.map((existingOrder) =>
          existingOrder.id === orderId ? { ...existingOrder, items: enrichedItems } : existingOrder,
        ),
      );

      // Bereits eingegebene Mengen niemals überschreiben.
      setReceivingItems((prev) =>
        prev.map((receivingItem) => {
          const enrichedItem = enrichedItems.find((item) => item.sku === receivingItem.sku);

          if (!enrichedItem) return receivingItem;

          return {
            ...receivingItem,
            ...enrichedItem,
            receivedQuantity: receivingItem.receivedQuantity,
            orderId,
          };
        }),
      );
    } catch (prepareError: any) {
      setError(
        `Zusätzliche Artikeldaten konnten nicht vollständig geladen werden: ${
          prepareError?.message || 'Unbekannter Fehler'
        }. Das Verbuchen ist trotzdem möglich.`,
      );
    }
  };

  const logDoubleBookingAttempt = async (attemptOrder: PurchaseOrder): Promise<void> => {
    if (!attemptOrder.verbuchtAm) return;

    try {
      await backend.addHistoryEvents([
        {
          ereignis: 'Doppelbuchungsversuch',
          bestelldatum: formatDateDisplay(attemptOrder.date),
          details: `Bereits verbucht am ${formatDateTimeDisplay(attemptOrder.verbuchtAm)}`,
          orderId: attemptOrder.id,
        },
      ]);
    } catch {
      // Ein Fehler beim Protokollieren darf die Entscheidung nicht blockieren.
    }
  };

  const checkAndProcessReceiving = (): void => {
    const hasDifferences = receivingItems.some((item) => item.receivedQuantity !== item.quantity);
    if (hasDifferences) {
      setShowReceivingConfirm(true);
    } else {
      handleProcessReceiving();
    }
  };

  const runProcessReceiving = async (): Promise<void> => {
    if (isProcessingReceiving) return;
    isProcessingReceiving = true;
    setProcessing(true);
    setError('');
    setSuccess('');

    const receivingOrder = purchaseOrders.find((o) => o.id === selectedOrderId);
    const receivingOrderDate = receivingOrder ? receivingOrder.date : '';

    // SPERRE: Das Backend setzt den Status "in Verarbeitung" und schreibt den Startvermerk in einer
    // Transaktion, BEVOR Bestandsanpassungen erfolgen. Das verhindert Doppelbuchungen, falls die Seite
    // mitten im Vorgang neu lädt. Die bookingId macht Wiederholungen bei Verbindungsproblemen harmlos.
    const bookingId = backend.newBookingId();
    const totalAcceptedQuantity = receivingItems.reduce(
      (sum, item) => sum + item.receivedQuantity,
      0,
    );
    const itemsToBook = receivingItems.filter((item) => item.receivedQuantity > 0);

    setReceivingProgress({
      current: 0,
      total: itemsToBook.length,
      sku: '',
      phase: 'Buchung wird vorbereitet (Sperre wird gesetzt)...',
    });

    try {
      const started = await backend.startReceiving(selectedOrderId, {
        bookingId,
        positions: receivingItems.length,
        acceptedQuantity: totalAcceptedQuantity,
      });
      setPurchaseOrders((prev) =>
        prev.map((o) =>
          o.id === selectedOrderId
            ? { ...o, status: started.order.status, verbuchtAm: started.order.verbuchtAm }
            : o,
        ),
      );
    } catch (startError: any) {
      const lockErrorText = `Bestellstatus konnte vor der Buchung nicht gespeichert werden (${startError?.message || 'Backend-Fehler'}). Es wurden keine Bestände verändert.`;
      await logReceivingAbort(receivingOrderDate, lockErrorText);
      setError(lockErrorText);
      setProcessing(false);
      isProcessingReceiving = false;
      return;
    }

    setReceivingProgress((prev) => ({ ...prev, phase: 'Lagerstandort wird ermittelt...' }));

    const { id: locationId, error: locationError } = await resolveActiveLocation();
    if (!locationId) {
      const noLocationText = locationError || 'Kein aktiver Lagerstandort gefunden. Buchung abgebrochen.';
      await logReceivingAbort(receivingOrderDate, noLocationText);
      setError(noLocationText);
      setProcessing(false);
      isProcessingReceiving = false;
      return;
    }
    setReceivingProgress((prev) => ({ ...prev, phase: 'Bestände werden aktualisiert...' }));
    const skippedSkus: string[] = [];
    let lastErrorMsg = '';
    const skuErrorMap: { [sku: string]: string } = {};

    for (let itemIndex = 0; itemIndex < itemsToBook.length; itemIndex++) {
      const item = itemsToBook[itemIndex];

      // "current" zählt die bereits abgeschlossenen Artikel, "sku" ist der Artikel in Arbeit.
      setReceivingProgress((prev) => ({
        ...prev,
        current: itemIndex,
        sku: item.sku,
        phase: 'Bestand wird aktualisiert...',
      }));

      let currentInventoryItemId = item.inventoryItemId || '';

      if (!currentInventoryItemId) {
        currentInventoryItemId = await resolveInventoryItemIdBySku(item.sku);
      }

      if (!currentInventoryItemId) {
        skippedSkus.push(item.sku);
        skuErrorMap[item.sku] = 'inventoryItemId konnte nicht ermittelt werden';
        lastErrorMsg = `SKU ${item.sku}: inventoryItemId konnte nicht ermittelt werden`;
        continue;
      }

      const availQuery = `query GetAvailable($id: ID!) {
        node(id: $id) {
          ... on InventoryItem {
            inventoryLevels(first: 10) {
              edges {
                node {
                  location { id }
                  quantities(names: ["available"]) {
                    name
                    quantity
                  }
                }
              }
            }
          }
        }
      }`;

      const adjustMutation = `mutation inventoryAdjustQuantities($input: InventoryAdjustQuantitiesInput!, $idempotencyKey: String!) {
        inventoryAdjustQuantities(input: $input) @idempotent(key: $idempotencyKey) {
          inventoryAdjustmentGroup {
            createdAt
            reason
          }
          userErrors {
            field
            message
          }
        }
      }`;

      // Ein stabiler Idempotenz-Schlüssel pro Artikel. Wiederholungsversuche (Retry)
      // desselben Artikels nutzen denselben Schlüssel, damit keine Doppelbuchung entsteht.
      const itemIdempotencyKey = generateIdempotencyKey();

      const fetchAvailable = async (invItemId: string): Promise<number> => {
        const { data: availData } = await shopifyQuery(availQuery, {
          variables: { id: invItemId },
        });
        const levels = availData?.node?.inventoryLevels?.edges ?? [];
        const match = levels.find((e: any) => e.node.location.id === locationId);
        return match?.node?.quantities?.find((q: any) => q.name === 'available')?.quantity ?? 0;
      };

      // Absicht auf dem Server festhalten, BEVOR Shopify den Bestand ändert. Ohne diese Zusage keine Buchung.
      try {
        await backend.recordReceivingProgress(selectedOrderId, {
          bookingId,
          sku: item.sku,
          title: item.title,
          quantity: item.quantity,
          receivedQuantity: item.receivedQuantity,
          status: 'pending',
        });
      } catch (intentError: any) {
        skippedSkus.push(item.sku);
        skuErrorMap[item.sku] =
          `Fortschritt konnte nicht gespeichert werden (${intentError?.message || 'Backend-Fehler'}); Bestand wurde NICHT verändert`;
        lastErrorMsg = skuErrorMap[item.sku];
        continue;
      }

      let itemSkipped = true;
      let firstErrorMsg = '';
      let retryErrorMsg = '';
      const MAX_ATTEMPTS = 3;

      for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
        const changeFromQuantity = await fetchAvailable(currentInventoryItemId);

        const { data, errors } = await shopifyQuery(adjustMutation, {
          variables: {
            idempotencyKey: itemIdempotencyKey,
            input: {
              reason: 'received',
              name: 'available',
              changes: [
                {
                  delta: item.receivedQuantity,
                  inventoryItemId: currentInventoryItemId,
                  locationId,
                  changeFromQuantity,
                },
              ],
            },
          },
        });

        const errorMsg =
          errors?.map((e: any) => e.message).join('; ') ||
          data?.inventoryAdjustQuantities?.userErrors
            ?.map((e: any) => `[${e.field}] ${e.message}`)
            .join('; ') ||
          '';

        if (attempt === 1) firstErrorMsg = errorMsg;
        else retryErrorMsg = errorMsg;

        if (!errorMsg) {
          itemSkipped = false;
          break;
        }

        lastErrorMsg = errorMsg;
      }

      // Falls nach MAX_ATTEMPTS weiterhin fehlerhaft: einmal mehr mit frischer
      // inventoryItemId aus der SKU-Suche versuchen.
      if (itemSkipped) {
        const skuQuery = `query ProductVariantBySku($sku: String!) {
          productVariants(first: 1, query: $sku) {
            edges {
              node {
                inventoryItem { id }
              }
            }
          }
        }`;
        const { data: skuData } = await shopifyQuery(skuQuery, {
          variables: { sku: `sku:${item.sku}` },
        });
        if (skuData?.productVariants?.edges?.length > 0) {
          currentInventoryItemId = skuData.productVariants.edges[0].node.inventoryItem.id;
          const changeFromQuantityFinal = await fetchAvailable(currentInventoryItemId);
          const { data: finalData, errors: finalErrors } = await shopifyQuery(adjustMutation, {
            variables: {
              idempotencyKey: generateIdempotencyKey(),
              input: {
                reason: 'received',
                name: 'available',
                changes: [
                  {
                    delta: item.receivedQuantity,
                    inventoryItemId: currentInventoryItemId,
                    locationId,
                    changeFromQuantity: changeFromQuantityFinal,
                  },
                ],
              },
            },
          });
          const finalErrorMsg =
            finalErrors?.map((e: any) => e.message).join('; ') ||
            finalData?.inventoryAdjustQuantities?.userErrors
              ?.map((e: any) => `[${e.field}] ${e.message}`)
              .join('; ') ||
            '';
          retryErrorMsg = finalErrorMsg;
          if (!finalErrorMsg) {
            itemSkipped = false;
          } else {
            lastErrorMsg = finalErrorMsg;
          }
        }
      }

      if (itemSkipped) {
        skippedSkus.push(item.sku);
      }
      skuErrorMap[item.sku] =
        `1.Versuch: ${firstErrorMsg || '?'} | Retry: ${retryErrorMsg || 'n/a'}`;

      // Ergebnis sofort festhalten (nicht erst am Ende): schützt bei Browser-Absturz vor Doppelbuchung.
      // Schlägt das fehl, sendet der Abschluss unten dasselbe Ergebnis noch einmal.
      try {
        await backend.recordReceivingProgress(selectedOrderId, {
          bookingId,
          sku: item.sku,
          title: item.title,
          quantity: item.quantity,
          receivedQuantity: item.receivedQuantity,
          status: itemSkipped ? 'skipped' : 'booked',
          error: itemSkipped ? skuErrorMap[item.sku] : undefined,
        });
      } catch {
        // wird beim Abschluss nachgereicht
      }
    }

    setReceivingProgress({
      current: itemsToBook.length,
      total: itemsToBook.length,
      sku: '',
      phase: 'Ergebnis wird in der Historie gespeichert...',
    });

    // Ergebnis je Artikel. Das Backend schreibt die Historie und setzt den Status in einer Transaktion:
    // "completed", wenn alles gebucht wurde, sonst bleibt die Bestellung "in Verarbeitung".
    const results: ReceivingResult[] = itemsToBook.map((item) => {
      const skipped = skippedSkus.includes(item.sku);
      return {
        sku: item.sku,
        title: item.title,
        quantity: item.quantity,
        receivedQuantity: item.receivedQuantity,
        outcome: skipped ? ('skipped' as const) : ('booked' as const),
        error: skipped ? skuErrorMap[item.sku] || 'Fehler bei Bestandsanpassung' : undefined,
      };
    });

    const bookingSucceeded: boolean = skippedSkus.length === 0;
    let finishError = '';

    try {
      const finished = await backend.finishReceiving(selectedOrderId, { bookingId, results });
      setPurchaseOrders((prev) =>
        prev.map((o) =>
          o.id === selectedOrderId
            ? { ...o, status: finished.order.status, verbuchtAm: finished.order.verbuchtAm }
            : o,
        ),
      );
      setPendingFinish(null);
      setUnclearSkus([]);
    } catch (finishErr: any) {
      finishError = finishErr?.message || 'Backend nicht erreichbar';
      // Die Bestände sind bei Shopify bereits gebucht. Das Ergebnis bleibt erhalten und kann erneut gesendet werden.
      setPendingFinish({ orderId: selectedOrderId, bookingId, results });
    }

    if (!bookingSucceeded) {
      setError(
        `Folgende SKUs konnten nicht verbucht werden: ${skippedSkus.join(', ')} — Fehler: ${lastErrorMsg}. Die Bestellung bleibt als "in Verarbeitung" erhalten und kann über "Bestellungen mit Verbuchungsproblemen" geprüft oder erneut gebucht werden.${
          finishError ? ` Zusätzlicher Historie-Fehler: ${finishError}` : ''
        }`,
      );
    } else if (finishError) {
      setError(
        `Wareneingang bei Shopify verbucht, aber die Historie konnte nicht gespeichert werden: ${finishError}. Bitte "Historie erneut speichern" verwenden, nicht erneut buchen.`,
      );
    } else {
      setReceivingProgress((prev) => ({ ...prev, phase: 'Fertig.' }));
      setSuccess('Wareneingang erfolgreich verbucht');
      setTimeout(() => {
        setView('menu');
        setSuccess('');
      }, 2000);
    }

    setProcessing(false);
    isProcessingReceiving = false;
  };

  const handleProcessReceiving = async (): Promise<void> => {
    try {
      await runProcessReceiving();
    } catch (err: any) {
      const abortMessage = err?.message || 'Unbekannter Fehler beim Verbuchen';
      const abortOrder = purchaseOrders.find((o) => o.id === selectedOrderId);
      await logReceivingAbort(abortOrder ? abortOrder.date : '', abortMessage);
      setError(
        `Wareneingang abgebrochen: ${abortMessage}. Die Bestellung bleibt als "in Verarbeitung" erhalten und kann über "Bestellungen mit Verbuchungsproblemen" geprüft oder erneut gebucht werden.`,
      );
      setProcessing(false);
      isProcessingReceiving = false;
    }
  };

  const toggleOrderExpansion = async (orderId: string): Promise<void> => {
    if (expandedOrders.includes(orderId)) {
      setExpandedOrders(expandedOrders.filter((id) => id !== orderId));
      return;
    }

    setExpandedOrders([...expandedOrders, orderId]);

    const order = purchaseOrders.find((o) => o.id === orderId);
    if (!order) return;

    if (!editingOrders[orderId]) {
      setEditingOrders((prev) => ({
        ...prev,
        [orderId]: JSON.parse(JSON.stringify(order)),
      }));
    }

    // Bilder werden nicht gespeichert und beim Aufklappen per SKU nachgeladen.
    const needsImages: boolean = order.items.some((item) => !item.imageUrl);
    if (!needsImages) return;

    const enrichedItems = await enrichOrderItems(order.items);

    setPurchaseOrders((prev) =>
      prev.map((o) => (o.id === orderId ? { ...o, items: enrichedItems } : o)),
    );

    setEditingOrders((prev) => {
      const currentEditingOrder = prev[orderId];
      if (!currentEditingOrder) return prev;
      const mergedItems = currentEditingOrder.items.map((editedItem) => {
        const match = enrichedItems.find((enriched) => enriched.sku === editedItem.sku);
        if (!match) return editedItem;
        return {
          ...editedItem,
          imageUrl: editedItem.imageUrl || match.imageUrl,
          inventoryItemId: editedItem.inventoryItemId || match.inventoryItemId,
        };
      });
      return { ...prev, [orderId]: { ...currentEditingOrder, items: mergedItems } };
    });
  };

  const loadOpenCustomerOrdersBySku = async (
    purchaseOrderDate: string,
  ): Promise<{ [sku: string]: { name: string; quantity: number; isNew: boolean }[] }> => {
    const skuMap: { [sku: string]: { name: string; quantity: number; isNew: boolean }[] } = {};
    const cutoffTime = new Date(`${purchaseOrderDate}T23:59:59Z`).getTime();

    const ordersQuery = `query OpenCustomerOrders($first: Int!, $after: String, $query: String!) {
      orders(first: $first, after: $after, sortKey: CREATED_AT, query: $query) {
        edges {
          node {
            id
            name
            createdAt
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }`;

    const lineItemsQuery = `query OrderLineItems($id: ID!, $after: String) {
      node(id: $id) {
        ... on Order {
          lineItems(first: 250, after: $after) {
            edges {
              node {
                sku
                quantity
                unfulfilledQuantity
              }
            }
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }
      }
    }`;

    const addLineItemToMap = (orderName: string, isNew: boolean, lineItemNode: any): void => {
      const lineSku = lineItemNode.sku || '';
      if (!lineSku) return;
      const openQty =
        lineItemNode.unfulfilledQuantity && lineItemNode.unfulfilledQuantity > 0
          ? lineItemNode.unfulfilledQuantity
          : lineItemNode.quantity || 0;
      if (openQty <= 0) return;
      if (!skuMap[lineSku]) skuMap[lineSku] = [];
      const existingEntry = skuMap[lineSku].find((entry) => entry.name === orderName);
      if (existingEntry) {
        existingEntry.quantity += openQty;
      } else {
        skuMap[lineSku].push({ name: orderName, quantity: openQty, isNew });
      }
    };

    const collectLineItems = async (
      orderId: string,
      orderName: string,
      isNew: boolean,
    ): Promise<boolean> => {
      let lineItemCursor: string | null = null;
      let hasMoreLineItems: boolean = true;
      let lineItemPageCount = 0;
      const MAX_LINE_ITEM_PAGES = 20;

      while (hasMoreLineItems && lineItemPageCount < MAX_LINE_ITEM_PAGES) {
        const { data, errors } = await shopifyQuery(lineItemsQuery, {
          variables: { id: orderId, after: lineItemCursor },
        });

        if (errors?.length > 0) {
          setError(errors.map((e: any) => e.message).join(', '));
          return false;
        }

        const lineItemEdges = data?.node?.lineItems?.edges ?? [];
        for (const lineItemEdge of lineItemEdges) {
          addLineItemToMap(orderName, isNew, lineItemEdge.node);
        }

        hasMoreLineItems = data?.node?.lineItems?.pageInfo?.hasNextPage || false;
        lineItemCursor = data?.node?.lineItems?.pageInfo?.endCursor || null;
        lineItemPageCount += 1;
      }

      return true;
    };

    const filter =
      '(fulfillment_status:unfulfilled OR fulfillment_status:partial) AND status:open AND -cancelled_at:*';

    let ordersCursor: string | null = null;
    let hasMoreOrders: boolean = true;
    let pageCount = 0;
    const MAX_PAGES = 10;

    while (hasMoreOrders && pageCount < MAX_PAGES) {
      const { data, errors } = await shopifyQuery(ordersQuery, {
        variables: { first: 50, after: ordersCursor, query: filter },
      });

      if (errors?.length > 0) {
        setError(errors.map((e: any) => e.message).join(', '));
        return {};
      }

      const edges = data?.orders?.edges ?? [];
      for (const edge of edges) {
        const orderName = String(edge.node.name || '').replace(/^#/, '');
        const createdTime = edge.node.createdAt ? new Date(edge.node.createdAt).getTime() : 0;
        const isNewOrder: boolean = createdTime > cutoffTime;
        const lineItemsLoaded = await collectLineItems(edge.node.id, orderName, isNewOrder);
        if (!lineItemsLoaded) return {};
      }

      hasMoreOrders = data?.orders?.pageInfo?.hasNextPage || false;
      ordersCursor = data?.orders?.pageInfo?.endCursor || null;
      pageCount += 1;
    }

    Object.keys(skuMap).forEach((key) => {
      skuMap[key].sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
    });

    return skuMap;
  };

  const escapeHtml = (value: string): string =>
    String(value ?? '')
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');

  const buildOrderPrintHtml = (
    printDate: string,
    printItems: {
      sku: string;
      lagerstandort: string;
      title: string;
      quantity: number;
      customerOrders: { name: string; quantity: number; isNew: boolean }[];
    }[],
  ): string => {
    const rows = printItems
      .map((item) => {
        let cumulated = 0;
        const orderEntries = item.customerOrders
          .map((customerOrder) => {
            cumulated += customerOrder.quantity;
            const exceeds: boolean = cumulated > item.quantity;
            const label = escapeHtml(
              `${customerOrder.isNew ? `${customerOrder.name} N` : customerOrder.name}: ${customerOrder.quantity}`,
            );
            return exceeds
              ? `<div><span class="exceeded">${label}</span></div>`
              : `<div>${label}</div>`;
          })
          .join('');
        return `<tr><td>${escapeHtml(item.sku)}</td><td>${escapeHtml(
          item.lagerstandort,
        )}</td><td>${escapeHtml(item.title)}</td><td>${item.quantity}</td><td>${orderEntries}</td></tr>`;
      })
      .join('');

    const heading = `Bestellung vom ${escapeHtml(formatDateDisplay(printDate))}`;

    return `<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>${heading}</title>
<style>
  body { font-family: Arial, Helvetica, sans-serif; font-size: 12px; margin: 16px; }
  h1 { font-size: 16px; margin: 0 0 12px 0; }
  table { border-collapse: collapse; width: 100%; }
  th, td { border: 1px solid #999; padding: 4px 6px; text-align: left; vertical-align: top; }
  th { background-color: #f1f1f1; }
  tr { page-break-inside: avoid; }
  thead { display: table-header-group; }
  .exceeded { background-color: #c00000; color: #ffffff; font-weight: bold; padding: 1px 5px; border-radius: 2px; }
  @media print {
    body { margin: 0; }
    table { width: 100%; font-size: 11pt; }
  }
</style>
</head>
<body>
<h1>${heading}</h1>
<table>
<thead><tr><th>SKU</th><th>Standort</th><th>Bezeichnung</th><th>Bestellt</th><th>Bestellungen</th></tr></thead>
<tbody>${rows}</tbody>
</table>
</body>
</html>`;
  };

  const buildInventoryPrintHtml = (heading: string, rows: any[]): string => {
    const body = rows
      .map(
        (item) =>
          `<tr><td>${escapeHtml(String(item.sku ?? ''))}</td><td>${escapeHtml(String(item.title ?? ''))}</td><td>${escapeHtml(
            String(item.lagerstandort ?? ''),
          )}</td><td class="num">${escapeHtml(String(item.stock ?? ''))}</td><td class="num">${escapeHtml(
            String(item.ve ?? ''),
          )}</td></tr>`,
      )
      .join('');
    const stand = new Date().toLocaleDateString('de-DE');
    return `<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>${escapeHtml(heading)}</title>
<style>
  body { font-family: Arial, Helvetica, sans-serif; font-size: 12px; margin: 16px; }
  h1 { font-size: 16px; margin: 0 0 2px 0; }
  .stand { color: #555; margin: 0 0 10px 0; }
  table { border-collapse: collapse; width: 100%; }
  th, td { border: 1px solid #999; padding: 4px 6px; text-align: left; vertical-align: top; }
  th { background-color: #f1f1f1; }
  td.num { text-align: right; }
  tr { page-break-inside: avoid; }
  thead { display: table-header-group; }
  @media print { table { font-size: 11pt; } }
</style>
</head>
<body>
<h1>${escapeHtml(heading)}</h1>
<p class="stand">Stand: ${stand}</p>
<table>
<thead><tr><th>SKU</th><th>Bezeichnung</th><th>Lagerstandort</th><th>Verfügbar</th><th>VE</th></tr></thead>
<tbody>${body}</tbody>
</table>
</body>
</html>`;
  };

  // Druckansichten liegen kurz auf dem eigenen Server (/print/<id>) und öffnen sich in einem normalen
  // neuen Fenster (kein blob:/data:-Link). Das Fenster wird beim Klick sofort geöffnet, damit der
  // Popup-Blocker nicht greift; wird es dennoch blockiert, erscheint stattdessen ein Link.
  const deliverPrintHtml = async (html: string, win: Window | null): Promise<void> => {
    const { path } = await backend.createPrint(html);
    const url = new URL(path, window.location.origin).toString();
    if (win && !win.closed) {
      win.location.href = url;
      setPrintLink('');
    } else {
      setPrintLink(url);
    }
  };

  const runPrint = async (build: () => Promise<string>): Promise<void> => {
    if (activeActionRef.current) return; // läuft bereits eine Aktion, keine zweite Druckansicht öffnen
    // Das Fenster muss direkt im Klick geöffnet werden (vor jeder Wartezeit), sonst blockiert es der Browser.
    const win = preOpenPrintWindow();
    await runExclusive('print', async () => {
      setError('');
      setPrintLink('');
      try {
        const html = await build();
        await deliverPrintHtml(html, win);
      } catch (printError: any) {
        try {
          win?.close();
        } catch {
          // Fenster bereits geschlossen
        }
        setError(`Druckansicht konnte nicht erstellt werden: ${printError?.message || 'Unbekannter Fehler'}`);
      }
    });
  };

  const prepareOrderPrint = async (order: PurchaseOrder): Promise<string> => {
    const skus = order.items.map((i) => i.sku);
    const lagerstandortMap: { [key: string]: string } = {};

    const batchSize = 50;
    for (let i = 0; i < skus.length; i += batchSize) {
      const batch = skus.slice(i, i + batchSize);
      const skuQuery = batch.map((s) => `sku:${s}`).join(' OR ');
      const query = `query ProductVariantsBySku($query: String!) {
        productVariants(first: 50, query: $query) {
          edges {
            node {
              sku
              product {
                lagerstandort: metafield(namespace: "custom", key: "lagerstandort") {
                  value
                }
              }
            }
          }
        }
      }`;
      const { data } = await shopifyQuery(query, { variables: { query: skuQuery } });
      if (data?.productVariants?.edges) {
        for (const edge of data.productVariants.edges) {
          lagerstandortMap[edge.node.sku] = edge.node.product?.lagerstandort?.value || '';
        }
      }
    }

    const customerOrdersBySku = await loadOpenCustomerOrdersBySku(order.date);

    const sortedItems = [...order.items]
      .sort((a, b) => a.sku.localeCompare(b.sku))
      .map((item) => ({
        sku: item.sku,
        lagerstandort: lagerstandortMap[item.sku] ?? item.lagerstandort ?? '',
        title: item.title,
        quantity: item.quantity,
        customerOrders: customerOrdersBySku[item.sku] ?? [],
      }));

    return buildOrderPrintHtml(order.date, sortedItems);
  };

  const prepareOrderPrintWithoutGlazures = async (order: PurchaseOrder): Promise<string> => {
    const skus = order.items.map((i) => i.sku);
    const lagerstandortMap: { [key: string]: string } = {};
    const productTypeMap: { [key: string]: string } = {};

    const batchSize = 50;
    for (let i = 0; i < skus.length; i += batchSize) {
      const batch = skus.slice(i, i + batchSize);
      const skuQuery = batch.map((s) => `sku:${s}`).join(' OR ');
      const query = `query ProductVariantsBySku($query: String!) {
        productVariants(first: 50, query: $query) {
          edges {
            node {
              sku
              product {
                productType
                lagerstandort: metafield(namespace: "custom", key: "lagerstandort") {
                  value
                }
              }
            }
          }
        }
      }`;
      const { data } = await shopifyQuery(query, { variables: { query: skuQuery } });
      if (data?.productVariants?.edges) {
        for (const edge of data.productVariants.edges) {
          lagerstandortMap[edge.node.sku] = edge.node.product?.lagerstandort?.value || '';
          productTypeMap[edge.node.sku] = edge.node.product?.productType || '';
        }
      }
    }

    const customerOrdersBySku = await loadOpenCustomerOrdersBySku(order.date);

    const sortedItems = [...order.items]
      .filter((item) => {
        const pt = productTypeMap[item.sku] || '';
        return pt.toLowerCase() !== 'glasuren';
      })
      .sort((a, b) => a.sku.localeCompare(b.sku))
      .map((item) => ({
        sku: item.sku,
        lagerstandort: lagerstandortMap[item.sku] ?? item.lagerstandort ?? '',
        title: item.title,
        quantity: item.quantity,
        customerOrders: customerOrdersBySku[item.sku] ?? [],
      }));

    return buildOrderPrintHtml(order.date, sortedItems);
  };

  const handlePrintOrder = (order: PurchaseOrder): Promise<void> => runPrint(() => prepareOrderPrint(order));

  const handlePrintOrderWithoutGlazures = (order: PurchaseOrder): Promise<void> =>
    runPrint(() => prepareOrderPrintWithoutGlazures(order));

  const ensureLagerstandortAllowed = async (newValue: string): Promise<boolean> => {
    if (locations.includes(newValue)) return true;

    const defQuery = `query {
      metafieldDefinitions(first: 10, ownerType: PRODUCT, namespace: "custom", key: "lagerstandort") {
        edges {
          node {
            id
            name
            validations { name value }
          }
        }
      }
    }`;

    const { data: defData, errors: defErrors } = await shopifyQuery(defQuery);

    // Wenn keine Definition existiert oder die Abfrage fehlschlägt, direkt versuchen zu speichern.
    if (defErrors?.length > 0 || !defData?.metafieldDefinitions?.edges?.length) return true;

    const definition = defData.metafieldDefinitions.edges[0].node;
    const defName = definition.name;

    // Wenn keine 'choices'-Validierung existiert, gibt es keine Einschränkung.
    const choicesValidation = definition.validations?.find((v: any) => v.name === 'choices');
    if (!choicesValidation) return true;

    let currentChoices: string[] = [];
    try {
      currentChoices = JSON.parse(choicesValidation.value || '[]');
    } catch {
      currentChoices = [];
    }

    if (currentChoices.includes(newValue)) return true;

    const updatedChoices = [...currentChoices, newValue].sort();

    const updateMutation = `mutation MetafieldDefinitionUpdate($definition: MetafieldDefinitionUpdateInput!) {
      metafieldDefinitionUpdate(definition: $definition) {
        updatedDefinition { id }
        userErrors { field message code }
      }
    }`;

    // Versuche die Definition zu aktualisieren, damit der neue Wert erlaubt wird.
    await shopifyQuery(updateMutation, {
      variables: {
        definition: {
          key: 'lagerstandort',
          namespace: 'custom',
          ownerType: 'PRODUCT',
          name: defName,
          validations: [{ name: 'choices', value: JSON.stringify(updatedChoices) }],
        },
      },
    });

    // Unabhängig vom Ergebnis der Definitionsaktualisierung immer den Speicherversuch
    // zulassen. Schlägt der eigentliche metafieldsSet-Aufruf fehl, wird dessen konkrete
    // Fehlermeldung angezeigt.
    return true;
  };

  const deleteOrder = async (orderId: string): Promise<void> => {
    try {
      // Das Backend schreibt „Bestellung gelöscht" je Position in die Historie und löscht die Bestellung.
      await backend.deleteOrder(orderId);
    } catch (deleteError: any) {
      setError(
        `Bestellung konnte nicht gelöscht werden: ${deleteError?.message || 'Unbekannter Fehler'}`,
      );
      return;
    }

    setPurchaseOrders(purchaseOrders.filter((o) => o.id !== orderId));
    setExpandedOrders(expandedOrders.filter((id) => id !== orderId));
    const { [orderId]: _, ...rest } = editingOrders;
    setEditingOrders(rest);
  };

  const addItemToOrder = async (orderId: string, sku: string, quantity: number): Promise<void> => {
    if (!sku || quantity <= 0) {
      setError('Bitte SKU und Menge eingeben');
      return;
    }

    const query = `query ProductVariantBySku($sku: String!) {
      productVariants(first: 1, query: $sku) {
        edges {
          node {
            id
            sku
            title
            displayName
            inventoryItem {
              id
            }
            media(first: 1) {
              edges {
                node {
                  ... on MediaImage {
                    image {
                      url
                    }
                  }
                }
              }
            }
            product {
              featuredMedia {
                preview {
                  image {
                    url
                  }
                }
              }
              lagerstandort: metafield(namespace: "custom", key: "lagerstandort") {
                value
              }
            }
          }
        }
      }
    }`;

    const { data, errors } = await shopifyQuery(query, {
      variables: { sku: `sku:${sku}` },
    });

    if (errors?.length > 0) {
      setError(errors.map((e: any) => e.message).join(', '));
      return;
    }

    if (!data?.productVariants?.edges?.length) {
      setError('SKU nicht gefunden');
      return;
    }

    const variant = data.productVariants.edges[0].node;
    const imageUrl =
      variant.media?.edges?.[0]?.node?.image?.url ||
      variant.product?.featuredMedia?.preview?.image?.url ||
      '';
    const newItem: OrderItem = {
      sku: variant.sku,
      quantity,
      inventoryItemId: variant.inventoryItem.id,
      title: variant.displayName || variant.title,
      imageUrl,
      lagerstandort: variant.product?.lagerstandort?.value || '',
    };

    const editingOrder = editingOrders[orderId];
    if (editingOrder) {
      editingOrder.items.push(newItem);
      setEditingOrders({ ...editingOrders, [orderId]: editingOrder });
    }
  };

  const saveOrderChanges = async (): Promise<void> => {
    setError('');
    const failed: string[] = [];
    let updatedOrders = purchaseOrders;

    for (const id of Object.keys(editingOrders)) {
      const edited = editingOrders[id];
      const original = purchaseOrders.find((o) => o.id === id);
      if (!edited || !original || original.status !== 'open') continue;

      try {
        const saved = await backend.updateOrder(id, {
          items: edited.items.map(toApiItem),
          trackingNumber: edited.trackingNumber || '',
        });
        updatedOrders = updatedOrders.map((o) =>
          o.id === id ? { ...edited, status: saved.status, verbuchtAm: saved.verbuchtAm } : o,
        );
      } catch (saveError: any) {
        failed.push(
          `Bestellung vom ${formatDateDisplay(original.date)}: ${saveError?.message || 'Fehler'}`,
        );
      }
    }

    setPurchaseOrders(updatedOrders);

    if (failed.length > 0) {
      setError(`Änderungen konnten nicht gespeichert werden: ${failed.join('; ')}`);
    } else {
      setSuccess('Änderungen gespeichert');
      setTimeout(() => setSuccess(''), 2000);
    }
  };

  const generateExport = async (): Promise<void> => {
    setLoading(true);
    setError('');

    const allVariants: any[] = [];
    let cursor: string | null = null;
    let hasNextPage: boolean = true;

    while (hasNextPage) {
      const query = `query ProductVariantsPaginated($first: Int!, $after: String) {
        productVariants(first: $first, after: $after) {
          edges {
            node {
              id
              sku
              title
              displayName
              product {
                status
              }
              inventoryItem {
                id
                inventoryLevels(first: 10) {
                  nodes {
                    location {
                      id
                      name
                    }
                    quantities(names: ["available", "on_hand", "committed", "incoming"]) {
                      name
                      quantity
                    }
                  }
                }
              }
            }
          }
          pageInfo {
            hasNextPage
            endCursor
          }
        }
      }`;

      const { data, errors } = await shopifyQuery(query, {
        variables: { first: 250, after: cursor },
      });

      if (errors?.length > 0) {
        setError(errors.map((e: any) => e.message).join(', '));
        setLoading(false);
        return;
      }

      if (data?.productVariants?.edges) {
        // Nur Produkte mit Status „Aktiv" (keine Entwürfe, nicht gelisteten oder archivierten Produkte)
        allVariants.push(
          ...data.productVariants.edges.map((e: any) => e.node).filter((n: any) => n.product?.status === 'ACTIVE'),
        );
      }

      hasNextPage = data?.productVariants?.pageInfo?.hasNextPage || false;
      cursor = data?.productVariants?.pageInfo?.endCursor || null;
    }

    if (allVariants.length === 0) {
      setError('Keine aktiven Produkte gefunden');
      setLoading(false);
      return;
    }

    const { id: activeLocationId } = await resolveActiveLocation();
    const statsBySku = await fetchShopifyQlStats();

    const csvLines: string[] = [
      'SKU,Artikelbezeichnung,Verfügbar,Auf Lager,Zugesagt,Eingehend,Bestellt,' +
        'Bestellmenge (365 Tage),Rückgabemenge (365 Tage),Bestandsreichweite (Tage),Abverkaufsquote',
    ];

    for (const variant of allVariants) {
      const sku = variant.sku || '';
      const title = variant.displayName || variant.title || '';

      let available = 0;
      let onHand = 0;
      let committed = 0;
      let incoming = 0;

      if (variant.inventoryItem?.inventoryLevels?.nodes) {
        for (const level of variant.inventoryItem.inventoryLevels.nodes) {
          if (level.location.id === activeLocationId) {
            for (const q of level.quantities) {
              if (q.name === 'available') available = q.quantity;
              else if (q.name === 'on_hand') onHand = q.quantity;
              else if (q.name === 'committed') committed = q.quantity;
              else if (q.name === 'incoming') incoming = q.quantity;
            }
            break;
          }
        }
      }

      let ordered = 0;
      for (const order of purchaseOrders) {
        if (order.status === 'open') {
          for (const item of order.items) {
            if (item.sku === sku) {
              ordered += item.quantity;
            }
          }
        }
      }

      const stats = statsBySku.get(sku);
      const quantityOrdered365 = stats?.quantityOrdered ?? 0;
      const quantityReturned365 = stats?.quantityReturned ?? 0;
      // Deutsches Dezimaltrennzeichen (Komma statt Punkt) für Excel.
      // In Anführungszeichen, da das CSV selbst Komma als Spaltentrennzeichen nutzt –
      // ohne Quotes würde das Dezimalkomma sonst als zusätzliche Spalte interpretiert.
      const formatDecimal = (n: number | null | undefined): string =>
        n === null || n === undefined ? '' : `"${n.toString().replace('.', ',')}"`;
      const daysRemaining = formatDecimal(stats?.daysOfInventoryRemaining);
      const sellThroughRate = formatDecimal(stats?.sellThroughRate);

      csvLines.push(
        `${sku},"${title}",${available},${onHand},${committed},${incoming},${ordered},` +
          `${quantityOrdered365},${quantityReturned365},${daysRemaining},${sellThroughRate}`,
      );
    }

    const csvContent = csvLines.join('\n');
    setExportData(`data:text/csv;charset=utf-8,${encodeURIComponent(csvContent)}`);
    setLoading(false);
  };

  const loadLocationsList = async (): Promise<void> => {
    setLoading(true);
    setError('');

    const query = `query {
      shop {
        metafield(namespace: "custom", key: "lagerstandort_liste") {
          value
        }
      }
    }`;

    const { data, errors } = await shopifyQuery(query);

    if (errors?.length > 0) {
      setError(errors.map((e: any) => e.message).join(', '));
      setLoading(false);
      return;
    }

    const locationsList = data?.shop?.metafield?.value ? JSON.parse(data.shop.metafield.value) : [];
    setLocations(locationsList);
    setInventoryData([]);
    setInventoryLoaded(false);
    setLoading(false);
  };

  const loadInventory = async (): Promise<void> => {
    setLoading(true);
    setError('');

    const allVariants: any[] = [];
    let cursor: string | null = null;
    let hasNextPage: boolean = true;

    while (hasNextPage) {
      const query = `query ProductVariantsPaginated($first: Int!, $after: String) {
        productVariants(first: $first, after: $after) {
          edges {
            node {
              id
              sku
              title
              displayName
              inventoryItem {
                inventoryLevels(first: 10) {
                  nodes {
                    location {
                      id
                      name
                    }
                    quantities(names: ["available"]) {
                      name
                      quantity
                    }
                  }
                }
              }
              product {
                lagerstandort: metafield(namespace: "custom", key: "lagerstandort") {
                  value
                }
                tags
              }
            }
          }
          pageInfo {
            hasNextPage
            endCursor
          }
        }
      }`;

      const { data, errors } = await shopifyQuery(query, {
        variables: { first: 250, after: cursor },
      });

      if (errors?.length > 0) {
        setError(errors.map((e: any) => e.message).join(', '));
        setLoading(false);
        return;
      }

      if (data?.productVariants?.edges) {
        allVariants.push(...data.productVariants.edges.map((e: any) => e.node));
      }

      hasNextPage = data?.productVariants?.pageInfo?.hasNextPage || false;
      cursor = data?.productVariants?.pageInfo?.endCursor || null;
    }

    if (allVariants.length === 0) {
      setError('Keine Produkte gefunden');
      setLoading(false);
      return;
    }

    const { id: activeLocationId } = await resolveActiveLocation();

    const inventory: any[] = [];
    const uniqueLocations = new Set<string>();

    for (const variant of allVariants) {
      const sku = variant.sku || '';
      const title = variant.displayName || variant.title || '';
      const lagerstandort = variant.product?.lagerstandort?.value || '';

      if (lagerstandort) {
        uniqueLocations.add(lagerstandort);
      }

      let stock = 0;
      if (variant.inventoryItem?.inventoryLevels?.nodes) {
        for (const level of variant.inventoryItem.inventoryLevels.nodes) {
          if (level.location.id === activeLocationId) {
            const qty = level.quantities.find((q: any) => q.name === 'available');
            stock = qty?.quantity || 0;
            break;
          }
        }
      }

      let ve = '';
      if (variant.product?.tags) {
        for (const tag of variant.product.tags) {
          if (tag.startsWith('VE')) {
            const match = tag.match(/VE(\d+)/);
            if (match) {
              ve = match[1];
              break;
            }
          }
        }
      }

      inventory.push({ sku, title, lagerstandort, stock, ve });
    }

    const sortedLocations = Array.from(uniqueLocations).sort();
    setLocations(sortedLocations);
    setInventoryData(inventory);
    setInventoryLoaded(true);

    // Save locations list to shop metafield for use in Order Printer templates
    if (sortedLocations.length > 0 && shopId) {
      const saveLocationsMutation = `mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
        metafieldsSet(metafields: $metafields) {
          metafields {
            id
          }
          userErrors {
            field
            message
          }
        }
      }`;

      await shopifyQuery(saveLocationsMutation, {
        variables: {
          metafields: [
            {
              ownerId: shopId,
              namespace: 'custom',
              key: 'lagerstandort_liste',
              type: 'list.single_line_text_field',
              value: JSON.stringify(sortedLocations),
            },
          ],
        },
      });
      // Silently ignore errors - this is a background operation
    }

    setLoading(false);
  };

  const formatDateDisplay = (isoDate: string): string => {
    const [year, month, day] = isoDate.split('-');
    return `${day}.${month}.${year}`;
  };

  const formatDateTimeDisplay = (isoTimestamp: string): string => {
    const date = new Date(isoTimestamp);
    const day = String(date.getDate()).padStart(2, '0');
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const year = date.getFullYear();
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    return `${day}.${month}.${year} um ${hours}:${minutes}`;
  };

  // Wiederholt eine Shopify-Abfrage bei Drosselung (THROTTLED) bzw. kurzzeitigen Fehlern.
  const shopifyQueryRetry = async (query: string, variables: any, attempts = 4): Promise<{ data: any; errors: any[] }> => {
    let last: { data: any; errors: any[] } = { data: null, errors: [] };
    for (let i = 0; i < attempts; i++) {
      try {
        const res: any = await shopifyQuery(query, { variables });
        last = { data: res?.data ?? null, errors: res?.errors || [] };
      } catch (e: any) {
        last = { data: null, errors: [{ message: e?.message || 'Netzwerkfehler' }] };
      }
      const throttled = last.errors.some((e: any) => /throttl|rate limit|timeout|502|503/i.test(String(e?.message || '')));
      if (last.errors.length === 0 || !throttled) return last;
      await new Promise((r) => setTimeout(r, 1000 * (i + 1) * (i + 1)));
    }
    return last;
  };

  // Auswahl je Artikel: standardmäßig sind alle abweichenden Felder (EK/VK/VE) angehakt.
  // Vorauswahl: nur Felder, bei denen der neue Wert HÖHER ist als der aktuelle (VE: fehlender Tag zählt als 1).
  // Der Tag „Aufschlag" begrenzt die Produktauswahl insgesamt (EK, VK und VE).
  const aufOk = (r: PriceRowView): boolean =>
    priceAufMode === 'all' || (priceAufMode === 'without' ? !r.hasAufschlag : r.hasAufschlag);
  const defaultOn = (r: PriceRowView, k: 'p' | 's' | 'v'): boolean =>
    aufOk(r) &&
    (k === 'p'
      ? direction(r.currentPrice, r.newPrice) === 'up'
      : k === 's'
        ? direction(r.currentSale, r.newSale) === 'up'
        : direction(r.currentVe, r.newVe, 1) === 'up');
  const isOn = (r: PriceRowView, k: 'p' | 's' | 'v'): boolean => priceChoice[r.sku]?.[k] ?? defaultOn(r, k);
  const setChoice = (sku: string, k: 'p' | 's' | 'v', on: boolean): void =>
    setPriceChoice((prev) => ({ ...prev, [sku]: { ...prev[sku], [k]: on } }));
  // Schnellauswahl für alle Artikel: welche Felder sollen geändert werden?
  const setAllChoices = (p: boolean, sv: boolean, v: boolean): void => {
    const next: typeof priceChoice = {};
    for (const r of withSalePrice(priceUpdateResults, parseFactor(priceFactor))) next[r.sku] = { p: p && aufOk(r), s: sv && aufOk(r), v: v && aufOk(r) };
    setPriceChoice(next);
  };

  const handleAnalyzePrices = async (): Promise<void> => {
    if (!priceUpdateFile) {
      setError('Bitte eine Excel-Datei (.xlsx) auswählen');
      return;
    }

    setPriceUpdateLoading(true);
    setError('');

    try {
      if (!/\.xlsx$/i.test(priceUpdateFile.name)) {
        throw new Error('Bitte die Keramix-Preisliste als .xlsx-Datei hochladen.');
      }
      const parsed = await parsePriceListXlsx(await priceUpdateFile.arrayBuffer());
      if (parsed.entries.length === 0) {
        throw new Error(
          'In der Datei wurden keine Artikel gefunden (erwartet: Spalten „KMX Code" und „Preis"). ' + parsed.warnings.join(' '),
        );
      }
      setPriceListInfo(
        [
          `${parsed.entries.length} Artikel aus ${parsed.sheets.length} Blättern eingelesen (` +
            parsed.sheets.map((s) => `${s.name.trim()}: ${s.rows}`).join(', ') +
            ').',
          ...(parsed.sheets.some((s) => !s.hasVe)
            ? [`Ohne VE-Spalte (nur Preise werden geprüft): ${parsed.sheets.filter((s) => !s.hasVe).map((s) => s.name.trim()).join(', ')}.`]
            : []),
          ...(parsed.duplicates.length ? [`Doppelte SKU in der Liste (erste Zeile gilt): ${parsed.duplicates.join(', ')}.`] : []),
          ...parsed.warnings,
        ].join(' '),
      );

      const allVariants: ShopVariant[] = [];
      let cursor: string | null = null;
      let hasNextPage: boolean = true;

      while (hasNextPage) {
        const query = `query ProductVariantsPaginated($first: Int!, $after: String) {
          productVariants(first: $first, after: $after) {
            edges {
              node {
                id
                sku
                displayName
                price
                einkaufspreis: metafield(namespace: "custom", key: "einkaufspreis") {
                  value
                  type
                }
                product {
                  id
                  status
                  tags
                }
              }
            }
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }`;

        const { data, errors } = await shopifyQueryRetry(query, { first: 250, after: cursor });

        if (errors.length > 0) {
          throw new Error(errors.map((e: any) => e.message).join(', '));
        }

        for (const e of data?.productVariants?.edges || []) {
          const n = e.node;
          allVariants.push({
            id: n.id,
            sku: n.sku || '',
            displayName: n.displayName || '',
            ...parseMoneyMetafield(n.einkaufspreis?.value || ''),
            salePrice: n.price || '',
            productId: n.product?.id || '',
            productStatus: n.product?.status || '',
            tags: n.product?.tags || [],
          });
        }

        hasNextPage = data?.productVariants?.pageInfo?.hasNextPage || false;
        cursor = data?.productVariants?.pageInfo?.endCursor || null;
      }

      const cmp = comparePriceList(parsed.entries, allVariants);
      setPriceUpdateResults(cmp.rows);
      setPriceOnlyShop(cmp.onlyInShop);
      setPriceOnlyList(cmp.onlyInList);
      setPriceChoice({});
      setPriceUpdateStep(2);
    } catch (err: any) {
      setError(err.message || 'Fehler beim Analysieren der Datei');
    }

    setPriceUpdateLoading(false);
  };

  const handleUpdatePrices = async (): Promise<void> => {
    const factor = parseFactor(priceFactor);
    if (factor === null) {
      setError('Bitte einen gültigen Faktor für den Verkaufspreis eingeben (z. B. 2,232).');
      return;
    }
    setPriceUpdateLoading(true);
    setError('');
    setPriceUpdateSuccess('');

    const metafieldsMutation = `mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
      metafieldsSet(metafields: $metafields) {
        metafields { id }
        userErrors { field message }
      }
    }`;
    const saleMutation = `mutation VariantPrices($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
      productVariantsBulkUpdate(productId: $productId, variants: $variants) {
        productVariants { id price }
        userErrors { field message }
      }
    }`;
    const tagsMutation = `mutation TagsUpdate($id: ID!, $remove: [String!]!, $add: [String!]!) {
      tagsRemove(id: $id, tags: $remove) { userErrors { message } }
      tagsAdd(id: $id, tags: $add) { userErrors { message } }
    }`;

    const view = withSalePrice(priceUpdateResults, factor);
    const doP = (r: PriceRowView) => r.priceChanged && isOn(r, 'p');
    const doS = (r: PriceRowView) => r.saleChanged && isOn(r, 's');
    const doV = (r: PriceRowView) => r.veChanged && isOn(r, 'v');
    const selected = view.filter((r) => doP(r) || doS(r) || doV(r));

    // Backup der alten Werte VOR jeder Änderung; schlägt es fehl, wird nichts geändert.
    try {
      const tagByProduct = new Map<string, { p: string; s: string[]; a: string[]; r: string[] }>();
      for (const r of selected.filter(doV)) {
        const cur = tagByProduct.get(r.productId) || { p: numId(r.productId), s: [], a: [`VE${r.newVe}`], r: r.currentVeTags };
        cur.s.push(r.sku);
        tagByProduct.set(r.productId, cur);
      }
      await saveBackupEntry({
        id: new Date().toISOString(),
        src: 'pricelist',
        n: priceUpdateFile?.name,
        items: selected
          .filter((r) => doP(r) || doS(r))
          .map((r) => ({
            v: numId(r.variantId),
            p: numId(r.productId),
            s: r.sku,
            t: shorten(r.title),
            c: r.currency,
            ...(doP(r) ? { ek: [r.currentPrice, r.newPrice] as [string, string] } : {}),
            ...(doS(r) ? { vk: [r.currentSale, r.newSale] as [string, string] } : {}),
          })),
        tags: [...tagByProduct.values()].map((t) => ({ p: t.p, s: t.s.join(', '), a: t.a, r: t.r })),
      });
    } catch (e: any) {
      setError(`Backup fehlgeschlagen – es wurde nichts geändert: ${e?.message || 'Fehler'}`);
      setPriceUpdateLoading(false);
      return;
    }

    const failed = new Map<string, string[]>();
    const okP = new Set<string>();
    const okS = new Set<string>();
    const okV = new Set<string>();
    const fail = (sku: string, msg: string) => failed.set(sku, [...(failed.get(sku) || []), msg]);
    const errText = (errors: any[], userErrors?: any[]): string =>
      [...(errors || []).map((e: any) => e.message), ...(userErrors || []).map((e: any) => e.message)].join(', ');

    // 1) Einkaufspreise (Metafeld custom.einkaufspreis, Typ money) in Paketen zu je 25
    const priceRows = selected.filter(doP);
    const metafieldInput = (r: PriceRowView) => ({
      ownerId: r.variantId,
      namespace: 'custom',
      key: 'einkaufspreis',
      type: 'money',
      value: JSON.stringify({ amount: Number(r.newPrice).toFixed(2), currency_code: r.currency || 'EUR' }),
    });
    for (let i = 0; i < priceRows.length; i += 25) {
      const chunk = priceRows.slice(i, i + 25);
      const res = await shopifyQueryRetry(metafieldsMutation, { metafields: chunk.map(metafieldInput) });
      if (!errText(res.errors, res.data?.metafieldsSet?.userErrors)) {
        chunk.forEach((r) => okP.add(r.sku));
        continue;
      }
      // Paket fehlgeschlagen: einzeln wiederholen, damit nur die tatsächlich fehlerhaften Artikel als Fehler gelten
      for (const r of chunk) {
        const one = await shopifyQueryRetry(metafieldsMutation, { metafields: [metafieldInput(r)] });
        const m = errText(one.errors, one.data?.metafieldsSet?.userErrors);
        if (m) fail(r.sku, `Einkaufspreis: ${m}`);
        else okP.add(r.sku);
      }
    }

    // 2) Verkaufspreise je Produkt (productVariantsBulkUpdate)
    const saleByProduct = new Map<string, PriceRowView[]>();
    for (const r of selected.filter(doS)) {
      saleByProduct.set(r.productId, [...(saleByProduct.get(r.productId) || []), r]);
    }
    for (const [productId, list] of saleByProduct) {
      const res = await shopifyQueryRetry(saleMutation, {
        productId,
        variants: list.map((r) => ({ id: r.variantId, price: r.newSale })),
      });
      const m = errText(res.errors, res.data?.productVariantsBulkUpdate?.userErrors);
      if (m) list.forEach((r) => fail(r.sku, `Verkaufspreis: ${m}`));
      else list.forEach((r) => okS.add(r.sku));
    }

    // 3) VE-Tags je Produkt (alte VE-Tags entfernen, neuen hinzufügen; VE1 wird wie jede andere VE gesetzt)
    const doneProducts = new Map<string, string>(); // productId -> Fehlertext ('' = ok)
    for (const r of selected.filter(doV)) {
      if (!doneProducts.has(r.productId)) {
        const res = await shopifyQueryRetry(tagsMutation, {
          id: r.productId,
          remove: r.currentVeTags,
          add: [`VE${r.newVe}`],
        });
        doneProducts.set(
          r.productId,
          errText(res.errors, [...(res.data?.tagsRemove?.userErrors || []), ...(res.data?.tagsAdd?.userErrors || [])]),
        );
      }
      const m = doneProducts.get(r.productId)!;
      if (m) fail(r.sku, `VE-Tag: ${m}`);
      else okV.add(r.sku);
    }

    const salesNow = new Map(view.map((r) => [r.sku, r.newSale]));
    setPriceUpdateResults((prev) =>
      prev.map((r) => {
        const touched = okP.has(r.sku) || okS.has(r.sku) || okV.has(r.sku) || failed.has(r.sku);
        if (!touched) return r;
        const err = failed.get(r.sku);
        const veOk = okV.has(r.sku);
        const next: PriceUpdateResult = {
          ...r,
          currentPrice: okP.has(r.sku) ? r.newPrice : r.currentPrice,
          priceChanged: r.priceChanged && !okP.has(r.sku),
          currentSale: okS.has(r.sku) ? salesNow.get(r.sku) || r.currentSale : r.currentSale,
          currentVe: veOk ? r.newVe : r.currentVe,
          currentVeTags: veOk ? [`VE${r.newVe}`] : r.currentVeTags,
          veChanged: r.veChanged && !veOk,
        };
        if (err) return { ...next, status: 'error' as const, errorMessage: err.join(' | ') };
        return { ...next, status: 'unchanged' as const, errorMessage: r.veConflict ? r.errorMessage : undefined };
      }),
    );

    const okCount = selected.filter((r) => !failed.has(r.sku)).length;
    setPriceUpdateSuccess(
      `${okCount} Artikel erfolgreich aktualisiert` + (failed.size ? `, ${failed.size} mit Fehler (siehe Liste).` : '.'),
    );
    setPriceUpdateLoading(false);
  };

  const handleSaveInventoryEdits = async (): Promise<void> => {
    setInventorySaving(true);
    setError('');
    setSuccess('');

    // Varianten ohne SKU können nicht per SKU gefunden werden und werden nicht gespeichert.
    const skusToProcess = Object.keys(inventoryEdits).filter((sku) => sku !== '');
    if (skusToProcess.length === 0) {
      setInventoryEditMode(false);
      setInventorySaving(false);
      return;
    }

    const { id: locationId, error: locationError } = await resolveActiveLocation();
    if (!locationId) {
      setError(locationError || 'Kein aktiver Lagerstandort gefunden');
      setInventorySaving(false);
      return;
    }

    const failedSkus: string[] = [];
    let updatedCount = 0;

    for (const sku of skusToProcess) {
      let failedReason = '';
      const edits = inventoryEdits[sku];
      const currentItem = inventoryData.find((i) => i.sku === sku);
      if (!currentItem) continue;

      const lagerstandortChanged =
        edits.newLagerstandort !== undefined &&
        edits.newLagerstandort !== null &&
        edits.newLagerstandort !== currentItem.lagerstandort;
      const stockChanged =
        edits.newStock !== undefined &&
        !isNaN(edits.newStock) &&
        edits.newStock !== currentItem.stock;

      if (!lagerstandortChanged && !stockChanged) continue;

      const variantQuery = `query ProductVariantBySku($sku: String!) {
        productVariants(first: 1, query: $sku) {
          edges {
            node {
              id
              inventoryItem {
                id
              }
              product {
                id
              }
            }
          }
        }
      }`;

      const { data: variantData, errors: variantErrors } = await shopifyQuery(variantQuery, {
        variables: { sku: `sku:${sku}` },
      });

      if (variantErrors?.length > 0 || !variantData?.productVariants?.edges?.length) {
        failedSkus.push(sku);
        continue;
      }

      const variant = variantData.productVariants.edges[0].node;
      const productId = variant.product?.id;
      const inventoryItemId = variant.inventoryItem?.id;

      let itemFailed = false;

      if (lagerstandortChanged && productId && edits.newLagerstandort) {
        await ensureLagerstandortAllowed(edits.newLagerstandort);
        {
          const metafieldMutation = `mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
            metafieldsSet(metafields: $metafields) {
              metafields {
                id
              }
              userErrors {
                field
                message
              }
            }
          }`;

          const { data: mfData, errors: mfErrors } = await shopifyQuery(metafieldMutation, {
            variables: {
              metafields: [
                {
                  ownerId: productId,
                  namespace: 'custom',
                  key: 'lagerstandort',
                  type: 'single_line_text_field',
                  value: edits.newLagerstandort,
                },
              ],
            },
          });

          if (mfErrors?.length > 0 || mfData?.metafieldsSet?.userErrors?.length > 0) {
            failedReason =
              mfErrors?.map((e: any) => e.message).join(', ') ||
              mfData?.metafieldsSet?.userErrors?.map((e: any) => e.message).join(', ') ||
              'Unbekannter Fehler';
            itemFailed = true;
          }
        }
      }

      if (stockChanged && inventoryItemId && !itemFailed) {
        const delta = (edits.newStock as number) - currentItem.stock;
        if (delta !== 0) {
          const adjustMutation = `mutation inventoryAdjustQuantities($input: InventoryAdjustQuantitiesInput!, $idempotencyKey: String!) {
            inventoryAdjustQuantities(input: $input) @idempotent(key: $idempotencyKey) {
              inventoryAdjustmentGroup {
                createdAt
              }
              userErrors {
                field
                message
              }
            }
          }`;

          const { data: adjData, errors: adjErrors } = await shopifyQuery(adjustMutation, {
            variables: {
              idempotencyKey: generateIdempotencyKey(),
              input: {
                reason: 'correction',
                name: 'available',
                changes: [
                  {
                    delta,
                    inventoryItemId,
                    locationId,
                  },
                ],
              },
            },
          });

          if (adjErrors?.length > 0 || adjData?.inventoryAdjustQuantities?.userErrors?.length > 0) {
            itemFailed = true;
          }
        }
      }

      if (itemFailed) {
        failedSkus.push(failedReason ? `${sku} (${failedReason})` : sku);
      } else {
        updatedCount++;
      }
    }

    if (failedSkus.length > 0) {
      setError(`Folgende SKUs konnten nicht aktualisiert werden: ${failedSkus.join(', ')}`);
    }

    if (updatedCount > 0) {
      setSuccess(`${updatedCount} Artikel erfolgreich aktualisiert`);
      setTimeout(() => setSuccess(''), 3000);
    }

    const editsSnapshot = inventoryEdits;
    const failedSet = new Set(failedSkus.map((s) => s.split(' (')[0]));

    const updatedInventoryData = inventoryData.map((item) => {
      const edits = editsSnapshot[item.sku];
      if (!edits || failedSet.has(item.sku)) return item;
      const nextItem = { ...item };
      if (
        edits.newLagerstandort !== undefined &&
        edits.newLagerstandort !== '' &&
        edits.newLagerstandort !== item.lagerstandort
      ) {
        nextItem.lagerstandort = edits.newLagerstandort;
      }
      if (edits.newStock !== undefined && edits.newStock !== item.stock) {
        nextItem.stock = edits.newStock;
      }
      return nextItem;
    });
    setInventoryData(updatedInventoryData);

    const mergedLocSet = new Set<string>(locations);
    Object.entries(editsSnapshot).forEach(([sku, edits]) => {
      if (failedSet.has(sku)) return;
      if (edits.newLagerstandort && edits.newLagerstandort.trim() !== '') {
        mergedLocSet.add(edits.newLagerstandort);
      }
    });
    const updatedLocationsList = Array.from(mergedLocSet).sort();
    setLocations(updatedLocationsList);

    // Save updated locations list to shop metafield for use in Order Printer templates
    if (updatedLocationsList.length > 0 && shopId) {
      const saveLocationsMutation = `mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
        metafieldsSet(metafields: $metafields) {
          metafields {
            id
          }
          userErrors {
            field
            message
          }
        }
      }`;

      await shopifyQuery(saveLocationsMutation, {
        variables: {
          metafields: [
            {
              ownerId: shopId,
              namespace: 'custom',
              key: 'lagerstandort_liste',
              type: 'list.single_line_text_field',
              value: JSON.stringify(updatedLocationsList),
            },
          ],
        },
      });
      // Silently ignore errors - this is a background operation
    }

    setInventoryEdits({});
    setInventoryFreeTextRows({});
    setInventoryEditMode(false);
    setInventorySaving(false);
  };

  const handleSearchOrders = (): void => {
    setSearchExecuted(true);
    const query = searchQuery.trim().toLowerCase();
    if (!query) {
      setSearchResults([]);
      setSearchOrderCount(0);
      return;
    }

    const openOrders = purchaseOrders.filter((o) => o.status === 'open');
    const results: any[] = [];
    const matchedOrderIds = new Set<string>();

    for (const order of openOrders) {
      for (const item of order.items) {
        if (item.sku.toLowerCase().includes(query)) {
          results.push({
            orderDate: order.date,
            sku: item.sku,
            title: item.title,
            quantity: item.quantity,
          });
          matchedOrderIds.add(order.id);
        }
      }
    }

    setSearchResults(results);
    setSearchOrderCount(matchedOrderIds.size);
  };

  const loadHistory = async (): Promise<void> => {
    setHistoryLoading(true);
    setError('');

    try {
      // Nur Jahre anbieten, für die es Einträge gibt (ohne Einträge: das laufende Jahr).
      const stats = await backend.getStats();
      const withData = Object.keys(stats?.historyByYear ?? {}).sort((a, b) => b.localeCompare(a));
      const years = withData.length > 0 ? withData : [String(new Date().getFullYear())];
      const year = years.includes(historyYear) ? historyYear : years[0];
      setHistoryYears(years);
      setHistoryYear(year);
      setHistoryData(await backend.getHistory(year));
    } catch (historyError: any) {
      setError(`Historie konnte nicht geladen werden: ${historyError?.message || 'Unbekannter Fehler'}`);
    }
    setHistoryLoading(false);
  };

  // Historie einer abgeschlossenen Buchung erneut senden (die Bestände sind bei Shopify bereits gebucht).
  const retryPendingFinish = async (): Promise<void> => {
    if (!pendingFinish) return;
    setError('');
    try {
      const finished = await backend.finishReceiving(pendingFinish.orderId, {
        bookingId: pendingFinish.bookingId,
        results: pendingFinish.results,
      });
      setPurchaseOrders((prev) =>
        prev.map((o) =>
          o.id === pendingFinish.orderId
            ? { ...o, status: finished.order.status, verbuchtAm: finished.order.verbuchtAm }
            : o,
        ),
      );
      setPendingFinish(null);
      setSuccess('Historie wurde gespeichert.');
      setTimeout(() => setSuccess(''), 3000);
    } catch (retryError: any) {
      setError(
        `Historie konnte weiterhin nicht gespeichert werden: ${retryError?.message || 'Backend nicht erreichbar'}`,
      );
    }
  };

  // Zeigt, in welche Datenbank die App gerade schreibt (schützt vor Verwechslung von Test-/Produktiv-Datei).
  const describeDatabase = async (): Promise<string[]> => {
    try {
      const d = await backend.getDiag();
      const lines = [
        `Aktive Datenbank: ${d.dbPath} – ${d.orders} Bestellungen, ${d.history} Historieneinträge${d.importDone ? ', Import bereits ausgeführt (gesperrt)' : ''}`,
      ];
      if (d.sameFileAsSessions) {
        lines.push('ACHTUNG: Die Datenbank ist dieselbe Datei wie der Session-Speicher. DB_PATH und SESSION_DB_PATH müssen verschieden sein.');
      }
      return lines;
    } catch (diagError: any) {
      return [`Aktive Datenbank: nicht ermittelbar (${diagError?.message || 'Fehler'})`];
    }
  };

  const checkBackend = async (): Promise<void> => {
    setMigrationBusy(true);
    setError('');
    const log: string[] = [];
    try {
      const health = await fetch("/healthz");
      log.push(`Erreichbarkeit: HTTP ${health.status} ${await health.text()}`);
    } catch (checkError: any) {
      log.push(`Erreichbarkeit: FEHLGESCHLAGEN (${checkError?.message || 'Netzwerkfehler'})`);
    }
    try {
      const stats = await backend.getStats();
      log.push(
        `Anmeldung mit Shopify-Token: OK – Bestellungen ${JSON.stringify(stats.ordersByStatus)}, Historie ${JSON.stringify(stats.historyByYear)}`,
      );
    } catch (checkError: any) {
      log.push(`Anmeldung mit Shopify-Token: FEHLGESCHLAGEN (${checkError?.message || 'Fehler'})`);
    }
    log.push(...(await describeDatabase()));
    setMigrationLog(log);
    setMigrationBusy(false);
  };

  const readMigrationSources = async (): Promise<void> => {
    setMigrationBusy(true);
    setError('');
    setMigrationReport(null);
    try {
      const sources = await backend.readMetafieldSources();
      setMigrationSources(sources);
      if (migrationBackupUrl) URL.revokeObjectURL(migrationBackupUrl);
      setMigrationBackupUrl(backend.makeRawBackupUrl(sources));
      setMigrationLog([
        ...(await describeDatabase()),
        `Bestellungen im Metafeld: ${sources.orders.length}`,
        ...sources.historySources.map(
          (source) => `Historie "${source.name}": ${source.entries.length} Einträge`,
        ),
      ]);
    } catch (readError: any) {
      setError(readError?.message || 'Metafelder konnten nicht gelesen werden');
    }
    setMigrationBusy(false);
  };

  const runMigration = async (dryRun: boolean): Promise<void> => {
    if (!migrationSources) return;
    setMigrationBusy(true);
    setError('');
    setSuccess('');
    try {
      const report = await backend.migrateFromMetafields(migrationSources, dryRun);
      setMigrationReport(report);
      if (!dryRun) {
        setPurchaseOrders((await backend.listOrders('open,processing')) as PurchaseOrder[]);
        setSuccess('Import abgeschlossen. Bitte die Zahlen unten mit den Erwartungen vergleichen.');
      }
    } catch (migrationError: any) {
      setError(migrationError?.message || 'Import fehlgeschlagen');
    }
    setMigrationBusy(false);
  };

  if (loading && view === 'menu') {
    return (
      <s-page heading="Bestellverwaltung">
        <s-section>
          <s-spinner />
        </s-section>
      </s-page>
    );
  }

  if (view === 'menu') {
    return (
      <s-page heading="Bestellverwaltung">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {success && <s-banner tone="success">{success}</s-banner>}
          {systemStatus && (systemStatus.stuckBookings > 0 || systemStatus.unclearItems > 0) && (
            <s-banner tone="critical">
              Unterbrochene Wareneingangs-Buchung erkannt ({systemStatus.stuckBookings} unfertig, {systemStatus.unclearItems}{' '}
              Artikel mit unklarem Stand). Bitte unter „Bestellungen mit Verbuchungsproblemen" prüfen, nicht blind erneut
              buchen.
            </s-banner>
          )}
          {systemStatus?.monitor && systemStatus.monitor.problems?.length > 0 && (
            <s-banner tone="critical">
              <s-stack direction="block" gap="small">
                <s-text type="strong">Die Überwachung meldet Probleme:</s-text>
                {systemStatus.monitor.problems.map((p: any) => (
                  <s-text key={p.key}>
                    {p.level}: {p.subject}
                  </s-text>
                ))}
              </s-stack>
            </s-banner>
          )}
          {systemStatus?.monitor &&
            systemStatus.monitor.lastRun &&
            Date.now() - Date.parse(systemStatus.monitor.lastRun) > 3 * 3600 * 1000 && (
              <s-banner tone="warning">
                Die Überwachung hat sich seit {formatDateTimeDisplay(systemStatus.monitor.lastRun)} nicht gemeldet
                (Cron-Job prüfen).
              </s-banner>
            )}
          {systemStatus &&
            !systemStatus.monitor && (
              <s-banner tone="info">
                Die automatische Überwachung ist noch nicht eingerichtet (Cron-Job monitor.js, siehe Betriebshandbuch).
              </s-banner>
            )}
          {systemStatus?.lastBackup && Date.now() - Date.parse(systemStatus.lastBackup.at) > 26 * 3600 * 1000 && (
            <s-banner tone="warning">
              Das letzte Backup ist vom {formatDateTimeDisplay(systemStatus.lastBackup.at)} – älter als 26 Stunden.
            </s-banner>
          )}

      <s-section heading="Standort für Bestandsbuchungen">
        <s-stack direction="block" gap="base">
          <s-text>
            Wareneingang, Lagerbestand und CSV-Export verwenden diesen Standort. Ohne Auswahl gilt wie bisher der
            erste aktive Standort im Shop.
          </s-text>
          <s-select
            label="Standort"
            ref={listen('change', (e: any) => saveConfiguredLocation(e.currentTarget.value))}
            {...(configuredLocationId ? { value: configuredLocationId } : {})}
            {...(locationSaving ? { disabled: true } : {})}
          >
            <s-option value="">Automatisch (erster aktiver Standort)</s-option>
            {availableLocations.map((loc) => (
              <s-option key={loc.id} value={loc.id}>
                {loc.name}
              </s-option>
            ))}
          </s-select>
        </s-stack>
      </s-section>
          {pendingFinish && (
            <s-banner tone="warning">
              <s-stack direction="block" gap="small">
                <s-text>
                  Ein Wareneingang wurde bei Shopify gebucht, aber die Historie ist noch nicht
                  gespeichert. Nicht erneut buchen.
                </s-text>
                <s-button onClick={() => runExclusive('retryFinish', retryPendingFinish)} {...busyProps('retryFinish')}>
                  Historie erneut speichern
                </s-button>
              </s-stack>
            </s-banner>
          )}
          <s-box paddingBlockStart="large">
          <s-stack gap="base" direction="block">
            <s-button variant="primary" onClick={() => setView('order')}>
              Bestellung erfassen
            </s-button>
            <s-button onClick={() => setView('receiving')}>Wareneingang verbuchen</s-button>
            <s-button onClick={() => setView('completed-orders')}>
              Bestellungen mit Verbuchungsproblemen
            </s-button>
            <s-stack direction="inline" gap="base">
              <s-button onClick={() => setView('view-orders')}>
                Bestellungen anzeigen/drucken
              </s-button>
              <s-button onClick={() => setView('search-orders')}>
                Bestellte Artikel abfragen
              </s-button>
            </s-stack>
            <s-button
              onClick={() => {
                setView('export');
                generateExport();
              }}
            >
              Export Inventar mit offenen Bestellungen
            </s-button>
            <s-button
              onClick={() => {
                setView('history');
                loadHistory();
              }}
            >
              Bestellhistorie
            </s-button>
            <s-button
              onClick={() => {
                setPriceUpdateStep(1);
                setPriceUpdateResults([]);
                setPriceChoice({});
                setPriceUpdateSuccess('');
                setPriceUpdateFile(null);
                setPriceListInfo('');
                setPriceOnlyShop([]);
                setPriceOnlyList([]);
                setShowOnlyShop(false);
                setShowOnlyList(false);
                setView('price-update');
              }}
            >
              Einkaufspreise mit Keramix-Preisliste aktualisieren
            </s-button>
            <s-button
              onClick={() => {
                setView('inventory');
                loadLocationsList();
              }}
            >
              Lagerbestand anzeigen (pro Lagerstandort)
            </s-button>
            <s-button onClick={() => setView('manual-price')}>Preisanpassung manuell</s-button>
            <s-button onClick={() => setView('backups')}>Preis-Backups (Wiederherstellen)</s-button>
          </s-stack>
          </s-box>
        </s-section>
      </s-page>
    );
  }

  if (view === 'order') {
    return (
      <s-page heading="Bestellung erfassen">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {success && <s-banner tone="success">{success}</s-banner>}
          {importWarning && <s-banner tone="warning">{importWarning}</s-banner>}

          <s-text-area
            label="Bestelldaten eingeben"
            placeholder={`Artikel Menge Stk/Karton\nANTI 4 12\nBD 2 12\nBERTA 3 8`}
            rows={10}
            value={orderInput}
            onInput={(e: any) => setOrderInput(e.currentTarget.value)}
          />

          <s-button onClick={() => runExclusive('import', parseOrderInput)} {...busyProps('import')}>
            Importieren
          </s-button>

          {importedItems.length > 0 && (
            <>
              <s-section padding="none">
                <s-table>
                  <s-table-header-row>
                    <s-table-header listSlot="primary">Bild</s-table-header>
                    <s-table-header listSlot="labeled">SKU</s-table-header>
                    <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                    <s-table-header listSlot="labeled">Bestellte Menge</s-table-header>
                  </s-table-header-row>
                  <s-table-body>
                    {importedItems.map((item, idx) => (
                      <s-table-row key={idx}>
                        <s-table-cell>
                          {item.imageUrl && <s-thumbnail src={item.imageUrl} alt={item.title} />}
                        </s-table-cell>
                        <s-table-cell>{item.sku}</s-table-cell>
                        <s-table-cell>{item.title}</s-table-cell>
                        <s-table-cell>{item.quantity}</s-table-cell>
                      </s-table-row>
                    ))}
                  </s-table-body>
                </s-table>
              </s-section>

              <s-text-field
                label="Bestelldatum (TT.MM.JJJJ)"
                placeholder="01.01.2024"
                value={orderDateDisplay}
                onInput={(e: any) => {
                  const val = e.currentTarget.value;
                  setOrderDateDisplay(val);
                  if (/^\d{2}\.\d{2}\.\d{4}$/.test(val)) {
                    const [dd, mm, yyyy] = val.split('.');
                    setOrderDate(`${yyyy}-${mm}-${dd}`);
                  } else {
                    setOrderDate('');
                  }
                }}
              />

              <s-stack direction="inline" gap="base" justifyContent="end">
                <s-button onClick={() => setView('menu')}>Zurück</s-button>
                <s-button variant="primary" onClick={() => runExclusive('saveOrder', saveOrder)} {...busyProps('saveOrder')}>
                  Speichern
                </s-button>
              </s-stack>
            </>
          )}

          {importedItems.length === 0 && (
            <s-button onClick={() => setView('menu')}>Zurück</s-button>
          )}
        </s-section>
      </s-page>
    );
  }

  if (view === 'receiving') {
    const openOrders = purchaseOrders.filter((o) => o.status === 'open');

    return (
      <s-page heading="Wareneingang - Bestellung auswählen">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {openOrders.length === 0 && <s-text>Keine offenen Bestellungen vorhanden</s-text>}
          {receivingPreparing && (
            <s-stack direction="block" gap="small">
              <s-text color="subdued">Artikeldaten werden geladen...</s-text>
              <s-spinner />
            </s-stack>
          )}
          <s-stack gap="base" direction="block">
            {openOrders.map((order) => (
              <s-button
                key={order.id}
                disabled={(receivingPreparing) || undefined}
                onClick={() => startReceiving(order.id)}
              >
                Bestellung vom {formatDateDisplay(order.date)} - {order.items.length} Artikel
              </s-button>
            ))}
            <s-button onClick={() => setView('menu')}>Zurück</s-button>
          </s-stack>
        </s-section>
      </s-page>
    );
  }

  if (view === 'receiving-details') {
    const order = purchaseOrders.find((o) => o.id === selectedOrderId);
    if (!order) {
      return (
        <s-page heading="Wareneingang">
          <s-section>
            <s-text>Bestellung nicht gefunden</s-text>
            <s-button onClick={() => setView('menu')}>Zurück</s-button>
          </s-section>
        </s-page>
      );
    }

    if (showIdempotencyWarning) {
      return (
        <s-page heading="Wareneingang">
          <s-section>
            {unclearSkus.length > 0 && (
              <s-banner tone="critical">
                <s-stack direction="block" gap="small">
                  <s-text type="strong">Unterbrochene Buchung erkannt</s-text>
                  <s-text>
                    Bei diesen Artikeln ist unklar, ob der Bestand bei Shopify bereits erhöht wurde:{' '}
                    {unclearSkus.map((u) => `${u.sku} (+${u.receivedQuantity})`).join(', ')}.
                  </s-text>
                  <s-text>
                    Bitte im Shopify-Admin den Bestandsverlauf dieser Artikel prüfen. Die angenommene Menge steht
                    deshalb auf 0 – nur dann erhöhen, wenn die Buchung nachweislich nicht erfolgt ist.
                  </s-text>
                </s-stack>
              </s-banner>
            )}
            <s-banner tone="warning">
              Wareneingang für diese Bestellung wurde bereits am{' '}
              {formatDateTimeDisplay(order.verbuchtAm!)} verbucht (oder der Vorgang wurde
              unterbrochen). Was möchten Sie tun?
            </s-banner>
            <s-stack direction="block" gap="base">
              <s-text>
                Option 1: Abbrechen — zurück zur Bestellauswahl (empfohlen wenn Buchung erfolgreich
                war)
              </s-text>
              <s-text>
                Option 2: Erneut verbuchen — Wareneingang nochmals buchen (nur wenn Buchung
                unvollständig war)
              </s-text>
              <s-stack direction="inline" gap="base" justifyContent="end">
                <s-button onClick={() => setView(receivingReturnView)}>Abbrechen</s-button>
                <s-button
                  variant="primary"
                  onClick={async () => {
                    await logDoubleBookingAttempt(order);
                    setShowIdempotencyWarning(false);
                  }}
                >
                  Ja, erneut verbuchen
                </s-button>
              </s-stack>
            </s-stack>
          </s-section>
        </s-page>
      );
    }

    const differingItems = receivingItems.filter((item) => item.receivedQuantity !== item.quantity);

    return (
      <s-page heading="Wareneingang verbuchen">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {success && <s-banner tone="success">{success}</s-banner>}
          {unclearSkus.length > 0 && (
              <s-banner tone="critical">
                <s-stack direction="block" gap="small">
                  <s-text type="strong">Unterbrochene Buchung erkannt</s-text>
                  <s-text>
                    Bei diesen Artikeln ist unklar, ob der Bestand bei Shopify bereits erhöht wurde:{' '}
                    {unclearSkus.map((u) => `${u.sku} (+${u.receivedQuantity})`).join(', ')}.
                  </s-text>
                  <s-text>
                    Bitte im Shopify-Admin den Bestandsverlauf dieser Artikel prüfen. Die angenommene Menge steht
                    deshalb auf 0 – nur dann erhöhen, wenn die Buchung nachweislich nicht erfolgt ist.
                  </s-text>
                </s-stack>
              </s-banner>
            )}

          {pendingFinish && !processing && (
            <s-banner tone="warning">
              <s-stack direction="block" gap="small">
                <s-text type="strong">
                  Bei Shopify gebucht, Historie noch nicht gespeichert – bitte NICHT erneut buchen.
                </s-text>
                <s-text>
                  Gebucht:{' '}
                  {pendingFinish.results
                    .filter((r) => r.outcome === 'booked')
                    .map((r) => `${r.sku} ${r.receivedQuantity}`)
                    .join(', ')}
                </s-text>
                <s-button onClick={() => runExclusive('retryFinish', retryPendingFinish)} {...busyProps('retryFinish')}>
                  Historie erneut speichern
                </s-button>
              </s-stack>
            </s-banner>
          )}

          {processing && (
            <s-banner tone="info">
              <s-stack direction="block" gap="small">
                <s-text type="strong">Wareneingang wird verbucht – bitte nicht schließen…</s-text>
                <s-text>{receivingProgress.phase}</s-text>
                {receivingProgress.total > 0 && (
                  <s-text>
                    {'█'.repeat(Math.round((receivingProgress.current / receivingProgress.total) * 20))}
                    {'░'.repeat(
                      20 - Math.round((receivingProgress.current / receivingProgress.total) * 20),
                    )}{' '}
                    {Math.round((receivingProgress.current / receivingProgress.total) * 100)} % –{' '}
                    {receivingProgress.current} von {receivingProgress.total} Artikeln abgeschlossen
                  </s-text>
                )}
                {receivingProgress.sku && (
                  <s-text color="subdued">Aktuelle SKU: {receivingProgress.sku}</s-text>
                )}
                <s-spinner />
              </s-stack>
            </s-banner>
          )}

          <s-stack gap="none" direction="block">
            <s-text color="subdued">
              Erklärung: Nur die Zahl im Feld "angenommene Menge" wird als Zugang verbucht.
            </s-text>
            <s-text color="subdued">
              - Bei Lieferung kaputter Artikel/zu wenig Stück -&gt; angenommene Menge entsprechend
              verringern.
            </s-text>
            <s-text color="subdued">
              - Bei Mehrlieferung angenommene Menge entsprechend erhöhen.
            </s-text>
            <s-text color="subdued">
              - Wurden zusätzliche SKU geliefert, müssen diese zuerst über "Bestellungen
              anzeigen/drucken" ergänzt werden.
            </s-text>
          </s-stack>

          <s-box padding="base" />

          <s-text>Bestellung vom {formatDateDisplay(order.date)}</s-text>

          {applyPreviousBookings && (
            <s-banner tone="info">
              Die angezeigten Mengen berücksichtigen bereits verbuchte Zugänge laut Bestellhistorie.
              Bitte prüfen Sie die Mengen vor dem Buchen.
            </s-banner>
          )}

          {showReceivingConfirm && (
            <>
              <s-banner tone="warning">Folgende Artikel weichen von der Bestellmenge ab:</s-banner>
              <s-section padding="none">
                <s-table>
                  <s-table-header-row>
                    <s-table-header listSlot="primary">SKU</s-table-header>
                    <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                    <s-table-header listSlot="labeled">Bestellt</s-table-header>
                    <s-table-header listSlot="labeled">Angenommen</s-table-header>
                    <s-table-header listSlot="labeled">Differenz</s-table-header>
                  </s-table-header-row>
                  <s-table-body>
                    {differingItems.map((item, idx) => (
                      <s-table-row key={idx}>
                        <s-table-cell>{item.sku}</s-table-cell>
                        <s-table-cell>{item.title}</s-table-cell>
                        <s-table-cell>{item.quantity}</s-table-cell>
                        <s-table-cell>{item.receivedQuantity}</s-table-cell>
                        <s-table-cell>{item.receivedQuantity - item.quantity}</s-table-cell>
                      </s-table-row>
                    ))}
                  </s-table-body>
                </s-table>
              </s-section>
              <s-stack direction="inline" gap="base" justifyContent="end">
                <s-button disabled={(processing) || undefined} onClick={() => setShowReceivingConfirm(false)}>
                  Abbrechen
                </s-button>
                <s-button variant="primary" disabled={(processing) || undefined} onClick={handleProcessReceiving}>
                  Ja, trotzdem buchen
                </s-button>
              </s-stack>
            </>
          )}

          {!showReceivingConfirm && (
            <>
              <s-section padding="none">
                <s-table>
                  <s-table-header-row>
                    <s-table-header listSlot="primary">Bild</s-table-header>
                    <s-table-header listSlot="labeled">SKU</s-table-header>
                    <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                    <s-table-header listSlot="labeled">Bestellte Menge</s-table-header>
                    {applyPreviousBookings && (
                      <s-table-header listSlot="labeled">Bereits verbucht</s-table-header>
                    )}
                    <s-table-header listSlot="labeled">Angenommene Menge</s-table-header>
                  </s-table-header-row>
                  <s-table-body>
                    {receivingItems.map((item, idx) => {
                      const bookedQuantity = alreadyBookedBySku[item.sku] || 0;
                      const fullyBooked: boolean =
                        applyPreviousBookings && bookedQuantity >= item.quantity;
                      return (
                        <s-table-row key={idx}>
                          <s-table-cell>
                            {item.imageUrl && <s-thumbnail src={item.imageUrl} alt={item.title} />}
                          </s-table-cell>
                          <s-table-cell>{item.sku}</s-table-cell>
                          <s-table-cell>{item.title}</s-table-cell>
                          <s-table-cell>{item.quantity}</s-table-cell>
                          {applyPreviousBookings && (
                            <s-table-cell>
                              {fullyBooked ? (
                                <s-badge tone="success">
                                  bereits verbucht ({bookedQuantity})
                                </s-badge>
                              ) : (
                                <s-text color="subdued">{bookedQuantity}</s-text>
                              )}
                            </s-table-cell>
                          )}
                          <s-table-cell>
                            <s-number-field
                              label=""
                              labelAccessibilityVisibility="exclusive"
                              value={String(item.receivedQuantity)}
                              onInput={(e: any) => {
                                const val = parseInt(e.currentTarget.value) || 0;
                                setReceivingItems(
                                  receivingItems.map((ri, i) =>
                                    i === idx ? { ...ri, receivedQuantity: val } : ri,
                                  ),
                                );
                              }}
                            />
                          </s-table-cell>
                        </s-table-row>
                      );
                    })}
                  </s-table-body>
                </s-table>
              </s-section>

              <s-stack direction="inline" gap="base" justifyContent="end">
                <s-button disabled={(processing) || undefined} onClick={() => setView(receivingReturnView)}>
                  Zurück
                </s-button>
                <s-button
                  variant="primary"
                  disabled={(processing) || undefined}
                  onClick={checkAndProcessReceiving}
                >
                  {processing ? 'Wird verbucht...' : 'Wareneingang buchen'}
                </s-button>
              </s-stack>
            </>
          )}
        </s-section>
      </s-page>
    );
  }

  if (view === 'completed-orders') {
    const finishedOrders = purchaseOrders.filter((o) => o.status === 'processing');

    return (
      <s-page heading="Bestellungen mit Verbuchungsproblemen">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {success && <s-banner tone="success">{success}</s-banner>}
          <s-text color="subdued">
            Bestellungen mit dem Status "Unvollständig / in Verarbeitung" wurden nicht (vollständig)
            verbucht. Über "Prüfen / erneut buchen" können sie nachgebucht werden; bereits verbuchte
            Mengen werden dabei aus der Bestellhistorie berücksichtigt. Mit "Löschen" wird eine
            Bestellung aus dieser Liste entfernt.
          </s-text>
          {finishedOrders.length === 0 && (
            <s-text>Keine Bestellungen mit Verbuchungsproblemen vorhanden</s-text>
          )}
          {receivingPreparing && (
            <s-stack direction="block" gap="small">
              <s-text color="subdued">Artikeldaten werden geladen...</s-text>
              <s-spinner />
            </s-stack>
          )}
        </s-section>

        {finishedOrders.length > 0 && (
          <s-section padding="none">
            <s-table>
              <s-table-header-row>
                <s-table-header listSlot="primary">Bestelldatum</s-table-header>
                <s-table-header listSlot="labeled">Artikel</s-table-header>
                <s-table-header listSlot="labeled">Status</s-table-header>
                <s-table-header listSlot="labeled">Verbucht am</s-table-header>
                <s-table-header listSlot="labeled">Aktion</s-table-header>
              </s-table-header-row>
              <s-table-body>
                {finishedOrders.map((finishedOrder) => (
                  <s-table-row key={finishedOrder.id}>
                    <s-table-cell>{formatDateDisplay(finishedOrder.date)}</s-table-cell>
                    <s-table-cell>{finishedOrder.items.length}</s-table-cell>
                    <s-table-cell>
                      {finishedOrder.status === 'processing' ? (
                        <s-badge tone="caution">Unvollständig / in Verarbeitung</s-badge>
                      ) : (
                        <s-badge tone="success">Verbucht</s-badge>
                      )}
                    </s-table-cell>
                    <s-table-cell>
                      {finishedOrder.verbuchtAm
                        ? formatDateTimeDisplay(finishedOrder.verbuchtAm)
                        : ''}
                    </s-table-cell>
                    <s-table-cell>
                      <s-stack direction="inline" gap="small">
                        <s-button
                          disabled={(receivingPreparing) || undefined}
                          onClick={() => startReceiving(finishedOrder.id, 'completed-orders')}
                        >
                          Prüfen / erneut buchen
                        </s-button>
                        {confirmDeleteOrderId !== finishedOrder.id ? (
                          <s-button
                            tone="critical"
                            onClick={() => setConfirmDeleteOrderId(finishedOrder.id)}
                          >
                            Löschen
                          </s-button>
                        ) : (
                          <>
                            <s-button
                              tone="critical"
                              onClick={() =>
                                runExclusive('delete', async () => {
                                  await deleteOrder(finishedOrder.id);
                                  setConfirmDeleteOrderId(null);
                                })
                              }
                              {...busyProps('delete')}
                            >
                              Wirklich löschen?
                            </s-button>
                            <s-button onClick={() => setConfirmDeleteOrderId(null)}>
                              Abbrechen
                            </s-button>
                          </>
                        )}
                      </s-stack>
                    </s-table-cell>
                  </s-table-row>
                ))}
              </s-table-body>
            </s-table>
          </s-section>
        )}

        <s-stack direction="inline" gap="base" justifyContent="end">
          <s-button onClick={() => setView('menu')}>Zurück</s-button>
        </s-stack>
      </s-page>
    );
  }

  if (view === 'view-orders') {
    const openOrders = purchaseOrders.filter((o) => o.status === 'open');

    return (
      <s-page heading="Bestellungen anzeigen/drucken">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {success && <s-banner tone="success">{success}</s-banner>}

          {printLink && (
            <s-banner tone="info">
              <s-stack direction="block" gap="small">
                <s-text>Der Browser hat das neue Fenster blockiert. Druckansicht hier öffnen:</s-text>
                <s-link href={printLink} target="_blank">
                  Druckansicht öffnen
                </s-link>
              </s-stack>
            </s-banner>
          )}

          {openOrders.length === 0 && <s-text>Keine offenen Bestellungen vorhanden</s-text>}

          {openOrders.map((order) => {
            const isExpanded = expandedOrders.includes(order.id);
            const editingOrder = editingOrders[order.id] || order;
            const newSku = newItemInputs[order.id]?.sku || '';
            const newQty = newItemInputs[order.id]?.qty || '';

            const setNewSku = (value: string): void => {
              setNewItemInputs({
                ...newItemInputs,
                [order.id]: { ...newItemInputs[order.id], sku: value, qty: newQty },
              });
            };

            const setNewQty = (value: string): void => {
              setNewItemInputs({
                ...newItemInputs,
                [order.id]: { ...newItemInputs[order.id], sku: newSku, qty: value },
              });
            };

            return (
              <s-box
                key={order.id}
                padding="base"
                borderWidth="base"
                borderColor="base"
                borderRadius="base"
              >
                <s-clickable onClick={() => toggleOrderExpansion(order.id)}>
                  <s-stack
                    direction="inline"
                    gap="base"
                    justifyContent="space-between"
                    alignItems="center"
                  >
                    <s-text type="strong">
                      {isExpanded ? '▼' : '▶'} Bestellung vom {formatDateDisplay(order.date)} -{' '}
                      {order.items.length} Artikel
                    </s-text>
                    <s-stack direction="inline" gap="base">
                      <s-button
                        onClick={(e: any) => {
                          e.stopPropagation();
                          setView('menu');
                        }}
                      >
                        Zurück
                      </s-button>
                      <s-button
                        onClick={(e: any) => {
                          e.stopPropagation();
                          handlePrintOrder(order);
                        }}
                        {...busyProps('print')}
                      >
                        Drucken
                      </s-button>
                      <s-button
                        onClick={(e: any) => {
                          e.stopPropagation();
                          handlePrintOrderWithoutGlazures(order);
                        }}
                        {...busyProps('print')}
                      >
                        Drucken ohne Glasuren
                      </s-button>
                      {confirmDeleteOrderId !== order.id ? (
                        <s-button
                          tone="critical"
                          onClick={(e: any) => {
                            e.stopPropagation();
                            setConfirmDeleteOrderId(order.id);
                          }}
                        >
                          Löschen
                        </s-button>
                      ) : (
                        <>
                          <s-button
                            tone="critical"
                            onClick={(e: any) => {
                              e.stopPropagation();
                              deleteOrder(order.id);
                              setConfirmDeleteOrderId(null);
                            }}
                          >
                            Wirklich löschen?
                          </s-button>
                          <s-button
                            onClick={(e: any) => {
                              e.stopPropagation();
                              setConfirmDeleteOrderId(null);
                            }}
                          >
                            Abbrechen
                          </s-button>
                        </>
                      )}
                    </s-stack>
                  </s-stack>
                </s-clickable>

                {isExpanded && (
                  <s-stack gap="base" direction="block">
                    <s-stack direction="inline" gap="base" alignItems="center">
                      <s-text-field
                        label="Sendungsnummer"
                        value={editingOrder.trackingNumber || ''}
                        onInput={(e: any) => {
                          const updated = {
                            ...editingOrder,
                            trackingNumber: e.currentTarget.value,
                          };
                          setEditingOrders({ ...editingOrders, [order.id]: updated });
                        }}
                      />
                      <s-link
                        href="https://elogistics.dachser.com/shp2s/ShpApplicationPage?3"
                        target="_blank"
                      >
                        Dachser Tracking öffnen
                      </s-link>
                    </s-stack>

                    <s-section padding="none">
                      <s-table>
                        <s-table-header-row>
                          <s-table-header listSlot="primary">Bild</s-table-header>
                          <s-table-header listSlot="labeled">SKU</s-table-header>
                          <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                          <s-table-header listSlot="labeled">Bestellte Menge</s-table-header>
                          <s-table-header listSlot="labeled">Neue Menge</s-table-header>
                        </s-table-header-row>
                        <s-table-body>
                          {editingOrder.items.map((item: OrderItem, idx: number) => (
                            <s-table-row key={idx}>
                              <s-table-cell>
                                {item.imageUrl && (
                                  <s-thumbnail src={item.imageUrl} alt={item.title} />
                                )}
                              </s-table-cell>
                              <s-table-cell>{item.sku}</s-table-cell>
                              <s-table-cell>{item.title}</s-table-cell>
                              <s-table-cell>{item.quantity}</s-table-cell>
                              <s-table-cell>
                                <s-number-field
                                  label=""
                                  labelAccessibilityVisibility="exclusive"
                                  value={String(item.quantity)}
                                  onInput={(e: any) => {
                                    const val = parseInt(e.currentTarget.value) || 0;
                                    const updatedItems = [...editingOrder.items];
                                    updatedItems[idx] = { ...updatedItems[idx], quantity: val };
                                    setEditingOrders({
                                      ...editingOrders,
                                      [order.id]: { ...editingOrder, items: updatedItems },
                                    });
                                  }}
                                />
                              </s-table-cell>
                            </s-table-row>
                          ))}
                        </s-table-body>
                      </s-table>
                    </s-section>

                    <s-stack direction="inline" gap="small" alignItems="center">
                      <s-text>Artikel ergänzen:</s-text>
                      <s-text-field
                        label="SKU"
                        labelAccessibilityVisibility="exclusive"
                        value={newSku}
                        onInput={(e: any) => setNewSku(e.currentTarget.value)}
                      />
                      <s-number-field
                        label="Menge"
                        labelAccessibilityVisibility="exclusive"
                        value={newQty}
                        onInput={(e: any) => setNewQty(e.currentTarget.value)}
                      />
                      <s-button
                        onClick={() => {
                          const qty = parseInt(newQty) || 0;
                          addItemToOrder(order.id, newSku, qty);
                          setNewSku('');
                          setNewQty('');
                        }}
                      >
                        Hinzufügen
                      </s-button>
                    </s-stack>
                  </s-stack>
                )}
              </s-box>
            );
          })}

          <s-stack direction="inline" gap="base" justifyContent="end">
            <s-button onClick={() => setView('menu')}>Zurück</s-button>
            <s-button variant="primary" onClick={() => runExclusive('saveChanges', saveOrderChanges)} {...busyProps('saveChanges')}>
              Änderungen speichern
            </s-button>
          </s-stack>
        </s-section>
      </s-page>
    );
  }

  if (view === 'export') {
    return (
      <s-page heading="Export Inventar">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {loading && <s-spinner />}
          {!loading && exportData && (
            <>
              <s-text>Export bereit zum Download</s-text>
              <s-link href={exportData} download="inventar-export.csv">
                CSV herunterladen
              </s-link>
            </>
          )}
          <s-button onClick={() => setView('menu')}>Zurück</s-button>
        </s-section>
      </s-page>
    );
  }

  if (view === 'inventory') {
    const filteredData = inventoryData
      .filter((item) => {
        if (showNoLocation) {
          if (item.lagerstandort && item.lagerstandort !== '') return false;
        } else if (selectedLocation && item.lagerstandort !== selectedLocation) {
          return false;
        }
        if (hideZeroStock && item.stock === 0) return false;
        return true;
      })
      .sort((a, b) => a.sku.localeCompare(b.sku));

    return (
      <s-page heading="Lagerbestand">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {success && <s-banner tone="success">{success}</s-banner>}
          {printLink && (
            <s-banner tone="info">
              <s-stack direction="block" gap="small">
                <s-text>Der Browser hat das neue Fenster blockiert. Druckansicht hier öffnen:</s-text>
                <s-link href={printLink} target="_blank">
                  Druckansicht öffnen
                </s-link>
              </s-stack>
            </s-banner>
          )}
          {loading && <s-spinner />}

          {!loading && (
            <>
              <s-stack direction="inline" gap="base" justifyContent="end">
                {inventoryLoaded && !inventoryEditMode && (
                  <s-button
                    tone="critical"
                    onClick={() => {
                      setInventoryEditMode(true);
                      const initialEdits: {
                        [sku: string]: { newStock?: number; newLagerstandort?: string };
                      } = {};
                      inventoryData.forEach((item) => {
                        if (!item.sku) return;
                        initialEdits[item.sku] = { newStock: item.stock };
                      });
                      setInventoryEdits(initialEdits);
                      setInventoryFreeTextRows({});
                    }}
                  >
                    Bearbeiten
                  </s-button>
                )}
                {inventoryEditMode && (
                  <>
                    <s-button
                      onClick={() => {
                        setInventoryEditMode(false);
                        setInventoryEdits({});
                        setInventoryFreeTextRows({});
                      }}
                      disabled={(inventorySaving) || undefined}
                    >
                      Abbrechen
                    </s-button>
                    <s-button
                      variant="primary"
                      tone="critical"
                      onClick={handleSaveInventoryEdits}
                      disabled={(inventorySaving) || undefined}
                    >
                      {inventorySaving ? 'Speichere...' : 'Speichern'}
                    </s-button>
                  </>
                )}
                <s-button onClick={() => setView('menu')} disabled={(inventorySaving) || undefined}>
                  Zurück
                </s-button>
              </s-stack>

              {inventoryEditMode && locations.length > 0 && (
                <s-text color="subdued">Vorhandene Standorte: {locations.join(', ')}</s-text>
              )}

              <s-stack direction="inline" gap="base">
                {locations.map((loc) => (
                  <s-button
                    key={loc}
                    variant={selectedLocation === loc ? 'primary' : 'secondary'}
                    onClick={() => {
                      setSelectedLocation(selectedLocation === loc ? '' : loc);
                      setShowNoLocation(false);
                      if (!inventoryLoaded) {
                        loadInventory().then(() => setInventoryLoaded(true));
                      }
                    }}
                  >
                    {loc}
                  </s-button>
                ))}
                <s-button
                  variant={showNoLocation ? 'primary' : 'secondary'}
                  onClick={() => {
                    setShowNoLocation(!showNoLocation);
                    setSelectedLocation('');
                    if (!inventoryLoaded) {
                      loadInventory().then(() => setInventoryLoaded(true));
                    }
                  }}
                >
                  Ohne Standort
                </s-button>
                <s-button
                  variant={
                    !selectedLocation && !showNoLocation && inventoryLoaded
                      ? 'primary'
                      : 'secondary'
                  }
                  onClick={() => {
                    setSelectedLocation('');
                    setShowNoLocation(false);
                    if (!inventoryLoaded) {
                      loadInventory().then(() => setInventoryLoaded(true));
                    }
                  }}
                >
                  Alle anzeigen
                </s-button>
              </s-stack>

              {!inventoryLoaded && !loading && (
                <s-text color="subdued">
                  Bitte einen Standort auswählen oder "Alle anzeigen" klicken, um die Artikel zu
                  laden.
                </s-text>
              )}

              {inventoryLoaded && (
                <>
                  <s-switch
                    label="Nullbestände ausblenden"
                    checked={(hideZeroStock) || undefined}
                    ref={listen('change', (e: any) => setHideZeroStock(e.currentTarget.checked))}
                  />

                  <s-section padding="none">
                    <s-table>
                      <s-table-header-row>
                        <s-table-header listSlot="primary">SKU</s-table-header>
                        <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                        <s-table-header listSlot="labeled">Lagerstandort</s-table-header>
                        <s-table-header listSlot="labeled">Verfügbar</s-table-header>
                        <s-table-header listSlot="labeled">VE</s-table-header>
                        {inventoryEditMode && (
                          <s-table-header listSlot="labeled">Neuer Bestand</s-table-header>
                        )}
                        {inventoryEditMode && (
                          <s-table-header listSlot="labeled">Neuer Lagerstandort</s-table-header>
                        )}
                      </s-table-header-row>
                      <s-table-body>
                        {filteredData.map((item, idx) => {
                          const edit = inventoryEdits[item.sku];
                          const newStockValue =
                            edit?.newStock !== undefined
                              ? String(edit.newStock)
                              : String(item.stock);
                          const newLagerstandortValue =
                            edit?.newLagerstandort !== undefined
                              ? edit.newLagerstandort
                              : item.lagerstandort || '';
                          const isFreeText = inventoryFreeTextRows[item.sku] || false;
                          const updateLagerstandort = (val: string): void => {
                            setInventoryEdits((prev) => {
                              const next = { ...prev };
                              const existing = next[item.sku] || {};
                              if (val === (item.lagerstandort || '')) {
                                const { newLagerstandort, ...rest } = existing;
                                if (Object.keys(rest).length === 0) {
                                  delete next[item.sku];
                                } else {
                                  next[item.sku] = rest;
                                }
                              } else {
                                next[item.sku] = {
                                  ...existing,
                                  newLagerstandort: val,
                                };
                              }
                              return next;
                            });
                          };
                          return (
                            <s-table-row key={idx}>
                              <s-table-cell>{item.sku}</s-table-cell>
                              <s-table-cell>{item.title}</s-table-cell>
                              <s-table-cell>{item.lagerstandort}</s-table-cell>
                              <s-table-cell>{item.stock}</s-table-cell>
                              <s-table-cell>{item.ve}</s-table-cell>
                              {inventoryEditMode && item.sku === '' && (
                                <>
                                  <s-table-cell>–</s-table-cell>
                                  <s-table-cell>keine SKU – nicht bearbeitbar</s-table-cell>
                                </>
                              )}
                              {inventoryEditMode && item.sku !== '' && (
                                <s-table-cell>
                                  <s-number-field
                                    label="Neuer Bestand"
                                    labelAccessibilityVisibility="exclusive"
                                    value={newStockValue}
                                    onInput={(e: any) => {
                                      const raw = e.currentTarget.value;
                                      setInventoryEdits((prev) => {
                                        const next = { ...prev };
                                        const existing = next[item.sku] || {};
                                        if (raw === '' || raw === null) {
                                          const { newStock, ...rest } = existing;
                                          if (Object.keys(rest).length === 0) {
                                            delete next[item.sku];
                                          } else {
                                            next[item.sku] = rest;
                                          }
                                        } else {
                                          const parsed = parseInt(raw);
                                          if (!isNaN(parsed)) {
                                            next[item.sku] = { ...existing, newStock: parsed };
                                          }
                                        }
                                        return next;
                                      });
                                    }}
                                  />
                                </s-table-cell>
                              )}
                              {inventoryEditMode && item.sku !== '' && (
                                <s-table-cell>
                                  <s-stack direction="block" gap="small">
                                    {!isFreeText ? (
                                      <s-select
                                        label="Neuer Lagerstandort"
                                        labelAccessibilityVisibility="exclusive"
                                        value={newLagerstandortValue}
                                        ref={listen('change', (e: any) => {
                                          updateLagerstandort(e.currentTarget.value);
                                        })}
                                      >
                                        <s-option value="">— kein Standort —</s-option>
                                        {locations.map((loc) => (
                                          <s-option key={loc} value={loc}>
                                            {loc}
                                          </s-option>
                                        ))}
                                      </s-select>
                                    ) : (
                                      <s-text-field
                                        label="Neuer Lagerstandort"
                                        labelAccessibilityVisibility="exclusive"
                                        value={newLagerstandortValue}
                                        onInput={(e: any) => {
                                          updateLagerstandort(e.currentTarget.value);
                                        }}
                                      />
                                    )}
                                    <s-checkbox
                                      label="Freie Eingabe"
                                      checked={(isFreeText) || undefined}
                                      ref={listen('change', (e: any) => {
                                        // Wert sofort lesen: im verzögert laufenden State-Update wäre currentTarget bereits null.
                                        const checked = !!e.currentTarget?.checked;
                                        setInventoryFreeTextRows((prev) => ({
                                          ...prev,
                                          [item.sku]: checked,
                                        }));
                                      })}
                                    />
                                  </s-stack>
                                </s-table-cell>
                              )}
                            </s-table-row>
                          );
                        })}
                      </s-table-body>
                    </s-table>
                  </s-section>

                  <s-stack direction="inline" gap="base" justifyContent="end">
                    <s-button
                      onClick={() => {
                        const heading = selectedLocation
                          ? `Lagerbestand – ${selectedLocation}`
                          : 'Lagerbestand';
                        void runPrint(async () => buildInventoryPrintHtml(heading, [...filteredData]));
                      }}
                      disabled={(inventoryEditMode) || undefined}
                    >
                      Drucken
                    </s-button>
                  </s-stack>
                </>
              )}

              <s-stack direction="inline" gap="base" justifyContent="end">
                <s-button
                  onClick={() => {
                    setInventoryLoaded(false);
                    setView('menu');
                  }}
                  disabled={(inventorySaving) || undefined}
                >
                  Zurück
                </s-button>
              </s-stack>
            </>
          )}
        </s-section>
      </s-page>
    );
  }

  if (view === 'backups') {
    return <BackupView onBack={() => setView('menu')} />;
  }

  if (view === 'manual-price') {
    return <ManualPriceView onBack={() => setView('menu')} />;
  }

  if (view === 'price-update') {
    if (priceUpdateStep === 1) {
      return (
        <s-page heading="Einkaufspreise aktualisieren">
          <s-section>
            {error && <s-banner tone="critical">{error}</s-banner>}
            <s-text>
              Keramix-Preisliste (.xlsx) hochladen. Alle Blätter werden verarbeitet; die Spalte „KMX Code" entspricht der
              SKU, „Preis" dem Einkaufspreis und „Stk./Karton" bzw. „Stk/Box" dem VE-Tag. Der Verkaufspreis wird aus dem
              neuen Einkaufspreis berechnet (Faktor auf der nächsten Seite einstellbar).
            </s-text>
            <s-drop-zone
              label={priceUpdateFile ? priceUpdateFile.name : 'Excel-Datei auswählen (.xlsx)'}
              accept=".xlsx"
              ref={listen('change', (e: any) => {
                const files = e.currentTarget?.files;
                if (files && files.length > 0) setPriceUpdateFile(files[0]);
              })}
            />
            <s-stack direction="inline" gap="base">
              <s-button onClick={() => setView('menu')}>Zurück</s-button>
              <s-button
                variant="primary"
                disabled={(priceUpdateLoading || !priceUpdateFile) || undefined}
                onClick={handleAnalyzePrices}
              >
                {priceUpdateLoading ? 'Analysiere...' : 'Analysieren'}
              </s-button>
            </s-stack>
            {priceUpdateLoading && <s-spinner />}
          </s-section>
        </s-page>
      );
    }

    const factorValue = parseFactor(priceFactor);
    const rowsView = withSalePrice(priceUpdateResults, factorValue);
    const isVisible = (r: PriceRowView) =>
      r.priceChanged || r.saleChanged || r.veChanged || r.veConflict || r.status === 'error';
    const visibleResults = rowsView.filter(isVisible);
    const changedCount = rowsView.filter((r) => r.priceChanged || r.saleChanged || r.veChanged).length;
    const unchangedCount = rowsView.length - visibleResults.length;
    const errorCount = rowsView.filter((r) => r.status === 'error' || r.veConflict).length;
    const selectedCount = rowsView.filter(
      (r) =>
        (r.priceChanged && isOn(r, 'p')) || (r.saleChanged && isOn(r, 's')) || (r.veChanged && isOn(r, 'v')),
    ).length;
    const fmt = (v: string) => (v === '' ? '–' : v);
    const LIST_LIMIT = 1000;

    return (
      <s-page heading="Einkaufspreise aktualisieren">
        <s-section heading="Verkaufspreis-Formel">
          <s-stack direction="block" gap="base">
            <s-text-field
              label="Faktor: Verkaufspreis = neuer Einkaufspreis × Faktor"
              value={priceFactor}
              onInput={(e: any) => setPriceFactor(e.currentTarget.value)}
            />
            {factorValue === null ? (
              <s-banner tone="critical">Ungültiger Faktor – bitte eine Zahl größer 0 eingeben (z. B. 2,232).</s-banner>
            ) : (
              <s-text>
                Beispiel: Einkaufspreis 10,00 € → Verkaufspreis{' '}
                {computeSalePrice(10, factorValue).toFixed(2).replace('.', ',')} €
              </s-text>
            )}
          </s-stack>
        </s-section>

        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {priceUpdateSuccess && <s-banner tone="success">{priceUpdateSuccess}</s-banner>}
          {priceListInfo && <s-banner tone="info">{priceListInfo}</s-banner>}

          <s-stack direction="block" gap="base">
            <s-heading>a) Abgleich: Einkaufspreis, Verkaufspreis und VE</s-heading>
            <s-text>
              {unchangedCount} unverändert, {changedCount} abweichend, {errorCount} Fehler/Konflikt – {selectedCount}{' '}
              Artikel zur Änderung ausgewählt
            </s-text>
            <s-select
              label="Vorauswahl für Produkte …"
              details="Gilt für Vorauswahl und Schnellauswahl bei EK, VK und VE. Beim Wechsel wird die Auswahl zurückgesetzt."
              value={priceAufMode}
              ref={listen('change', (e: any) => {
                setPriceAufMode(e.currentTarget.value);
                setPriceChoice({});
              })}
            >
              <s-option value="all">alle (mit und ohne Tag „Aufschlag")</s-option>
              <s-option value="without">nur ohne Tag „Aufschlag"</s-option>
              <s-option value="with">nur mit Tag „Aufschlag"</s-option>
            </s-select>
            <s-stack direction="inline" gap="small">
              <s-button onClick={() => setPriceChoice({})}>Vorauswahl (nur höhere Werte)</s-button>
              <s-button onClick={() => setAllChoices(true, true, true)}>Alles ändern</s-button>
              <s-button onClick={() => setAllChoices(true, false, false)}>Nur Einkaufspreise</s-button>
              <s-button onClick={() => setAllChoices(false, true, false)}>Nur Verkaufspreise</s-button>
              <s-button onClick={() => setAllChoices(false, false, true)}>Nur VE</s-button>
              <s-button onClick={() => setAllChoices(false, false, false)}>Nichts ändern</s-button>
            </s-stack>

            {visibleResults.length === 0 ? (
              <s-text>Alle Artikel aus der Preisliste stimmen bei Preisen und VE mit dem Shop überein.</s-text>
            ) : (
              <s-section padding="none">
                <s-table>
                  <s-table-header-row>
                    <s-table-header listSlot="primary">SKU</s-table-header>
                    <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                    <s-table-header listSlot="labeled">EK aktuell</s-table-header>
                    <s-table-header listSlot="labeled">EK neu</s-table-header>
                    <s-table-header listSlot="labeled">VK aktuell</s-table-header>
                    <s-table-header listSlot="labeled">VK neu</s-table-header>
                    <s-table-header listSlot="labeled">VE aktuell</s-table-header>
                    <s-table-header listSlot="labeled">VE neu</s-table-header>
                    <s-table-header listSlot="labeled">Ändern EK</s-table-header>
                    <s-table-header listSlot="labeled">Ändern VK</s-table-header>
                    <s-table-header listSlot="labeled">Ändern VE</s-table-header>
                  </s-table-header-row>
                  <s-table-body>
                    {visibleResults.map((r) => {
                      const anyChange = r.priceChanged || r.saleChanged || r.veChanged;
                      return (
                        <s-table-row key={r.sku}>
                          <s-table-cell>
                            <s-badge tone={r.status === 'error' || r.veConflict ? 'critical' : anyChange ? 'caution' : 'success'}>
                              {r.sku}
                            </s-badge>
                          </s-table-cell>
                          <s-table-cell>
                            {r.title}
                            {r.errorMessage ? ` – ${r.errorMessage}` : ''}
                            {r.hasAufschlag && (
                              <>
                                {' '}
                                <s-badge tone="warning">Aufschlag</s-badge>
                              </>
                            )}
                          </s-table-cell>
                          <s-table-cell>{fmt(r.currentPrice)}</s-table-cell>
                          <s-table-cell>
                            {r.priceChanged ? (
                              <span style={direction(r.currentPrice, r.newPrice) === 'up' ? CHANGE_UP : CHANGE_DOWN}>{r.newPrice}</span>
                            ) : (
                              `${r.newPrice} ✓`
                            )}
                          </s-table-cell>
                          <s-table-cell>{fmt(r.currentSale)}</s-table-cell>
                          <s-table-cell>
                            {r.newSale === '' ? (
                              '–'
                            ) : r.saleChanged ? (
                              <span style={direction(r.currentSale, r.newSale) === 'up' ? CHANGE_UP : CHANGE_DOWN}>{r.newSale}</span>
                            ) : (
                              `${r.newSale} ✓`
                            )}
                          </s-table-cell>
                          <s-table-cell>{r.currentVeTags.length > 1 ? r.currentVeTags.join(', ') : fmt(r.currentVe)}</s-table-cell>
                          <s-table-cell>{r.newVe ? (r.veChanged ? r.newVe : `${r.newVe} ✓`) : '–'}</s-table-cell>
                          <s-table-cell>
                            {r.priceChanged && (
                              <s-checkbox
                                label="EK"
                                accessibilityLabel={`${r.sku}: Einkaufspreis ändern`}
                                checked={isOn(r, 'p') || undefined}
                                ref={listen('change', (e: any) => setChoice(r.sku, 'p', !!e.currentTarget.checked))}
                              />
                            )}
                          </s-table-cell>
                          <s-table-cell>
                            {r.saleChanged && (
                              <s-checkbox
                                label="VK"
                                accessibilityLabel={`${r.sku}: Verkaufspreis ändern`}
                                checked={isOn(r, 's') || undefined}
                                ref={listen('change', (e: any) => setChoice(r.sku, 's', !!e.currentTarget.checked))}
                              />
                            )}
                          </s-table-cell>
                          <s-table-cell>
                            {r.veChanged && (
                              <s-checkbox
                                label="VE"
                                accessibilityLabel={`${r.sku}: VE-Tag ändern`}
                                checked={isOn(r, 'v') || undefined}
                                ref={listen('change', (e: any) => setChoice(r.sku, 'v', !!e.currentTarget.checked))}
                              />
                            )}
                          </s-table-cell>
                        </s-table-row>
                      );
                    })}
                  </s-table-body>
                </s-table>
              </s-section>
            )}

            <s-stack direction="inline" gap="base">
              <s-button onClick={() => setPriceUpdateStep(1)}>Zurück zur Analyse</s-button>
              <s-button onClick={() => setView('menu')}>Zurück zum Menü</s-button>
              <s-button
                variant="primary"
                disabled={(priceUpdateLoading || selectedCount === 0 || factorValue === null) || undefined}
                onClick={handleUpdatePrices}
              >
                {priceUpdateLoading ? 'Aktualisiere...' : `Ausgewählte aktualisieren (${selectedCount})`}
              </s-button>
            </s-stack>
            {priceUpdateLoading && <s-spinner />}
          </s-stack>
        </s-section>

        <s-section heading={`b) Im Shop, aber nicht in der Preisliste (${priceOnlyShop.length})`}>
          {priceOnlyShop.length === 0 ? (
            <s-text>Alle Shop-Produkte stehen in der Preisliste.</s-text>
          ) : (
            <s-stack direction="block" gap="base">
              <s-button onClick={() => setShowOnlyShop(!showOnlyShop)}>
                {showOnlyShop ? 'Liste ausblenden' : 'Liste anzeigen'}
              </s-button>
              {showOnlyShop && (
                <s-table>
                  <s-table-header-row>
                    <s-table-header listSlot="primary">SKU</s-table-header>
                    <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                    <s-table-header listSlot="labeled">Status</s-table-header>
                  </s-table-header-row>
                  <s-table-body>
                    {priceOnlyShop.slice(0, LIST_LIMIT).map((r, idx) => (
                      <s-table-row key={`${r.sku}-${idx}`}>
                        <s-table-cell>{r.sku || '(ohne SKU)'}</s-table-cell>
                        <s-table-cell>{r.title}</s-table-cell>
                        <s-table-cell>{r.productStatus}</s-table-cell>
                      </s-table-row>
                    ))}
                  </s-table-body>
                </s-table>
              )}
              {showOnlyShop && priceOnlyShop.length > LIST_LIMIT && (
                <s-text>Angezeigt werden die ersten {LIST_LIMIT} von {priceOnlyShop.length} Einträgen.</s-text>
              )}
            </s-stack>
          )}
        </s-section>

        <s-section heading={`c) In der Preisliste, aber nicht im Shop (${priceOnlyList.length})`}>
          {priceOnlyList.length === 0 ? (
            <s-text>Alle Artikel der Preisliste sind im Shop vorhanden.</s-text>
          ) : (
            <s-stack direction="block" gap="base">
              <s-button onClick={() => setShowOnlyList(!showOnlyList)}>
                {showOnlyList ? 'Liste ausblenden' : 'Liste anzeigen'}
              </s-button>
              {showOnlyList && (
                <s-table>
                  <s-table-header-row>
                    <s-table-header listSlot="primary">SKU</s-table-header>
                    <s-table-header listSlot="labeled">Produktname</s-table-header>
                    <s-table-header listSlot="labeled">Blatt</s-table-header>
                    <s-table-header listSlot="labeled">Preis</s-table-header>
                    <s-table-header listSlot="labeled">VE</s-table-header>
                  </s-table-header-row>
                  <s-table-body>
                    {priceOnlyList.slice(0, LIST_LIMIT).map((r) => (
                      <s-table-row key={r.sku}>
                        <s-table-cell>{r.sku}</s-table-cell>
                        <s-table-cell>{r.name}</s-table-cell>
                        <s-table-cell>{r.sheet.trim()}</s-table-cell>
                        <s-table-cell>{r.price.toFixed(2)}</s-table-cell>
                        <s-table-cell>{r.ve ?? '–'}</s-table-cell>
                      </s-table-row>
                    ))}
                  </s-table-body>
                </s-table>
              )}
              {showOnlyList && priceOnlyList.length > LIST_LIMIT && (
                <s-text>Angezeigt werden die ersten {LIST_LIMIT} von {priceOnlyList.length} Einträgen.</s-text>
              )}
            </s-stack>
          )}
        </s-section>
      </s-page>
    );
  }

  if (view === 'search-orders') {
    const totalQuantity = searchResults.reduce((sum, r) => sum + r.quantity, 0);

    return (
      <s-page heading="Bestellte Artikel abfragen">
        <s-section>
          <s-text-field
            label="SKU suchen"
            value={searchQuery}
            onInput={(e: any) => {
              setSearchQuery(e.currentTarget.value);
              setSearchExecuted(false);
            }}
          />
          <s-button onClick={handleSearchOrders}>Suchen</s-button>

          {searchResults.length > 0 && (
            <>
              <s-section padding="none">
                <s-table>
                  <s-table-header-row>
                    <s-table-header listSlot="primary">Bestelldatum</s-table-header>
                    <s-table-header listSlot="labeled">SKU</s-table-header>
                    <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                    <s-table-header listSlot="labeled">Menge</s-table-header>
                  </s-table-header-row>
                  <s-table-body>
                    {searchResults.map((result, idx) => (
                      <s-table-row key={idx}>
                        <s-table-cell>{formatDateDisplay(result.orderDate)}</s-table-cell>
                        <s-table-cell>{result.sku}</s-table-cell>
                        <s-table-cell>{result.title}</s-table-cell>
                        <s-table-cell>{result.quantity}</s-table-cell>
                      </s-table-row>
                    ))}
                  </s-table-body>
                </s-table>
              </s-section>
              <s-text>
                Total bestellt: {totalQuantity} Stück in {searchOrderCount} Bestellungen
              </s-text>
            </>
          )}

          {searchExecuted && searchResults.length === 0 && (
            <s-text>SKU "{searchQuery}" ist in keiner offenen Bestellung vorhanden.</s-text>
          )}

          <s-button onClick={() => setView('menu')}>Zurück</s-button>
        </s-section>
      </s-page>
    );
  }

  if (view === 'migration') {
    return (
      <s-page heading="Datenmigration in die neue Datenbank">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {success && <s-banner tone="success">{success}</s-banner>}

          <s-text color="subdued">
            Einmalig: übernimmt Bestellungen und Historie aus den bisherigen Metafeldern in die
            Datenbank auf dem Novatrend-Server. Die Metafelder werden nicht verändert oder gelöscht.
            Der Probelauf ist jederzeit möglich und schreibt nichts. Der echte Import läuft nur einmal und
            nur in eine leere Datenbank (danach ist er gesperrt).
          </s-text>

          <s-stack direction="block" gap="base">
            <s-button disabled={(migrationBusy) || undefined} onClick={checkBackend}>
              0. Verbindung zum Backend prüfen
            </s-button>
            <s-button disabled={(migrationBusy) || undefined} onClick={readMigrationSources}>
              1. Metafelder lesen und Roh-Backup erstellen
            </s-button>
            <s-button
              disabled={(migrationBusy || !migrationSources) || undefined}
              onClick={() => runMigration(true)}
            >
              2. Probelauf (schreibt nichts)
            </s-button>
            <s-button
              variant="primary"
              disabled={(migrationBusy || !migrationSources) || undefined}
              onClick={() => runMigration(false)}
            >
              3. Import ausführen
            </s-button>
          </s-stack>

          {migrationBusy && <s-spinner />}

          {migrationBackupUrl && (
            <s-banner tone="info">
              <s-stack direction="block" gap="small">
                <s-text>
                  Roh-Backup der Metafelder bereit. Datei herunterladen und aufbewahren
                  (Rechtsklick → Link speichern unter, falls der Klick nichts tut).
                </s-text>
                <s-link href={migrationBackupUrl} download="metafeld-backup.json">
                  Roh-Backup herunterladen
                </s-link>
              </s-stack>
            </s-banner>
          )}

          {migrationLog.length > 0 && (
            <s-stack direction="block" gap="none">
              {migrationLog.map((line, idx) => (
                <s-text key={idx}>{line}</s-text>
              ))}
            </s-stack>
          )}

          {migrationReport && (
            <s-banner tone={migrationReport.dryRun ? 'info' : 'success'}>
              <s-stack direction="block" gap="small">
                <s-text type="strong">
                  {migrationReport.dryRun ? 'Probelauf (nichts geschrieben)' : 'Import ausgeführt'}
                </s-text>
                <s-text>
                  Bestellungen: {migrationReport.orders.received} gelesen,{' '}
                  {migrationReport.orders.toInsert} {migrationReport.dryRun ? 'würden übernommen' : 'übernommen'},{' '}
                  {migrationReport.orders.skippedExisting} bereits vorhanden,{' '}
                  {migrationReport.orders.invalid} ungültig
                </s-text>
                <s-text>
                  Historie: {migrationReport.history.received} gelesen,{' '}
                  {migrationReport.history.toInsert} {migrationReport.dryRun ? 'würden übernommen' : 'übernommen'},{' '}
                  {migrationReport.history.duplicatesSkipped} Duplikate übersprungen,{' '}
                  {migrationReport.history.invalid} ungültig
                </s-text>
                <s-text>
                  In der Datenbank insgesamt: {migrationReport.totals.orders} Bestellungen,{' '}
                  {migrationReport.totals.history} Historieneinträge
                </s-text>
                {migrationReport.invalidExamples.length > 0 && (
                  <s-text color="subdued">
                    Ungültige Datensätze (Beispiele): {migrationReport.invalidExamples.join(' | ')}
                  </s-text>
                )}
              </s-stack>
            </s-banner>
          )}

          <s-button onClick={() => setView('menu')}>Zurück</s-button>
        </s-section>
      </s-page>
    );
  }

  if (view === 'history') {
    const currentYear = new Date().getFullYear();
    const years = historyYears.length > 0 ? historyYears : [String(currentYear)];

    const filteredHistory = historyData.filter((h) => h.datum.split('.')[2] === historyYear);

    const csvLines = [
      'Datum;Uhrzeit;Ereignis;Bestelldatum;SKU;Bezeichnung;Bestellt;Verbucht;Details',
      ...filteredHistory.map(
        (h) =>
          `${h.datum};${h.uhrzeit};${h.ereignis};${h.bestelldatum};${h.sku};${h.bezeichnung};${h.mengeBestellt};${h.mengeVerbucht};${h.details}`,
      ),
    ];
    const csvContent = csvLines.join('\n');
    const csvDataUrl = `data:text/csv;charset=utf-8,${encodeURIComponent(csvContent)}`;

    return (
      <s-page heading="Bestellhistorie">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          {historyLoading && <s-spinner />}

          {!historyLoading && (
            <>
              <s-stack direction="inline" gap="base" justifyContent="end">
                <s-button onClick={() => setView('menu')}>Zurück</s-button>
              </s-stack>
              <s-stack direction="inline" gap="base">
                {years.map((year) => (
                  <s-button
                    key={year}
                    variant={historyYear === year ? 'primary' : 'secondary'}
                    onClick={async () => {
                      setHistoryYear(year);
                      setHistoryLoading(true);
                      try {
                        setHistoryData(await backend.getHistory(year));
                      } catch (yearError: any) {
                        setError(
                          `Historie konnte nicht geladen werden: ${yearError?.message || 'Unbekannter Fehler'}`,
                        );
                      }
                      setHistoryLoading(false);
                    }}
                  >
                    {year}
                  </s-button>
                ))}
              </s-stack>

              {filteredHistory.length > 0 && (
                <>
                  <s-section padding="none">
                    <s-table>
                      <s-table-header-row>
                        <s-table-header listSlot="primary">Datum</s-table-header>
                        <s-table-header listSlot="labeled">Uhrzeit</s-table-header>
                        <s-table-header listSlot="labeled">Ereignis</s-table-header>
                        <s-table-header listSlot="labeled">Bestelldatum</s-table-header>
                        <s-table-header listSlot="labeled">SKU</s-table-header>
                        <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                        <s-table-header listSlot="labeled">Bestellt</s-table-header>
                        <s-table-header listSlot="labeled">Verbucht</s-table-header>
                        <s-table-header listSlot="labeled">Details</s-table-header>
                      </s-table-header-row>
                      <s-table-body>
                        {filteredHistory.map((entry, idx) => (
                          <s-table-row key={idx}>
                            <s-table-cell>{entry.datum}</s-table-cell>
                            <s-table-cell>{entry.uhrzeit}</s-table-cell>
                            <s-table-cell>{entry.ereignis}</s-table-cell>
                            <s-table-cell>{entry.bestelldatum}</s-table-cell>
                            <s-table-cell>{entry.sku}</s-table-cell>
                            <s-table-cell>{entry.bezeichnung}</s-table-cell>
                            <s-table-cell>{entry.mengeBestellt}</s-table-cell>
                            <s-table-cell>{entry.mengeVerbucht}</s-table-cell>
                            <s-table-cell>{entry.details}</s-table-cell>
                          </s-table-row>
                        ))}
                      </s-table-body>
                    </s-table>
                  </s-section>
                  <s-link href={csvDataUrl} download={`bestellhistorie-${historyYear}.csv`}>
                    CSV herunterladen
                  </s-link>
                </>
              )}

              {filteredHistory.length === 0 && (
                <s-text>Keine Einträge für {historyYear} vorhanden</s-text>
              )}

              <s-button onClick={() => setView('menu')}>Zurück</s-button>
            </>
          )}
        </s-section>
      </s-page>
    );
  }

  return null;
}


export function ErrorBoundary() {
  return boundary.error(useRouteError());
}
export const headers = (headersArgs: any) => boundary.headers(headersArgs);
