import { useEffect, useState } from 'react';
import { shopifyQuery } from '../lib/shopify-client';
import { saveBackupEntry, numId, shorten } from '../lib/price-backup';

// Ansicht „Preisanpassung manuell": alle aktiven Produkte nach Typ gruppiert, mit Gewinnberechnung,
// editierbarem Verkaufspreis (VK), Tag-Checkboxen und Sammelanpassung des VK.

// Polaris-Webkomponenten unter React 18: das native "change"-Ereignis per ref anhängen (wie in app._index.tsx).
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;
};

// Reihenfolge der Typen; alle weiteren Typen folgen alphabetisch.
const TYPE_ORDER = ['Keramik', 'Glasuren', 'Zubehör', 'Brennofen'];
// Produkt-Tags, die per Checkbox gesetzt/entfernt werden (Vergleich ohne Groß-/Kleinschreibung).
const TAGS = [
  { key: 'vol', label: 'voluminös' },
  { key: 'zer', label: 'zerbrechlich' },
  { key: 'auf', label: 'Aufschlag' },
] as const;
type TagKey = (typeof TAGS)[number]['key'];

const PAGE_SIZES = ['100', '200', '500']; // Zeilen pro Seite (Webkomponenten sind bei sehr vielen Zeilen träge)
const SETTINGS_KEY = 'bestellverwaltung.manualPrice.settings';

interface Row {
  variantId: string;
  productId: string;
  sku: string;
  title: string;
  type: string;
  ek: number | null;
  vk: number;
  r1: boolean;
  r2: boolean;
}

const parseNum = (s: string): number | null => {
  const n = Number(String(s).trim().replace(/\s/g, '').replace(',', '.'));
  return String(s).trim() !== '' && Number.isFinite(n) ? n : null;
};
const round2 = (n: number): number => Math.round(n * 100 + 1e-9) / 100;
const fmt = (n: number | null): string => (n === null ? '–' : n.toFixed(2).replace('.', ','));
const typeRank = (t: string): number => {
  const i = TYPE_ORDER.findIndex((x) => x.toLowerCase() === t.toLowerCase());
  return i < 0 ? TYPE_ORDER.length : i;
};
const tagMatch = (tag: string, label: string): boolean => tag.trim().toLowerCase() === label.toLowerCase();

const queryRetry = 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;
};

const green = { color: '#00a63e', fontWeight: 700 } as const;
const red = { color: '#ff1a1a', fontWeight: 700 } as const;

