import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { api } from "../api";
import Icon from "../components/Icon";
import { useSettings } from "../settings";
import {
  amountText, articlePrimary, articleSecondary, fmt, relativeExpiry,
} from "../units";
import Bars from "./charts/Bars";
import Donut from "./charts/Donut";
import Line from "./charts/Line";
import Wasserfall from "./charts/Wasserfall";
import { istDunkel, mitFarben, tokenFarbe } from "./palette";

/**
 * Verzeichnis aller Karten der Startseite.
 *
 * Eine Karte ist ein Eintrag hier – mehr braucht es nicht, um sie im
 * Hinzufügen-Menü und im Raster verfügbar zu machen. Jede Karte lädt ihre
 * eigenen Daten; so kostet eine nicht genutzte Karte auch keine Abfrage.
 */

// ---------------------------------------------------------------- Helfer
function useDaten(laden, abhaengig = []) {
  const [daten, setDaten] = useState(null);
  const [fehler, setFehler] = useState(null);
  useEffect(() => {
    let aktiv = true;
    laden()
      .then((d) => aktiv && setDaten(d))
      .catch((e) => aktiv && setFehler(e.message));
    return () => { aktiv = false; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, abhaengig);
  return { daten, fehler };
}

function Zustand({ fehler, daten, children, leer = "Keine Daten." }) {
  if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
  if (daten == null) return <div className="empty">Lädt…</div>;
  if (Array.isArray(daten) && daten.length === 0) return <div className="empty">{leer}</div>;
  return children;
}

/**
 * Beschriftung der Zeitachse. Bei kurzen Zeiträumen tastet der Server stündlich
 * ab – dann ist die Uhrzeit die nützliche Angabe, sonst das Datum.
 */
function zeitLabel(iso, tage) {
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return String(iso);
  if (tage <= 2) return d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
  if (tage <= 14) {
    return d.toLocaleString("de-DE", { day: "2-digit", month: "2-digit", hour: "2-digit" });
  }
  return d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" });
}

// ---------------------------------------------------------------- Aktionen
/**
 * Artikelsuche mit Tastaturbedienung, wie sie die Aktionskarten brauchen.
 * Erst ab zwei Zeichen wird gesucht – sonst laedt jede Karte beim Oeffnen die
 * halbe Artikelliste.
 */
function Artikelsuche({ gewaehlt, onWaehlen }) {
  const [text, setText] = useState("");
  const [treffer, setTreffer] = useState([]);

  useEffect(() => {
    if (gewaehlt || text.trim().length < 2) { setTreffer([]); return undefined; }
    let aktiv = true;
    const zeit = setTimeout(() => {
      api.listProducts(text.trim())
        .then((liste) => aktiv && setTreffer(liste.slice(0, 6)))
        .catch(() => aktiv && setTreffer([]));
    }, 250);
    return () => { aktiv = false; clearTimeout(zeit); };
  }, [text, gewaehlt]);

  if (gewaehlt) {
    return (
      <div className="aktion-gewaehlt">
        <span className="name">{gewaehlt.name}</span>
        <button className="btn-icon" title="Andere wählen"
          onClick={() => { onWaehlen(null); setText(""); }}>
          <Icon name="close" size={15} />
        </button>
      </div>
    );
  }
  return (
    <div className="aktion-suche">
      <input value={text} onChange={(e) => setText(e.target.value)}
        placeholder="Artikel suchen…" onMouseDown={(e) => e.stopPropagation()} />
      {treffer.length > 0 && (
        <ul className="aktion-treffer">
          {treffer.map((p) => (
            <li key={p.id}>
              <button onClick={() => { onWaehlen(p); setText(""); }}>{p.name}</button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

/**
 * Ein- und Auslagern ohne Seitenwechsel: Artikel suchen, Menge eintragen,
 * fertig. MHD und Lagerort bleiben bewusst weg – wer die braucht, ist auf der
 * vollen Seite besser aufgehoben; hier zaehlt der schnelle Alltagsfall.
 */
function KarteSchnellbuchung({ richtung }) {
  const einlagern = richtung === "in";
  const [artikel, setArtikel] = useState(null);
  const [menge, setMenge] = useState("1");
  const [meldung, setMeldung] = useState(null);
  const [fehler, setFehler] = useState(null);
  const [laeuft, setLaeuft] = useState(false);

  const zahl = Number(String(menge).replace(",", "."));
  const bereit = artikel && zahl > 0 && !laeuft;

  async function buchen() {
    if (!bereit) return;
    setLaeuft(true);
    setFehler(null);
    setMeldung(null);
    try {
      // Die Menge zaehlt in Artikeleinheiten: Gibt es ein Gebinde, ist das eine
      // Packung, sonst die Basiseinheit. Der Server kennt kein "article".
      const unit = artikel.package_size > 0 ? "package" : artikel.base_unit;
      if (einlagern) {
        await api.checkInBatch({
          product_id: artikel.id, unit, lines: [{ quantity: zahl }],
        });
      } else {
        await api.checkOut({ product_id: artikel.id, quantity: zahl, unit });
      }
      setMeldung(`${fmt(zahl)} ${einlagern ? "eingelagert" : "ausgelagert"}.`);
      setMenge("1");
    } catch (err) {
      setFehler(err.message);
    } finally {
      setLaeuft(false);
    }
  }

  return (
    <div className="card-stack aktion-karte">
      <Artikelsuche gewaehlt={artikel} onWaehlen={(p) => {
        setArtikel(p); setMeldung(null); setFehler(null);
      }} />
      <div className="aktion-zeile">
        <input className="aktion-menge" value={menge} inputMode="decimal"
          onMouseDown={(e) => e.stopPropagation()}
          onChange={(e) => setMenge(e.target.value)}
          onKeyDown={(e) => { if (e.key === "Enter") buchen(); }} />
        <button className={`btn ${einlagern ? "primary" : ""}`} disabled={!bereit} onClick={buchen}>
          <Icon name={einlagern ? "checkin" : "checkout"} size={16} />
          {einlagern ? "Einlagern" : "Auslagern"}
        </button>
      </div>
      {meldung && <div className="alert ok">{meldung}</div>}
      {fehler && <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>}
    </div>
  );
}

/**
 * Ein fest eingestellter Artikel im Blick: Bestand jetzt, mit Hinweis auf den
 * Mindestbestand. Welcher Artikel gemeint ist, steht in den Karten-Props und
 * wird deshalb mitgespeichert.
 */
function KarteArtikel({ props: karteProps }) {
  const produktId = karteProps?.product_id;
  const { daten, fehler } = useDaten(
    () => (produktId ? api.getProduct(produktId) : Promise.resolve(null)),
    [produktId],
  );

  if (!produktId) {
    return <div className="empty">Kein Artikel gewählt – über „Anordnen“ einstellen.</div>;
  }
  return (
    <Zustand fehler={fehler} daten={daten}>
      {daten && (
        <div className="card-stack">
          <div className="kpi">
            <div className="kpi-label"><Icon name="package" size={14} />{daten.name}</div>
            <div className="kpi-value">{articlePrimary(daten)}</div>
          </div>
          <div className="muted small">{articleSecondary(daten)}</div>
        </div>
      )}
    </Zustand>
  );
}

function KarteAktionen() {
  return (
    <div className="card-actions">
      <Link className="btn primary" to="/checkin"><Icon name="checkin" size={16} />Einlagern</Link>
      <Link className="btn" to="/checkout"><Icon name="checkout" size={16} />Auslagern</Link>
    </div>
  );
}

// ---------------------------------------------------------------- Kennzahlen
function Kennzahl({ wert, label, icon, ton }) {
  return (
    <div className={`kpi ${ton || ""}`}>
      <div className="kpi-label"><Icon name={icon} size={14} />{label}</div>
      <div className="kpi-value">{wert}</div>
    </div>
  );
}

function KarteStatus() {
  const { daten, fehler } = useDaten(() => api.dashboardStats());
  return (
    <Zustand fehler={fehler} daten={daten}>
      <div className="kpi-row">
        <Kennzahl icon="clock" label="Bald ablaufend" wert={daten?.expiring_soon ?? 0}
          ton={daten?.expiring_soon ? "warn" : ""} />
        <Kennzahl icon="alert" label="Abgelaufen" wert={daten?.expired ?? 0}
          ton={daten?.expired ? "danger" : ""} />
        <Kennzahl icon="cart" label="Nachzukaufen" wert={daten?.shopping_items ?? 0} />
      </div>
    </Zustand>
  );
}

function KarteArtikelzahl() {
  const { daten, fehler } = useDaten(() => api.dashboardStats());
  return (
    <Zustand fehler={fehler} daten={daten}>
      <Kennzahl icon="package" label="Artikel mit Bestand" wert={daten?.products_in_stock ?? 0} />
    </Zustand>
  );
}

function KarteEinheiten() {
  const { daten, fehler } = useDaten(() => api.dashboardStats());
  return (
    <Zustand fehler={fehler} daten={daten}>
      <Kennzahl icon="box" label="Artikeleinheiten im Lager" wert={fmt(daten?.article_units ?? 0)} />
    </Zustand>
  );
}

// ---------------------------------------------------------------- Listen
/**
 * Ablaufliste in drei Ausprägungen.
 *
 * "soon" zeigt bewusst NUR das, was noch läuft: Eine Karte mit der Überschrift
 * „Bald ablaufend“, in der bereits Abgelaufenes steht, führt in die Irre.
 * Wer beides zusammen sehen will, nimmt "alle".
 */
const ABLAUF_LEER = {
  soon: "Nichts läuft demnächst ab.",
  expired: "Nichts ist abgelaufen.",
  alle: "Nichts läuft demnächst ab und nichts ist abgelaufen.",
};

function KarteAblauf({ modus = "alle" }) {
  const { formatBestBefore } = useSettings();
  const { daten, fehler } = useDaten(() => api.expiring());
  const zeilen = (daten || []).filter((e) => {
    if (modus === "expired") return e.days_left < 0;
    if (modus === "soon") return e.days_left >= 0;
    return true;
  });

  return (
    <Zustand fehler={fehler} daten={daten} leer={ABLAUF_LEER[modus]}>
      {zeilen.length === 0 ? (
        <div className="empty">{ABLAUF_LEER[modus]}</div>
      ) : (
        <div className="table-wrap">
          <table className="table">
            <thead>
              <tr><th>Produkt</th><th className="num">Menge</th><th>MHD</th><th>Frist</th></tr>
            </thead>
            <tbody>
              {zeilen.map((it) => (
                <tr key={it.lot_id} className={it.days_left < 0 ? "row-danger" : "row-warn"}>
                  <td data-label="Produkt">{it.product_name}</td>
                  <td data-label="Menge" className="num">
                    {articlePrimary(it)}
                    {articleSecondary(it) && <div className="muted small">{articleSecondary(it)}</div>}
                  </td>
                  <td data-label="MHD">{formatBestBefore(it.best_before, it.best_before_precision)}</td>
                  <td data-label="Frist">
                    <span className={`badge ${it.days_left < 0 ? "danger" : "warn"}`}>
                      {relativeExpiry(it.days_left)}
                    </span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </Zustand>
  );
}

function KarteEinkaufsliste() {
  const { daten, fehler } = useDaten(async () => {
    const [produkte, gruppen] = await Promise.all([api.shoppingList(), api.groupShoppingList()]);
    return { produkte, gruppen };
  });

  if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
  if (!daten) return <div className="empty">Lädt…</div>;
  if (!daten.produkte.length && !daten.gruppen.length) {
    return <div className="empty">Alle Mindestbestände erreicht.</div>;
  }

  return (
    <div className="table-wrap">
      <table className="table">
        <thead>
          <tr><th>Bedarf</th><th className="num">Bestand</th><th className="num">Fehlt</th></tr>
        </thead>
        <tbody>
          {daten.gruppen.map((it) => (
            <tr key={`g${it.group_id}`}>
              <td data-label="Bedarf"><span className="badge accent">Gruppe</span> {it.name}</td>
              <td data-label="Bestand" className="num">{fmt(it.stock)} {it.unit_name}</td>
              <td data-label="Fehlt" className="num strong">{fmt(it.deficit)} {it.unit_name}</td>
            </tr>
          ))}
          {daten.produkte.map((it) => (
            <tr key={`p${it.product_id}`}>
              <td data-label="Bedarf">{it.name}</td>
              <td data-label="Bestand" className="num">{amountText(it.stock, it.package_size, it.base_unit)}</td>
              <td data-label="Fehlt" className="num strong">{amountText(it.deficit, it.package_size, it.base_unit)}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

const BEWEGUNG_LABEL = { in: "Eingelagert", out: "Ausgelagert", adjust: "Korrektur" };

function KarteBewegungen() {
  const { daten, fehler } = useDaten(() => api.listMovements(20));
  return (
    <Zustand fehler={fehler} daten={daten} leer="Noch keine Bewegungen.">
      <div className="table-wrap">
        <table className="table">
          <thead><tr><th>Zeitpunkt</th><th>Aktion</th><th>Produkt</th></tr></thead>
          <tbody>
            {(daten || []).map((m) => (
              <tr key={m.id}>
                <td data-label="Zeitpunkt" className="muted">
                  {new Date(m.created_at).toLocaleString("de-DE", {
                    day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit",
                  })}
                </td>
                <td data-label="Aktion">
                  <span className={`badge ${m.type === "in" ? "ok" : m.type === "out" ? "warn" : ""}`}>
                    {BEWEGUNG_LABEL[m.type] || m.type}
                  </span>
                </td>
                <td data-label="Produkt">{m.product_name}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </Zustand>
  );
}

// ---------------------------------------------------------------- Diagramme
function KarteAblaufRing() {
  const { daten, fehler } = useDaten(() => api.dashboardExpirySplit());
  // Ob „Ohne MHD" (Gegenstände/undatierte Chargen) mitgezählt wird – gemerkt.
  const [mitOhneMhd, setMitOhneMhd] = useState(
    () => localStorage.getItem("ablauf_mit_ohne_mhd") !== "0",
  );
  function umschalten(on) {
    setMitOhneMhd(on);
    localStorage.setItem("ablauf_mit_ohne_mhd", on ? "1" : "0");
  }
  if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
  if (!daten) return <div className="empty">Lädt…</div>;

  // Zustandsfarben, nie Serienfarben – und immer mit Beschriftung.
  const segmente = [
    { name: "In Ordnung", value: daten.ok, color: tokenFarbe("--ok", "#2e7d55") },
    { name: "Bald ablaufend", value: daten.soon, color: tokenFarbe("--warn", "#b06f14") },
    { name: "Abgelaufen", value: daten.expired, color: tokenFarbe("--danger", "#c0392b") },
  ];
  if (mitOhneMhd) {
    segmente.push({ name: "Ohne MHD", value: daten.no_date, color: tokenFarbe("--muted", "#6b7480") });
  }
  return (
    <div className="ring-mit-schalter">
      <label className="check-inline ring-schalter">
        <input type="checkbox" checked={mitOhneMhd}
          onChange={(e) => umschalten(e.target.checked)} />
        <span>„Ohne MHD" einbeziehen</span>
      </label>
      <Donut segmente={segmente} gesamtLabel="Einheiten" />
    </div>
  );
}

function KarteKategorienRing() {
  const { daten, fehler } = useDaten(() => api.dashboardByCategory());
  if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
  if (!daten) return <div className="empty">Lädt…</div>;

  const segmente = mitFarben(
    daten.map((k) => ({ name: k.name, value: k.article_units })),
    istDunkel(),
  );
  return <Donut segmente={segmente} gesamtLabel="Einheiten" />;
}

function KarteKategorienSaeulen() {
  const { daten, fehler } = useDaten(() => api.dashboardByCategory());
  if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
  if (!daten) return <div className="empty">Lädt…</div>;

  const zeilen = daten.slice(0, 8).map((k) => ({
    name: k.name,
    teile: [
      { key: "ok", value: k.ok, label: "In Ordnung", color: tokenFarbe("--ok", "#2e7d55") },
      { key: "soon", value: k.soon, label: "Bald ablaufend", color: tokenFarbe("--warn", "#b06f14") },
      { key: "expired", value: k.expired, label: "Abgelaufen", color: tokenFarbe("--danger", "#c0392b") },
      { key: "no_date", value: k.no_date, label: "Ohne MHD", color: tokenFarbe("--muted", "#6b7480") },
    ],
  }));

  return (
    <>
      <ul className="chart-legend inline">
        {[["In Ordnung", "--ok"], ["Bald ablaufend", "--warn"], ["Abgelaufen", "--danger"], ["Ohne MHD", "--muted"]]
          .map(([name, token]) => (
            <li key={name}>
              <span className="swatch" style={{ background: tokenFarbe(token) }} />
              <span className="legend-name">{name}</span>
            </li>
          ))}
      </ul>
      <Bars zeilen={zeilen} einheit="Einheiten" />
    </>
  );
}

/**
 * Auswahlmöglichkeiten für die Verlaufskarten. Die Auswahl selbst sitzt im
 * Kartenkopf (siehe Dashboard) – so bleibt die ganze Kartenhöhe dem Diagramm.
 */
export const ZEITRAEUME = [
  // 1 Tag wird stündlich abgetastet; man sieht also, wann am Tag gelagert wurde.
  { tage: 1, label: "24 Stunden" },
  { tage: 2, label: "2 Tage" },
  { tage: 7, label: "7 Tage" },
  { tage: 30, label: "30 Tage" },
  { tage: 90, label: "90 Tage" },
  { tage: 180, label: "6 Monate" },
  { tage: 365, label: "1 Jahr" },
];

function KarteBestandsverlauf({ tage = 90 }) {
  const { daten, fehler } = useDaten(() => api.dashboardTimeline(tage), [tage]);

  if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
  if (!daten) return <div className="empty">Lädt…</div>;

  return (
    <Line
      labels={daten.map((p) => zeitLabel(p.at, tage))}
      serien={[{
        name: "Artikeleinheiten",
        color: tokenFarbe("--accent", "#3f51b5"),
        werte: daten.map((p) => p.article_units),
      }]}
      einheit="Einheiten"
    />
  );
}

/**
 * Verlauf eines Artikels. Welcher gemeint ist, steht in den Karten-Props und
 * ist damit gespeichert - frueher lag die Wahl nur in useState und war nach
 * jedem Neuladen wieder weg, weshalb sich damit nichts beobachten liess.
 */
function KarteArtikelverlauf({ tage = 90, props: karteProps }) {
  const gewaehlt = karteProps?.product_id ?? "";
  const { daten, fehler } = useDaten(
    () => (gewaehlt ? api.dashboardTimeline(tage, gewaehlt) : Promise.resolve([])),
    [gewaehlt, tage],
  );

  if (!gewaehlt) {
    return <div className="empty">Kein Artikel gewählt – über „Anordnen“ einstellen.</div>;
  }

  return (
    <div className="card-stack">
      {fehler ? (
        <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>
      ) : (
        <Line
          labels={(daten || []).map((p) => zeitLabel(p.at, tage))}
          serien={[{
            name: "Bestand",
            color: tokenFarbe("--accent", "#3f51b5"),
            werte: (daten || []).map((p) => p.article_units),
          }]}
          einheit="Einheiten"
        />
      )}
    </div>
  );
}

/**
 * Ein- und Auslagerungen als Brücke: die Bestandslinie plus die Balken, die
 * jeden ihrer Sprünge erklären. Die Mengen kommen deshalb in Artikeleinheiten
 * und nicht als Anzahl Vorgänge – sonst lägen Balken und Linie auf zwei Skalen.
 */
function KarteBewegungsverlauf({ tage = 30 }) {
  const { daten, fehler } = useDaten(() => api.dashboardFlow(tage), [tage]);

  if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
  if (!daten) return <div className="empty">Lädt…</div>;

  return (
    <Wasserfall
      abschnitte={daten.map((p) => ({ ...p, label: zeitLabel(p.at, tage) }))}
      einheit="Einheiten"
    />
  );
}

// ---------------------------------------------------------------- Verzeichnis
/**
 * Höhen sind Rasterzeilen à 40 px (plus 16 px Abstand je weiterer Zeile).
 * Die Mindesthöhe ist bewusst so gewählt, dass der Inhalt ohne Rollbalken
 * hineinpasst – vorher war sie gleich der Standardhöhe und beide zu klein.
 *
 *   nutzbare Höhe = h * 40 + (h - 1) * 16 − 41 (Kopf) − 32 (Innenabstand)
 *   h=3 → 79 px   h=4 → 119 px   h=6 → 199 px   h=7 → 239 px
 */
export const KARTEN = {
  actions: {
    titel: "Schnellzugriff", icon: "checkin", vorschau: "aktion",
    beschreibung: "Knöpfe zum Ein- und Auslagern",
    standard: { w: 4, h: 3 }, min: { w: 3, h: 3 },
    komponente: KarteAktionen,
  },
  "checkin-quick": {
    titel: "Einlagern", icon: "checkin", vorschau: "aktion",
    beschreibung: "Artikel suchen, Menge eintragen – ohne Seitenwechsel",
    standard: { w: 4, h: 4 }, min: { w: 3, h: 4 },
    komponente: () => <KarteSchnellbuchung richtung="in" />,
  },
  "checkout-quick": {
    titel: "Auslagern", icon: "checkout", vorschau: "aktion",
    beschreibung: "Artikel suchen, Menge entnehmen – ohne Seitenwechsel",
    standard: { w: 4, h: 4 }, min: { w: 3, h: 4 },
    komponente: () => <KarteSchnellbuchung richtung="out" />,
  },
  "product-stock": {
    titel: "Artikel im Blick", icon: "package", vorschau: "kennzahl",
    beschreibung: "Bestand eines festen Artikels – mehrfach verwendbar",
    standard: { w: 3, h: 3 }, min: { w: 2, h: 3 },
    artikel: true,
    komponente: KarteArtikel,
  },
  status: {
    titel: "Status", icon: "dashboard", vorschau: "kennzahlen",
    beschreibung: "Bald ablaufend, abgelaufen, nachzukaufen",
    standard: { w: 8, h: 3 }, min: { w: 4, h: 3 },
    komponente: KarteStatus,
  },
  "kpi-products": {
    titel: "Artikel mit Bestand", icon: "package", vorschau: "kennzahl",
    beschreibung: "Anzahl verschiedener Artikel im Lager",
    standard: { w: 3, h: 3 }, min: { w: 2, h: 3 },
    komponente: KarteArtikelzahl,
  },
  "kpi-units": {
    titel: "Artikeleinheiten", icon: "box", vorschau: "kennzahl",
    beschreibung: "Summe aller Gebinde im Lager",
    standard: { w: 3, h: 3 }, min: { w: 2, h: 3 },
    komponente: KarteEinheiten,
  },
  expiring: {
    titel: "Bald ablaufend", icon: "clock", vorschau: "tabelle",
    beschreibung: "Nur Chargen innerhalb der Warnfrist – ohne bereits abgelaufene",
    standard: { w: 6, h: 6 }, min: { w: 4, h: 4 },
    komponente: () => <KarteAblauf modus="soon" />,
  },
  expired: {
    titel: "Abgelaufen", icon: "alert", vorschau: "tabelle",
    beschreibung: "Nur überfällige Chargen",
    standard: { w: 6, h: 5 }, min: { w: 4, h: 4 },
    komponente: () => <KarteAblauf modus="expired" />,
  },
  "expiry-all": {
    titel: "Ablauf-Übersicht", icon: "clock", vorschau: "tabelle",
    beschreibung: "Abgelaufen und bald ablaufend in einer Liste",
    standard: { w: 6, h: 6 }, min: { w: 4, h: 4 },
    komponente: () => <KarteAblauf modus="alle" />,
  },
  shopping: {
    titel: "Einkaufsliste", icon: "cart", vorschau: "tabelle",
    beschreibung: "Produkte und Gruppen unter Mindestbestand",
    standard: { w: 6, h: 6 }, min: { w: 4, h: 4 },
    komponente: KarteEinkaufsliste,
  },
  movements: {
    titel: "Letzte Bewegungen", icon: "history", vorschau: "tabelle",
    beschreibung: "Wer hat zuletzt was ein- oder ausgelagert",
    standard: { w: 6, h: 6 }, min: { w: 4, h: 4 },
    komponente: KarteBewegungen,
  },
  "expiry-donut": {
    titel: "Ablauf-Anteile", icon: "clock", vorschau: "ring",
    beschreibung: "Ring: in Ordnung, bald ablaufend, abgelaufen",
    standard: { w: 4, h: 6 }, min: { w: 3, h: 5 },
    komponente: KarteAblaufRing,
  },
  "category-donut": {
    titel: "Kategorien-Anteile", icon: "tag", vorschau: "ring",
    beschreibung: "Ring: welche Kategorie wie viel ausmacht",
    standard: { w: 4, h: 6 }, min: { w: 3, h: 5 },
    komponente: KarteKategorienRing,
  },
  "category-bars": {
    titel: "Kategorien nach Zustand", icon: "tag", vorschau: "balken",
    beschreibung: "Balken je Kategorie, nach Ablauf aufgeteilt",
    standard: { w: 8, h: 6 }, min: { w: 4, h: 5 },
    komponente: KarteKategorienSaeulen,
  },
  "stock-timeline": {
    titel: "Bestandsverlauf", icon: "history", vorschau: "linie",
    beschreibung: "Artikeleinheiten im Lager über 90 Tage",
    zeitraum: 90, standard: { w: 8, h: 6 }, min: { w: 4, h: 5 },
    komponente: KarteBestandsverlauf,
  },
  "product-timeline": {
    titel: "Verlauf eines Artikels", icon: "package", vorschau: "linie",
    beschreibung: "Ein fester Artikel über die Zeit – mehrfach verwendbar",
    zeitraum: 90, standard: { w: 8, h: 7 }, min: { w: 4, h: 6 },
    artikel: true,
    komponente: KarteArtikelverlauf,
  },
  "activity-timeline": {
    titel: "Ein- und Auslagerungen", icon: "history", vorschau: "wasserfall",
    beschreibung: "Brücke: Bestandslinie mit grünen und roten Balken je Bewegung",
    zeitraum: 30, standard: { w: 8, h: 6 }, min: { w: 5, h: 5 },
    komponente: KarteBewegungsverlauf,
  },
};

export const KARTEN_IDS = Object.keys(KARTEN);