export default function ManualPriceView({ onBack }: { onBack: () => void }) {
  const [rows, setRows] = useState<Row[]>([]);
  const [tagsByProduct, setTagsByProduct] = useState<Record<string, string[]>>({});
  const [loading, setLoading] = useState<boolean>(true);
  const [saving, setSaving] = useState<boolean>(false);
  const [error, setError] = useState<string>('');
  const [success, setSuccess] = useState<string>('');

  // Einstellungen (editierbare Standardvorgaben)
  const [factorDV, setFactorDV] = useState<string>('0,8645');
  const [vatDE, setVatDE] = useState<string>('19');
  const [vatFR, setVatFR] = useState<string>('20');
  const [rab1, setRab1] = useState<string>('20');
  const [rab2, setRab2] = useState<string>('15');
  // Gewinngrenzen (Wert <= Grenze gilt als „unterschritten", Anzeige in Rot)
  const [thDvPct, setThDvPct] = useState<string>('35');
  const [thDvEur, setThDvEur] = useState<string>('1,50');
  const [thResPct, setThResPct] = useState<string>('30');
  const [thResEur, setThResEur] = useState<string>('1');

  const [edits, setEdits] = useState<Record<string, string>>({}); // variantId -> VK-Eingabe
  const [tagEdits, setTagEdits] = useState<Record<string, Partial<Record<TagKey, boolean>>>>({}); // productId -> gewünschter Zustand
  const [typeFilter, setTypeFilter] = useState<string>('');
  const [search, setSearch] = useState<string>('');
  const [tagFilter, setTagFilter] = useState<string>('all'); // all | vol | zer | auf
  const [lowFilter, setLowFilter] = useState<string>('all'); // all | dv | res | any
  const [pageSize, setPageSize] = useState<string>('200');
  const [page, setPage] = useState<number>(0);
  const [reviewOpen, setReviewOpen] = useState<boolean>(false);
  // Sammelanpassung: Typ, Tags und Gewinngrenze werden kombiniert (UND)
  const [bulkType, setBulkType] = useState<string>('all');
  const [bulkTags, setBulkTags] = useState<TagKey[]>([]);
  const [bulkLow, setBulkLow] = useState<string>('all');
  const [amount, setAmount] = useState<string>('');
  const [unit, setUnit] = useState<string>('eur');

  // gespeicherte Einstellungen laden/speichern (nur Komfort; Fehler werden ignoriert)
  useEffect(() => {
    try {
      const raw = window.localStorage.getItem(SETTINGS_KEY);
      if (!raw) return;
      const s = JSON.parse(raw);
      if (s.factorDV) setFactorDV(String(s.factorDV));
      if (s.vatDE) setVatDE(String(s.vatDE));
      if (s.vatFR) setVatFR(String(s.vatFR));
      if (s.rab1) setRab1(String(s.rab1));
      if (s.rab2) setRab2(String(s.rab2));
      if (s.thDvPct) setThDvPct(String(s.thDvPct));
      if (s.thDvEur) setThDvEur(String(s.thDvEur));
      if (s.thResPct) setThResPct(String(s.thResPct));
      if (s.thResEur) setThResEur(String(s.thResEur));
    } catch {
      /* ignorieren */
    }
  }, []);
  useEffect(() => {
    try {
      window.localStorage.setItem(SETTINGS_KEY, JSON.stringify({ factorDV, vatDE, vatFR, rab1, rab2, thDvPct, thDvEur, thResPct, thResEur }),
      );
    } catch {
      /* ignorieren */
    }
  }, [factorDV, vatDE, vatFR, rab1, rab2, thDvPct, thDvEur, thResPct, thResEur]);

  // bei geänderten Filtern wieder auf Seite 1
  useEffect(() => {
    setPage(0);
  }, [typeFilter, tagFilter, lowFilter, search, pageSize]);

  const load = async (): Promise<void> => {
    setLoading(true);
    setError('');
    try {
      // Produkt-IDs der Kollektionen Reseller1 / Reseller2
      const collectionIds = async (handle: string): Promise<Set<string>> => {
        const ids = new Set<string>();
        let after: string | null = null;
        for (;;) {
          const { data, errors } = await queryRetry(
            `query ResellerProducts($handle: String!, $after: String) {
              collectionByIdentifier(identifier: {handle: $handle}) {
                products(first: 250, after: $after) {
                  nodes { id }
                  pageInfo { hasNextPage endCursor }
                }
              }
            }`,
            { handle, after },
          );
          if (errors.length) throw new Error(errors.map((e: any) => e.message).join(', '));
          const prods = data?.collectionByIdentifier?.products;
          for (const n of prods?.nodes || []) ids.add(n.id);
          if (!prods?.pageInfo?.hasNextPage) break;
          after = prods.pageInfo.endCursor;
        }
        return ids;
      };
      const [r1Ids, r2Ids] = [await collectionIds('reseller1'), await collectionIds('reseller2')];

      const out: Row[] = [];
      const tagMap: Record<string, string[]> = {};
      let cursor: string | null = null;
      for (;;) {
        const { data, errors } = await queryRetry(
          `query Variants($first: Int!, $after: String) {
            productVariants(first: $first, after: $after) {
              edges {
                node {
                  id
                  sku
                  displayName
                  price
                  einkaufspreis: metafield(namespace: "custom", key: "einkaufspreis") { value }
                  product { id status productType tags }
                }
              }
              pageInfo { hasNextPage endCursor }
            }
          }`,
          { first: 100, after: cursor },
        );
        if (errors.length) throw new Error(errors.map((e: any) => e.message).join(', '));
        for (const e of data?.productVariants?.edges || []) {
          const n = e.node;
          if (n.product?.status !== 'ACTIVE') continue; // nur aktive Produkte
          let ek: number | null = null;
          const raw = n.einkaufspreis?.value;
          if (raw) {
            try {
              ek = raw.trim().startsWith('{') ? parseNum(JSON.parse(raw).amount) : parseNum(raw);
            } catch {
              ek = null;
            }
          }
          tagMap[n.product.id] = n.product.tags || [];
          out.push({
            variantId: n.id,
            productId: n.product.id,
            sku: n.sku || '',
            title: n.displayName || '',
            type: n.product.productType || '(ohne Typ)',
            ek,
            vk: Number(n.price) || 0,
            r1: r1Ids.has(n.product.id),
            r2: r2Ids.has(n.product.id),
          });
        }
        if (!data?.productVariants?.pageInfo?.hasNextPage) break;
        cursor = data.productVariants.pageInfo.endCursor;
      }
      out.sort(
        (a, b) =>
          typeRank(a.type) - typeRank(b.type) ||
          a.type.localeCompare(b.type) ||
          a.sku.localeCompare(b.sku, 'de', { numeric: true, sensitivity: 'base' }),
      );
      setRows(out);
      setTagsByProduct(tagMap);
      setEdits({});
      setTagEdits({});
      const firstType = out[0]?.type || '';
      setTypeFilter((prev) => prev || firstType);
    } catch (e: any) {
      setError(e?.message || 'Produkte konnten nicht geladen werden.');
    }
    setLoading(false);
  };

  useEffect(() => {
    void load();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // ---------- Berechnung ----------
  const fDV = parseNum(factorDV);
  const pDE = parseNum(vatDE);
  const pFR = parseNum(vatFR);
  const d1 = parseNum(rab1);
  const d2 = parseNum(rab2);
  const tDvP = parseNum(thDvPct);
  const tDvE = parseNum(thDvEur);
  const tResP = parseNum(thResPct);
  const tResE = parseNum(thResEur);
  const settingsOk =
    fDV !== null && fDV > 0 && pDE !== null && pFR !== null && d1 !== null && d2 !== null &&
    tDvP !== null && tDvE !== null && tResP !== null && tResE !== null;

  const vkOf = (r: Row): number => {
    const e = edits[r.variantId];
    const n = e === undefined ? null : parseNum(e);
    return n === null ? r.vk : n;
  };
  const hasTag = (r: Row, key: TagKey): boolean => {
    const wanted = tagEdits[r.productId]?.[key];
    if (wanted !== undefined) return wanted;
    const label = TAGS.find((t) => t.key === key)!.label;
    return (tagsByProduct[r.productId] || []).some((t) => tagMatch(t, label));
  };

  // Gewinn = VK − MwSt − EK; Prozent = Gewinn / Netto-VK
  const profit = (vkGross: number, vatPct: number, ek: number | null) => {
    const net = vkGross / (1 + vatPct / 100);
    if (ek === null) return { eur: null as number | null, pct: null as number | null };
    const eur = net - ek;
    return { eur, pct: net > 0 ? (eur / net) * 100 : null };
  };

  const calc = (r: Row) => {
    const vk = vkOf(r);
    const vkDV = fDV !== null ? vk * fDV : null;
    const pDv = vkDV !== null && pDE !== null ? profit(vkDV, pDE, r.ek) : { eur: null as number | null, pct: null as number | null };
    const disc = r.r1 ? d1 : r.r2 ? d2 : null;
    const vkRes = disc !== null ? vk * (1 - disc / 100) : null;
    const pRes = vkRes !== null && pFR !== null ? profit(vkRes, pFR, r.ek) : { eur: null as number | null, pct: null as number | null };
    // „unterschritten": Prozentwert <= Grenze ODER Eurobetrag <= Grenze (auf 2 Nachkommastellen gerundet verglichen)
    const isLow = (p: { eur: number | null; pct: number | null }, tp: number | null, te: number | null): boolean =>
      p.eur !== null &&
      ((p.pct !== null && tp !== null && round2(p.pct) <= tp) || (te !== null && round2(p.eur) <= te));
    return {
      vk,
      vkDV,
      pDv,
      vkRes,
      pRes,
      lowDV: isLow(pDv, tDvP, tDvE),
      lowRes: vkRes !== null && isLow(pRes, tResP, tResE),
    };
  };
  const matchesLow = (c: ReturnType<typeof calc>, mode: string): boolean =>
    mode === 'dv' ? c.lowDV : mode === 'res' ? c.lowRes : mode === 'any' ? c.lowDV || c.lowRes : true;

  const priceChanged = (r: Row): boolean => Math.abs(vkOf(r) - r.vk) >= 0.005;
  const tagChanged = (productId: string): boolean => {
    const e = tagEdits[productId];
    if (!e) return false;
    const current = tagsByProduct[productId] || [];
    return TAGS.some((t) => e[t.key] !== undefined && e[t.key] !== current.some((c) => tagMatch(c, t.label)));
  };
  const changedPriceRows = rows.filter(priceChanged);
  const changedTagProducts = Object.keys(tagEdits).filter(tagChanged);

  const types = Array.from(new Set(rows.map((r) => r.type)));

  // ---------- Sammelanpassung ----------
  const bulkMatch = (r: Row): boolean =>
    (bulkType === 'all' || r.type === bulkType) &&
    bulkTags.every((k) => hasTag(r, k)) &&
    matchesLow(calc(r), bulkLow);

  const applyBulk = (sign: 1 | -1): void => {
    const a = parseNum(amount);
    if (a === null || a <= 0) {
      setError('Bitte einen Betrag oder Prozentsatz größer 0 eingeben.');
      return;
    }
    setError('');
    setSuccess('');
    const next = { ...edits };
    let n = 0;
    for (const r of rows) {
      if (!bulkMatch(r)) continue;
      const base = vkOf(r);
      const v = unit === 'pct' ? base * (1 + (sign * a) / 100) : base + sign * a;
      next[r.variantId] = Math.max(0, round2(v)).toFixed(2).replace('.', ',');
      n++;
    }
    setEdits(next);
    setSuccess(`${n} Verkaufspreise angepasst (noch nicht gespeichert).`);
  };

  // ---------- Speichern ----------
  const save = async (): Promise<void> => {
    setSaving(true);
    setError('');
    setSuccess('');
    // Backup der alten Werte VOR jeder Änderung; schlägt es fehl, wird nichts geändert.
    try {
      const tagBackup = changedTagProducts.map((productId) => {
        const e = tagEdits[productId]!;
        const current = tagsByProduct[productId] || [];
        const a: string[] = [];
        const r: string[] = [];
        for (const t of TAGS) {
          const want = e[t.key];
          if (want === undefined) continue;
          const existing = current.filter((c) => tagMatch(c, t.label));
          if (want && existing.length === 0) a.push(t.label);
          if (!want && existing.length > 0) r.push(...existing);
        }
        const skus = rows.filter((x) => x.productId === productId).map((x) => x.sku).join(', ');
        return { p: numId(productId), s: skus, a, r };
      });
      await saveBackupEntry({
        id: new Date().toISOString(),
        src: 'manual',
        items: changedPriceRows.map((r) => ({
          v: numId(r.variantId),
          p: numId(r.productId),
          s: r.sku,
          t: shorten(r.title),
          vk: [r.vk.toFixed(2), vkOf(r).toFixed(2)] as [string, string],
        })),
        tags: tagBackup.filter((t) => t.a.length + t.r.length > 0),
      });
    } catch (e: any) {
      setError(`Backup fehlgeschlagen – es wurde nichts geändert: ${e?.message || 'Fehler'}`);
      setSaving(false);
      return;
    }

    const problems: string[] = [];
    const okVariants = new Set<string>();
    const okTagProducts = new Set<string>();
    const errText = (errors: any[], ue?: any[]): string =>
      [...(errors || []).map((e: any) => e.message), ...(ue || []).map((e: any) => e.message)].join(', ');

    // Verkaufspreise je Produkt
    const byProduct = new Map<string, Row[]>();
    for (const r of changedPriceRows) byProduct.set(r.productId, [...(byProduct.get(r.productId) || []), r]);
    for (const [productId, list] of byProduct) {
      const res = await queryRetry(
        `mutation VariantPrices($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
          productVariantsBulkUpdate(productId: $productId, variants: $variants) {
            productVariants { id price }
            userErrors { field message }
          }
        }`,
        { productId, variants: list.map((r) => ({ id: r.variantId, price: vkOf(r).toFixed(2) })) },
      );
      const m = errText(res.errors, res.data?.productVariantsBulkUpdate?.userErrors);
      if (m) problems.push(`${list.map((r) => r.sku).join(', ')}: ${m}`);
      else list.forEach((r) => okVariants.add(r.variantId));
    }

    // Tags je Produkt
    const newTags: Record<string, string[]> = {};
    for (const productId of changedTagProducts) {
      const e = tagEdits[productId]!;
      let current = [...(tagsByProduct[productId] || [])];
      const add: string[] = [];
      const remove: string[] = [];
      for (const t of TAGS) {
        const want = e[t.key];
        if (want === undefined) continue;
        const existing = current.filter((c) => tagMatch(c, t.label));
        if (want && existing.length === 0) add.push(t.label);
        if (!want && existing.length > 0) remove.push(...existing);
      }
      let msg = '';
      if (add.length) {
        const res = await queryRetry(
          `mutation TagsAdd($id: ID!, $tags: [String!]!) { tagsAdd(id: $id, tags: $tags) { userErrors { message } } }`,
          { id: productId, tags: add },
        );
        msg += errText(res.errors, res.data?.tagsAdd?.userErrors);
      }
      if (remove.length && !msg) {
        const res = await queryRetry(
          `mutation TagsRemove($id: ID!, $tags: [String!]!) { tagsRemove(id: $id, tags: $tags) { userErrors { message } } }`,
          { id: productId, tags: remove },
        );
        msg += errText(res.errors, res.data?.tagsRemove?.userErrors);
      }
      if (msg) {
        const sku = rows.find((r) => r.productId === productId)?.sku || productId;
        problems.push(`Tags ${sku}: ${msg}`);
      } else {
        current = current.filter((c) => !remove.includes(c)).concat(add);
        newTags[productId] = current;
        okTagProducts.add(productId);
      }
    }

    setRows((prev) => prev.map((r) => (okVariants.has(r.variantId) ? { ...r, vk: vkOf(r) } : r)));
    setEdits((prev) => {
      const next = { ...prev };
      okVariants.forEach((id) => delete next[id]);
      return next;
    });
    setTagsByProduct((prev) => ({ ...prev, ...newTags }));
    setTagEdits((prev) => {
      const next = { ...prev };
      okTagProducts.forEach((id) => delete next[id]);
      return next;
    });
    if (problems.length) setError(`Fehler bei: ${problems.join(' | ')}`);
    setSuccess(`${okVariants.size} Verkaufspreise und ${okTagProducts.size} Produkt-Tags gespeichert.`);
    setReviewOpen(false);
    setSaving(false);
  };

  const discard = (): void => {
    setEdits({});
    setTagEdits({});
    setSuccess('');
    setError('');
  };

  // ---------- Darstellung ----------
  if (loading) {
    return (
      <s-page heading="Preisanpassung manuell">
        <s-section>
          <s-text>Produkte werden geladen …</s-text>
          <s-spinner />
        </s-section>
      </s-page>
    );
  }


  // Gewinn als „€ / %"; leuchtend rot, wenn die Gewinngrenze unterschritten ist
  const profitText = (p: { eur: number | null; pct: number | null }, low: boolean) =>
    p.eur === null ? (
      '–'
    ) : (
      <span style={low ? red : undefined}>
        {fmt(round2(p.eur))} € / {p.pct === null ? '–' : fmt(round2(p.pct))} %
      </span>
    );

  // ---------- Änderungsübersicht (Prüfansicht vor dem Speichern) ----------
  const tagChangeList = changedTagProducts.map((productId) => {
    const e = tagEdits[productId]!;
    const current = tagsByProduct[productId] || [];
    const add: string[] = [];
    const remove: string[] = [];
    for (const t of TAGS) {
      const want = e[t.key];
      if (want === undefined) continue;
      const has = current.some((c) => tagMatch(c, t.label));
      if (want && !has) add.push(t.label);
      if (!want && has) remove.push(t.label);
    }
    const prodRows = rows.filter((r) => r.productId === productId);
    return {
      productId,
      skus: prodRows.map((r) => r.sku).join(', '),
      title: prodRows[0]?.title || '',
      add,
      remove,
    };
  });

  const pendingCount = changedPriceRows.length + changedTagProducts.length;

  const actionButtons = (
    <s-stack direction="inline" gap="base">
      <s-button onClick={onBack}>Zurück</s-button>
      <s-button onClick={discard} disabled={(saving || pendingCount === 0) || undefined}>
        Änderungen verwerfen
      </s-button>
      <s-button
        variant="primary"
        onClick={() => {
          setError('');
          setSuccess('');
          setReviewOpen(true);
        }}
        disabled={(saving || pendingCount === 0) || undefined}
      >
        {`Änderungen speichern (${changedPriceRows.length} VK, ${changedTagProducts.length} Tags)`}
      </s-button>
    </s-stack>
  );

  if (reviewOpen) {
    return (
      <s-page heading="Änderungen prüfen">
        <s-section>
          {error && <s-banner tone="critical">{error}</s-banner>}
          <s-text>
            Folgende Änderungen werden an Shopify übertragen: {changedPriceRows.length} Verkaufspreise und{' '}
            {changedTagProducts.length} Produkte mit geänderten Tags.
          </s-text>
          <s-stack direction="inline" gap="base">
            <s-button onClick={() => setReviewOpen(false)} disabled={saving || undefined}>
              Zurück zur Bearbeitung
            </s-button>
            <s-button variant="primary" onClick={save} disabled={(saving || pendingCount === 0) || undefined}>
              {saving ? 'Speichere...' : 'Jetzt endgültig speichern'}
            </s-button>
          </s-stack>
          {saving && <s-spinner />}
        </s-section>

        {changedPriceRows.length > 0 && (
          <s-section heading={`Verkaufspreise (${changedPriceRows.length})`}>
            <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">VK alt</s-table-header>
                <s-table-header listSlot="labeled">VK neu</s-table-header>
                <s-table-header listSlot="labeled">Differenz</s-table-header>
                <s-table-header listSlot="labeled">Gewinn D&VE neu</s-table-header>
                <s-table-header listSlot="labeled">Gewinn Reseller neu</s-table-header>
              </s-table-header-row>
              <s-table-body>
                {changedPriceRows.map((r) => {
                  const c = calc(r);
                  const diff = c.vk - r.vk;
                  return (
                    <s-table-row key={r.variantId}>
                      <s-table-cell>{r.sku}</s-table-cell>
                      <s-table-cell>{r.title}</s-table-cell>
                      <s-table-cell>{fmt(r.vk)}</s-table-cell>
                      <s-table-cell>{fmt(round2(c.vk))}</s-table-cell>
                      <s-table-cell>
                        <span style={diff > 0 ? red : green}>
                          {diff > 0 ? '+' : ''}
                          {fmt(round2(diff))}
                        </span>
                      </s-table-cell>
                      <s-table-cell>{profitText(c.pDv, c.lowDV)}</s-table-cell>
                      <s-table-cell>{c.vkRes === null ? '–' : profitText(c.pRes, c.lowRes)}</s-table-cell>
                    </s-table-row>
                  );
                })}
              </s-table-body>
            </s-table>
          </s-section>
        )}

        {tagChangeList.length > 0 && (
          <s-section heading={`Produkt-Tags (${tagChangeList.length})`}>
            <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">Tag hinzufügen</s-table-header>
                <s-table-header listSlot="labeled">Tag entfernen</s-table-header>
              </s-table-header-row>
              <s-table-body>
                {tagChangeList.map((t) => (
                  <s-table-row key={t.productId}>
                    <s-table-cell>{t.skus}</s-table-cell>
                    <s-table-cell>{t.title}</s-table-cell>
                    <s-table-cell>{t.add.join(', ') || '–'}</s-table-cell>
                    <s-table-cell>{t.remove.join(', ') || '–'}</s-table-cell>
                  </s-table-row>
                ))}
              </s-table-body>
            </s-table>
          </s-section>
        )}
      </s-page>
    );
  }

  // ---------- Filter, Seiten ----------
  const q = search.trim().toLowerCase();
  const visible = rows.filter(
    (r) =>
      (typeFilter === 'all' || typeFilter === '' || r.type === typeFilter) &&
      (tagFilter === 'all' || hasTag(r, tagFilter as TagKey)) &&
      (!q || r.sku.toLowerCase().includes(q) || r.title.toLowerCase().includes(q)) &&
      (lowFilter === 'all' || matchesLow(calc(r), lowFilter)),
  );
  const size = Number(pageSize) || 200;
  const pages = Math.max(1, Math.ceil(visible.length / size));
  const pg = Math.min(page, pages - 1);
  const pageRows = visible.slice(pg * size, (pg + 1) * size);
  const groups = types
    .map((t) => ({ type: t, items: pageRows.filter((r) => r.type === t) }))
    .filter((g) => g.items.length > 0);
  const bulkCount = rows.filter(bulkMatch).length;

  const pager = (
    <s-stack direction="inline" gap="base">
      <s-button onClick={() => setPage(Math.max(0, pg - 1))} disabled={(pg === 0) || undefined}>
        ← Vorherige Seite
      </s-button>
      <s-text>
        Seite {pg + 1} von {pages} ({visible.length} Produkte)
      </s-text>
      <s-button onClick={() => setPage(Math.min(pages - 1, pg + 1))} disabled={(pg >= pages - 1) || undefined}>
        Nächste Seite →
      </s-button>
    </s-stack>
  );

  return (
    <s-page heading="Preisanpassung manuell">
      <s-section heading="Einstellungen">
        <s-stack direction="inline" gap="base">
          <s-text-field
            label="VK D&VE (Faktor auf VK Frankreich)"
            details="Region- und VE-Rabatt kombiniert, z. B. 0,91 × 0,95 = 0,8645"
            value={factorDV}
            onInput={(e: any) => setFactorDV(e.currentTarget.value)}
          />
          <s-text-field label="MwSt D (%)" value={vatDE} onInput={(e: any) => setVatDE(e.currentTarget.value)} />
          <s-text-field label="MwSt FR (%)" value={vatFR} onInput={(e: any) => setVatFR(e.currentTarget.value)} />
          <s-text-field
            label="Rabatt Reseller 1 (%)"
            details="Remise spéciale 1, Kollektion Reseller1"
            value={rab1}
            onInput={(e: any) => setRab1(e.currentTarget.value)}
          />
          <s-text-field
            label="Rabatt Reseller 2 (%)"
            details="Remise spéciale 2, Kollektion Reseller2"
            value={rab2}
            onInput={(e: any) => setRab2(e.currentTarget.value)}
          />
        </s-stack>
        <s-stack direction="inline" gap="base">
          <s-text-field
            label="Gewinn D&VE: Grenze in %"
            details="Rot bei Wert ≤ Grenze"
            value={thDvPct}
            onInput={(e: any) => setThDvPct(e.currentTarget.value)}
          />
          <s-text-field
            label="Gewinn D&VE: Grenze in €"
            details="Rot bei Wert ≤ Grenze"
            value={thDvEur}
            onInput={(e: any) => setThDvEur(e.currentTarget.value)}
          />
          <s-text-field
            label="Gewinn Reseller: Grenze in %"
            details="Rot bei Wert ≤ Grenze"
            value={thResPct}
            onInput={(e: any) => setThResPct(e.currentTarget.value)}
          />
          <s-text-field
            label="Gewinn Reseller: Grenze in €"
            details="Rot bei Wert ≤ Grenze"
            value={thResEur}
            onInput={(e: any) => setThResEur(e.currentTarget.value)}
          />
        </s-stack>
        {!settingsOk && <s-banner tone="critical">Bitte alle Einstellungen als Zahl eingeben.</s-banner>}
      </s-section>

      <s-section heading="VK sammelweise anpassen">
        <s-stack direction="inline" gap="base">
          <s-select label="Produkttyp" value={bulkType} ref={listen('change', (e: any) => setBulkType(e.currentTarget.value))}>
            <s-option value="all">Alle Typen</s-option>
            {types.map((t) => (
              <s-option key={t} value={t}>
                {t}
              </s-option>
            ))}
          </s-select>
          <s-select label="Gewinngrenze" value={bulkLow} ref={listen('change', (e: any) => setBulkLow(e.currentTarget.value))}>
            <s-option value="all">egal</s-option>
            <s-option value="dv">D&VE unterschritten</s-option>
            <s-option value="res">Reseller unterschritten</s-option>
            <s-option value="any">D&VE oder Reseller unterschritten</s-option>
          </s-select>
        </s-stack>
        <s-stack direction="inline" gap="base">
          <s-text>Nur Produkte mit Tag (alle gewählten):</s-text>
          {TAGS.map((t) => (
            <s-checkbox
              key={t.key}
              label={t.label}
              checked={bulkTags.includes(t.key) || undefined}
              ref={listen('change', (e: any) => {
                const on = !!e.currentTarget.checked;
                setBulkTags((prev) => (on ? (prev.includes(t.key) ? prev : [...prev, t.key]) : prev.filter((k) => k !== t.key)));
              })}
            />
          ))}
        </s-stack>
        <s-stack direction="inline" gap="base">
          <s-text-field label="Betrag / Prozentsatz" value={amount} onInput={(e: any) => setAmount(e.currentTarget.value)} />
          <s-select label="Einheit" value={unit} ref={listen('change', (e: any) => setUnit(e.currentTarget.value))}>
            <s-option value="eur">Euro (€)</s-option>
            <s-option value="pct">Prozent (%)</s-option>
          </s-select>
        </s-stack>
        <s-text>Betrifft {bulkCount} Produkte (Typ, Tags und Gewinngrenze werden kombiniert).</s-text>
        <s-stack direction="inline" gap="base">
          <s-button onClick={() => applyBulk(1)} disabled={(bulkCount === 0) || undefined}>
            VK erhöhen
          </s-button>
          <s-button onClick={() => applyBulk(-1)} disabled={(bulkCount === 0) || undefined}>
            VK senken
          </s-button>
        </s-stack>
      </s-section>

      <s-section>
        {error && <s-banner tone="critical">{error}</s-banner>}
        {success && <s-banner tone="success">{success}</s-banner>}
        <s-stack direction="inline" gap="base">
          <s-select label="Typ" value={typeFilter || 'all'} ref={listen('change', (e: any) => setTypeFilter(e.currentTarget.value))}>
            <s-option value="all">Alle Typen ({rows.length})</s-option>
            {types.map((t) => (
              <s-option key={t} value={t}>
                {t} ({rows.filter((r) => r.type === t).length})
              </s-option>
            ))}
          </s-select>
          <s-select label="Tag" value={tagFilter} ref={listen('change', (e: any) => setTagFilter(e.currentTarget.value))}>
            <s-option value="all">Alle</s-option>
            {TAGS.map((t) => (
              <s-option key={t.key} value={t.key}>
                {t.label}
              </s-option>
            ))}
          </s-select>
          <s-select label="Gewinngrenze" value={lowFilter} ref={listen('change', (e: any) => setLowFilter(e.currentTarget.value))}>
            <s-option value="all">Alle</s-option>
            <s-option value="dv">D&VE unterschritten</s-option>
            <s-option value="res">Reseller unterschritten</s-option>
            <s-option value="any">D&VE oder Reseller unterschritten</s-option>
          </s-select>
          <s-text-field label="Suche (SKU/Bezeichnung)" value={search} onInput={(e: any) => setSearch(e.currentTarget.value)} />
          <s-select label="Zeilen pro Seite" value={pageSize} ref={listen('change', (e: any) => setPageSize(e.currentTarget.value))}>
            {PAGE_SIZES.map((n) => (
              <s-option key={n} value={n}>
                {n}
              </s-option>
            ))}
          </s-select>
        </s-stack>
        {actionButtons}
        {pager}
      </s-section>

      {groups.length === 0 && (
        <s-section>
          <s-text>Keine Produkte gefunden.</s-text>
        </s-section>
      )}

      {groups.map((g) => (
        <s-section key={g.type} heading={`${g.type} (${g.items.length} auf dieser Seite)`}>
          <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</s-table-header>
              <s-table-header listSlot="labeled">VK</s-table-header>
              <s-table-header listSlot="labeled">VK D&VE</s-table-header>
              <s-table-header listSlot="labeled">Gewinn D&VE</s-table-header>
              <s-table-header listSlot="labeled">VK Reseller</s-table-header>
              <s-table-header listSlot="labeled">Gewinn Reseller</s-table-header>
              {TAGS.map((t) => (
                <s-table-header key={t.key} listSlot="labeled">
                  {t.label}
                </s-table-header>
              ))}
            </s-table-header-row>
            <s-table-body>
              {g.items.map((r) => {
                const c = calc(r);
                return (
                  <s-table-row key={r.variantId}>
                    <s-table-cell>{r.sku}</s-table-cell>
                    <s-table-cell>{r.title}</s-table-cell>
                    <s-table-cell>{fmt(r.ek)}</s-table-cell>
                    <s-table-cell>
                      <s-text-field
                        label={`VK ${r.sku}`}
                        labelAccessibilityVisibility="exclusive"
                        value={edits[r.variantId] ?? fmt(r.vk)}
                        onInput={(e: any) => {
                          const v = e.currentTarget.value;
                          setEdits((prev) => ({ ...prev, [r.variantId]: v }));
                        }}
                      />
                    </s-table-cell>
                    <s-table-cell>{c.vkDV === null ? '–' : fmt(round2(c.vkDV))}</s-table-cell>
                    <s-table-cell>{profitText(c.pDv, c.lowDV)}</s-table-cell>
                    <s-table-cell>
                      {c.vkRes === null ? '–' : `${fmt(round2(c.vkRes))}${r.r1 && r.r2 ? ' (R1+R2)' : r.r1 ? ' (R1)' : ' (R2)'}`}
                    </s-table-cell>
                    <s-table-cell>{c.vkRes === null ? '–' : profitText(c.pRes, c.lowRes)}</s-table-cell>
                    {TAGS.map((t) => (
                      <s-table-cell key={t.key}>
                        <s-checkbox
                          accessibilityLabel={`${r.sku}: ${t.label}`}
                          checked={hasTag(r, t.key) || undefined}
                          ref={listen('change', (e: any) => {
                            const on = !!e.currentTarget.checked;
                            setTagEdits((prev) => ({ ...prev, [r.productId]: { ...prev[r.productId], [t.key]: on } }));
                          })}
                        />
                      </s-table-cell>
                    ))}
                  </s-table-row>
                );
              })}
            </s-table-body>
          </s-table>
        </s-section>
      ))}

      <s-section>
        {pager}
        {actionButtons}
      </s-section>
    </s-page>
  );
}
