/* =========================================================
   CONNIE — Jarvis cards (S3a + S3b)

   Four new Jarvis dashboard cards backed by GET /web/jarvis:
     - NextMeetingCard  (next upcoming calendar event)
     - TasksCard        (open tasks + overdue highlight)
     - MailsCard        (unread mails top 5 — From + Subject, no snippet)
     - InsightsCard     (KG insights/decisions/reflections, max 5)

   Pure logic helpers (testable without React):
     window.JarvisCardLogic = {
       parseEventStart,   // "HH:MM" / "HH:MM-HH:MM" / "ganztaegig" / "" -> {h,m}|null
       parseDue,          // "2026-07-04" -> Date(midnight) | null
       nextEvent,         // (todayItems, tomItems, now) -> {item, startsAt, isToday}|null
       overdueTasks,      // (taskItems, today) -> [{item, due}]
       relTime,           // (dateStr, now) -> "vor N Min/Std/Tagen" | ""
       shortFrom,         // ("Name <a@b.de>" | "a@b.de") -> "Name" | "a"
     }

   Each card receives `data` (the full /web/jarvis response payload) and
   fail-softs gracefully: null=loading, available:false=unavailable.

   New components are in window scope so app.jsx can reference them without
   a build step. No collision with panels.jsx — different window-global names.
   CSS uses existing class names from styles.css where possible; new layout
   uses inline styles to avoid touching styles.css (S3b scope, design §5).
   ========================================================= */

/* React hooks — alias with j* prefix to avoid any potential name collision
   if this file is ever concatenated with panels.jsx in a future build. */
const { useState: jState, useEffect: jEffect } = React;

// ---------------------------------------------------------------------------
// PURE HELPERS (window.JarvisCardLogic)
// Tested by jarvis-cards.test.mjs — must stay dependency-free.
// ---------------------------------------------------------------------------

/**
 * parseEventStart — defensively extract {h, m} from a time string.
 *
 * Handles four shapes:
 *   "HH:MM"          -> {h: 10, m: 30}
 *   "HH:MM-HH:MM"    -> {h: 10, m: 30}  (start time only)
 *   "ganztaegig"     -> null  (all-day, no specific start)
 *   "" / any other   -> null
 */
function parseEventStart(timeStr) {
  if (!timeStr || typeof timeStr !== "string") return null;
  const t = timeStr.trim();
  if (!t || t === "ganztaegig") return null;
  // Accept "HH:MM" or "HH:MM-HH:MM" (take the start part)
  const m = t.match(/^(\d{1,2}):(\d{2})/);
  if (!m) return null;
  const h = parseInt(m[1], 10);
  const min = parseInt(m[2], 10);
  if (h < 0 || h > 23 || min < 0 || min > 59) return null;
  return { h, min };
}

/**
 * parseDue — parse a due-date string ("2026-07-04") to a Date at midnight
 * local time. Returns null for empty / unparseable strings.
 */
function parseDue(str) {
  if (!str || typeof str !== "string") return null;
  const t = str.trim();
  if (!t) return null;
  // Accept ISO date "YYYY-MM-DD" (calendar task format)
  const dm = t.match(/^(\d{4})-(\d{2})-(\d{2})/);
  if (!dm) return null;
  const y = parseInt(dm[1], 10);
  const mo = parseInt(dm[2], 10) - 1; // 0-indexed month
  const d = parseInt(dm[3], 10);
  const date = new Date(y, mo, d, 0, 0, 0, 0);
  if (isNaN(date.getTime())) return null;
  return date;
}

/**
 * _parseEventEnd — extract the END time {h, min} from a "HH:MM-HH:MM" range.
 * Returns null for single-time / all-day / unparseable strings.
 */
function _parseEventEnd(timeStr) {
  if (!timeStr || typeof timeStr !== "string") return null;
  const m = timeStr.trim().match(/^\d{1,2}:\d{2}\s*-\s*(\d{1,2}):(\d{2})/);
  if (!m) return null;
  const h = parseInt(m[1], 10);
  const min = parseInt(m[2], 10);
  if (h < 0 || h > 23 || min < 0 || min > 59) return null;
  return { h, min };
}

/**
 * nextEvent — find the next upcoming OR currently running calendar event.
 *
 * Searches todayItems first (calendar_today), then tomItems (calendar_tomorrow).
 * All-day events (no parseable time) are skipped for countdown purposes.
 * A running event (start <= now < end) is returned before any future one
 * (QS-JV-3; NextMeetingCard shows it as "läuft seit HH:MM"). The end comes
 * from a "HH:MM-HH:MM" range when present, otherwise a default 60-minute
 * window is assumed.
 * Returns { item, startsAt: Date, isToday: boolean } or null when neither a
 * running nor a future timed event is found.
 *
 * Note: timezone assumption is Europe/Berlin (client-local). This is documented
 * as a known limitation (design §7): breaks on travel, accepted V1.
 */
function nextEvent(todayItems, tomItems, now) {
  const _now = now instanceof Date ? now : new Date();
  const todayBase = new Date(_now);
  todayBase.setHours(0, 0, 0, 0);
  const tomBase = new Date(todayBase);
  tomBase.setDate(tomBase.getDate() + 1);

  const sources = [
    { items: todayItems || [], base: todayBase, isToday: true },
    { items: tomItems || [], base: tomBase, isToday: false },
  ];

  for (const { items, base, isToday } of sources) {
    if (!Array.isArray(items)) continue;
    // Items from the sidecar come in start-time order; find the first future one.
    for (const item of items) {
      if (!item || typeof item !== "object") continue;
      const parsed = parseEventStart(item.time || "");
      if (!parsed) continue; // skip all-day / no-time events
      const startsAt = new Date(base);
      startsAt.setHours(parsed.h, parsed.min, 0, 0);
      if (startsAt > _now) {
        return { item, startsAt, isToday };
      }
      // QS-JV-3: started but not yet over -> the event is running and is the
      // one to show. Cross-midnight or missing ends fall back to +60 minutes.
      const endParsed = _parseEventEnd(item.time || "");
      let endsAt = new Date(startsAt);
      if (endParsed) endsAt.setHours(endParsed.h, endParsed.min, 0, 0);
      if (!endParsed || endsAt <= startsAt) {
        endsAt = new Date(startsAt.getTime() + 60 * 60000);
      }
      if (_now < endsAt) {
        return { item, startsAt, isToday };
      }
    }
  }
  return null;
}

/**
 * overdueTasks — return task items whose due date is strictly before `today`.
 *
 * `today` should be a Date set to midnight (start of today) in local time.
 * Tasks with no `due` field or an unparseable due date are excluded from the
 * overdue list (they show normally in the task list without a red highlight).
 *
 * Returns an array of { item, due: Date } pairs sorted earliest-first.
 */
function overdueTasks(taskItems, today) {
  const _today = today instanceof Date ? today : (() => {
    const d = new Date();
    d.setHours(0, 0, 0, 0);
    return d;
  })();
  const out = [];
  for (const item of (taskItems || [])) {
    if (!item || typeof item !== "object") continue;
    const due = parseDue(item.due || "");
    if (!due) continue;
    if (due.getTime() < _today.getTime()) {
      out.push({ item, due });
    }
  }
  out.sort((a, b) => a.due - b.due);
  return out;
}

/**
 * relTime — format a past timestamp as a relative German label.
 *
 * Accepts any string `Date` can parse: ISO 8601 ("2026-05-31T16:00:00Z", KG
 * insights) as well as RFC 2822 mail-header dates ("Mon, 1 Jun 2026 08:12:00
 * +0200", gmail_unread_top5). Returns "" for empty/unparseable input so
 * callers can simply omit the label rather than show "Invalid Date".
 *
 * Buckets: <1 Min "gerade eben", <60 Min "vor N Min", <24 Std "vor N Std",
 * else "vor N Tag(en)". Future timestamps (clock skew) also fall back to
 * "gerade eben" rather than a negative duration.
 */
function relTime(dateStr, now) {
  if (!dateStr || typeof dateStr !== "string") return "";
  const t = dateStr.trim();
  if (!t) return "";
  const parsed = new Date(t);
  if (isNaN(parsed.getTime())) return "";
  const _now = now instanceof Date ? now : new Date();
  const diffMs = _now.getTime() - parsed.getTime();
  if (diffMs < 60_000) return "gerade eben";
  const diffMin = Math.floor(diffMs / 60_000);
  if (diffMin < 60) return `vor ${diffMin} Min`;
  const diffH = Math.floor(diffMin / 60);
  if (diffH < 24) return `vor ${diffH} Std`;
  const diffD = Math.floor(diffH / 24);
  return `vor ${diffD} Tag${diffD === 1 ? "" : "en"}`;
}

/**
 * shortFrom — shorten a Gmail "From" header to a display-friendly name.
 *
 * Handles `"Display Name" <addr@x.de>` and `Display Name <addr@x.de>` by
 * returning the display name; a bare `<addr@x.de>` (no name) or a plain
 * `addr@x.de` falls back to the local-part before "@". Anything else
 * (already-plain text, empty, non-string) is returned trimmed/as-is.
 */
function shortFrom(fromStr) {
  if (!fromStr || typeof fromStr !== "string") return "";
  const t = fromStr.trim();
  if (!t) return "";
  const angled = t.match(/^"?([^"<]*)"?\s*<([^>]+)>$/);
  if (angled) {
    const name = angled[1].trim();
    if (name) return name;
    const email = angled[2].trim();
    const at = email.indexOf("@");
    return at > 0 ? email.slice(0, at) : email;
  }
  const at = t.indexOf("@");
  if (at > 0 && !t.includes(" ")) return t.slice(0, at);
  return t;
}

// Expose pure helpers globally for testing + use by app.jsx if needed.
window.JarvisCardLogic = { parseEventStart, parseDue, nextEvent, overdueTasks, relTime, shortFrom };

// ---------------------------------------------------------------------------
// DISPLAY HELPERS (browser-only, not exported for pure-function testing)
// ---------------------------------------------------------------------------

/** Format a countdown from now to `target` Date as "in N Min" / "in X Std". */
function _countdown(target, now) {
  const diffMs = target.getTime() - (now instanceof Date ? now : new Date()).getTime();
  if (diffMs <= 0) return "jetzt";
  const diffMin = Math.round(diffMs / 60000);
  if (diffMin < 60) return `in ${diffMin} Min`;
  const diffH = Math.round(diffMin / 60);
  return `in ${diffH} Std`;
}

/** Format "HH:MM" from a Date object. */
function _toHHMM(date) {
  if (!(date instanceof Date) || isNaN(date.getTime())) return "";
  const h = String(date.getHours()).padStart(2, "0");
  const m = String(date.getMinutes()).padStart(2, "0");
  return `${h}:${m}`;
}

/** "Stand vor X Min" label from age_min or fresh_since fallback. */
function _standLabel(ageMins) {
  if (ageMins === null || ageMins === undefined) return "";
  return `Stand vor ${ageMins} Min`;
}

// Shared inline-style tokens (avoid touching styles.css in S3a scope).
const _TEAL = "#05E6A5";
const _RED = "#FF6464";
const _AMBER = "#FFB020";
const _PURPLE = "#B388FF";
const _FG2 = "var(--fg-2)";
const _FG3 = "var(--fg-3)";
const _MONO = "var(--font-mono)";

function _metaStyle() {
  return {
    fontSize: 11, fontFamily: _MONO, letterSpacing: "0.08em",
    color: _FG3, marginTop: 2,
  };
}

/**
 * _kindMeta — chip label/colors for a kg_recent_insights "kind" value.
 * Unknown/missing kind falls back to the "Insight" look (most common kind,
 * design §3). Backend sends lowercase ("insight"/"decision"/"reflection").
 */
function _kindMeta(kind) {
  const k = typeof kind === "string" ? kind.trim().toLowerCase() : "";
  if (k === "decision") {
    return { label: "Decision", color: _AMBER, bg: "rgba(255,176,32,0.12)" };
  }
  if (k === "reflection") {
    return { label: "Reflection", color: _PURPLE, bg: "rgba(179,136,255,0.12)" };
  }
  return { label: "Insight", color: _TEAL, bg: "rgba(5,230,165,0.10)" };
}

// ---------------------------------------------------------------------------
// NEXT MEETING CARD
// ---------------------------------------------------------------------------
/**
 * NextMeetingCard — shows the next upcoming calendar event.
 *
 * Props:
 *   data            - the full /web/jarvis response (or null while loading)
 *   onPrepMeeting   - callback fired when the "Prep" button is clicked
 */
function NextMeetingCard({ data, onPrepMeeting }) {
  const [now, setNow] = jState(new Date());

  // Refresh countdown every 30s so the label stays current.
  jEffect(() => {
    const id = setInterval(() => setNow(new Date()), 30_000);
    return () => clearInterval(id);
  }, []);

  if (data === null) {
    return (
      <div className="card">
        <div className="card__head">
          <div className="card__title">Nächster Termin</div>
        </div>
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          lade ...
        </div>
      </div>
    );
  }

  if (!data.available) {
    return (
      <div className="card">
        <div className="card__head">
          <div className="card__title">Nächster Termin</div>
        </div>
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          nicht verfügbar
        </div>
      </div>
    );
  }

  const fields = data.fields || {};
  const calToday = fields.calendar_today;
  const calTom = fields.calendar_tomorrow;
  const todayItems = (calToday && calToday.items) || [];
  const tomItems = (calTom && calTom.items) || [];
  const next = nextEvent(todayItems, tomItems, now);
  const standLabel = _standLabel(data.age_min);

  const todayMidnight = new Date(now);
  todayMidnight.setHours(0, 0, 0, 0);

  const noEventsStyle = {
    padding: "12px 8px", textAlign: "center", color: _FG3,
    fontSize: 12, fontFamily: _MONO,
  };

  return (
    <div className="card">
      <div className="card__head">
        <div className="card__title">Nächster Termin</div>
        {standLabel && <div className="card__meta">{standLabel}</div>}
      </div>
      {!next ? (
        <div style={noEventsStyle}>Keine weiteren Termine heute.</div>
      ) : (() => {
        const { item, startsAt } = next;
        const isRunning = startsAt <= now;
        const countdownLabel = isRunning
          ? `läuft seit ${_toHHMM(startsAt)} Uhr`
          : _countdown(startsAt, now);
        const peers = Array.isArray(item.peers) && item.peers.length > 0
          ? item.peers.join(", ")
          : null;
        return (
          <div style={{ padding: "10px 14px" }}>
            <div style={{ fontSize: 15, fontWeight: 600, color: "var(--fg-1)", marginBottom: 4 }}>
              {item.title || "(kein Titel)"}
            </div>
            {peers && (
              <div style={{ fontSize: 11, color: _FG2, marginBottom: 6, fontFamily: _MONO }}>
                mit {peers}
              </div>
            )}
            <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 6 }}>
              <span style={{
                fontSize: 12, fontFamily: _MONO, letterSpacing: "0.08em",
                color: isRunning ? _TEAL : _FG2,
                background: isRunning ? "rgba(5,230,165,0.10)" : "transparent",
                borderRadius: 4, padding: isRunning ? "2px 6px" : 0,
              }}>
                {_toHHMM(startsAt)} · {countdownLabel}
              </span>
              {typeof onPrepMeeting === "function" && (
                <button
                  onClick={onPrepMeeting}
                  title="Meeting vorbereiten"
                  style={{
                    marginLeft: "auto", fontSize: 11, fontFamily: _MONO,
                    letterSpacing: "0.08em", color: _TEAL,
                    background: "rgba(5,230,165,0.10)",
                    border: "1px solid rgba(5,230,165,0.25)",
                    borderRadius: 4, padding: "3px 10px", cursor: "pointer",
                  }}
                >
                  Prep
                </button>
              )}
            </div>
          </div>
        );
      })()}
    </div>
  );
}

window.NextMeetingCard = NextMeetingCard;

// ---------------------------------------------------------------------------
// TASKS CARD
// ---------------------------------------------------------------------------
/**
 * TasksCard — shows open tasks with overdue highlight.
 *
 * Props:
 *   data  - the full /web/jarvis response (or null while loading)
 */
function TasksCard({ data }) {
  if (data === null) {
    return (
      <div className="card">
        <div className="card__head"><div className="card__title">Aufgaben</div></div>
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          lade ...
        </div>
      </div>
    );
  }

  if (!data.available) {
    return (
      <div className="card">
        <div className="card__head"><div className="card__title">Aufgaben</div></div>
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          nicht verfügbar
        </div>
      </div>
    );
  }

  const fields = data.fields || {};
  const tasksField = fields.tasks_open;
  const items = (tasksField && tasksField.items) || [];
  const standLabel = _standLabel(data.age_min);

  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const overdueSet = new Set(
    overdueTasks(items, today).map((x) => x.item.id || x.item.title)
  );

  const overdueCount = overdueSet.size;
  const meta = items.length === 0
    ? "keine offenen Aufgaben"
    : `${items.length} offen${overdueCount > 0 ? ` · ${overdueCount} überfällig` : ""}`;

  return (
    <div className="card">
      <div className="card__head">
        <div className="card__title">Aufgaben</div>
        <div className="card__meta">{standLabel || meta}</div>
      </div>
      {standLabel && (
        <div style={{ ..._metaStyle(), padding: "0 14px 4px", marginTop: -2 }}>{meta}</div>
      )}
      {items.length === 0 ? (
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          Keine offenen Aufgaben.
        </div>
      ) : (
        <div style={{ padding: "4px 0 6px" }}>
          {items.map((item, idx) => {
            const key = item.id || item.title || idx;
            const isOverdue = overdueSet.has(item.id || item.title);
            const due = parseDue(item.due || "");
            const dueLabel = due
              ? due.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" })
              : (item.due || "");
            return (
              <div key={key} style={{
                display: "flex", alignItems: "center", gap: 8,
                padding: "6px 14px",
                borderBottom: "1px solid var(--line)",
              }}>
                <div style={{
                  flex: 1, fontSize: 13,
                  color: isOverdue ? _RED : "var(--fg-1)",
                  fontWeight: isOverdue ? 600 : 400,
                }}>
                  {item.title || "(kein Titel)"}
                </div>
                {dueLabel && (
                  <div style={{
                    fontSize: 11, fontFamily: _MONO, letterSpacing: "0.06em",
                    color: isOverdue ? _RED : _FG3,
                    flexShrink: 0,
                  }}>
                    {dueLabel}
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

window.TasksCard = TasksCard;

// ---------------------------------------------------------------------------
// MAILS CARD
// ---------------------------------------------------------------------------
/**
 * MailsCard — unread mails top 5 (From + Subject, no snippet — SR-JV-1/§2
 * Hard-Projection already drops it server-side; nothing to hide here).
 *
 * From/Subject/event titles are attacker-influenced strings (incoming mail,
 * SR-JV-6) — rendered ONLY as JSX text nodes below, never via
 * dangerouslySetInnerHTML, never used to build an href/src.
 *
 * Props:
 *   data  - the full /web/jarvis response (or null while loading)
 */
function MailsCard({ data }) {
  const [now, setNow] = jState(new Date());
  jEffect(() => {
    const id = setInterval(() => setNow(new Date()), 30_000);
    return () => clearInterval(id);
  }, []);

  if (data === null) {
    return (
      <div className="card">
        <div className="card__head"><div className="card__title">Ungelesen · Top 5</div></div>
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          lade ...
        </div>
      </div>
    );
  }

  if (!data.available) {
    return (
      <div className="card">
        <div className="card__head"><div className="card__title">Ungelesen · Top 5</div></div>
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          nicht verfügbar
        </div>
      </div>
    );
  }

  const fields = data.fields || {};
  const mailField = fields.gmail_unread_top5;
  const rawItems = (mailField && Array.isArray(mailField.items)) ? mailField.items : [];
  const items = rawItems.slice(0, 5);
  const count = mailField && typeof mailField.count === "number" ? mailField.count : items.length;
  const standLabel = _standLabel(data.age_min);
  const countLabel = count === 0 ? "keine ungelesenen Mails" : `${count} ungelesen`;

  return (
    <div className="card">
      <div className="card__head">
        <div className="card__title">Ungelesen · Top 5</div>
        <div className="card__meta">{standLabel || countLabel}</div>
      </div>
      {standLabel && (
        <div style={{ ..._metaStyle(), padding: "0 14px 4px", marginTop: -2 }}>{countLabel}</div>
      )}
      {items.length === 0 ? (
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          Keine ungelesenen Mails.
        </div>
      ) : (
        <div style={{ padding: "4px 0 6px" }}>
          {items.map((item, idx) => {
            const key = (item && item.thread_id) || idx;
            const from = shortFrom((item && item.from) || "");
            const subject = (item && item.subject) || "(kein Betreff)";
            const rel = relTime((item && item.date) || "", now);
            return (
              <div key={key} style={{
                display: "flex", flexDirection: "column", gap: 2,
                padding: "6px 14px", borderBottom: "1px solid var(--line)",
              }}>
                <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
                  <div style={{
                    fontSize: 13, fontWeight: 600, color: "var(--fg-1)",
                    flexShrink: 0, maxWidth: "42%", overflow: "hidden",
                    textOverflow: "ellipsis", whiteSpace: "nowrap",
                  }}>
                    {from || "(unbekannt)"}
                  </div>
                  <div style={{
                    fontSize: 13, color: _FG2, flex: 1, overflow: "hidden",
                    textOverflow: "ellipsis", whiteSpace: "nowrap",
                  }}>
                    {subject}
                  </div>
                </div>
                {rel && (
                  <div style={{ fontSize: 11, fontFamily: _MONO, color: _FG3 }}>{rel}</div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

window.MailsCard = MailsCard;

// ---------------------------------------------------------------------------
// INSIGHTS CARD
// ---------------------------------------------------------------------------
/**
 * InsightsCard — recent KG insights/decisions/reflections, capped at 5 even
 * if the backend ever sends more (design §3 "max 3-5 Eintraege").
 *
 * KG summaries are attacker-influenced strings (derived from conversations,
 * SR-JV-6) — rendered ONLY as JSX text nodes, never dangerouslySetInnerHTML,
 * never used to build an href/src.
 *
 * Props:
 *   data  - the full /web/jarvis response (or null while loading)
 */
function InsightsCard({ data }) {
  const [now, setNow] = jState(new Date());
  jEffect(() => {
    const id = setInterval(() => setNow(new Date()), 30_000);
    return () => clearInterval(id);
  }, []);

  if (data === null) {
    return (
      <div className="card">
        <div className="card__head"><div className="card__title">Erkenntnisse · KG</div></div>
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          lade ...
        </div>
      </div>
    );
  }

  if (!data.available) {
    return (
      <div className="card">
        <div className="card__head"><div className="card__title">Erkenntnisse · KG</div></div>
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          nicht verfügbar
        </div>
      </div>
    );
  }

  const fields = data.fields || {};
  const insightsField = fields.kg_recent_insights;
  const rawItems = (insightsField && Array.isArray(insightsField.items)) ? insightsField.items : [];
  const items = rawItems.slice(0, 5);
  const standLabel = _standLabel(data.age_min);
  const countLabel = items.length === 0 ? "keine Erkenntnisse" : `${items.length} Erkenntnisse`;

  return (
    <div className="card">
      <div className="card__head">
        <div className="card__title">Erkenntnisse · KG</div>
        <div className="card__meta">{standLabel || countLabel}</div>
      </div>
      {standLabel && (
        <div style={{ ..._metaStyle(), padding: "0 14px 4px", marginTop: -2 }}>{countLabel}</div>
      )}
      {items.length === 0 ? (
        <div style={{ padding: "18px 8px", textAlign: "center", color: _FG3, fontSize: 12, fontFamily: _MONO }}>
          Keine aktuellen Erkenntnisse.
        </div>
      ) : (
        <div style={{ padding: "4px 0 6px" }}>
          {items.map((item, idx) => {
            const key = (item && item.id) || idx;
            const km = _kindMeta(item && item.kind);
            const summary = (item && item.summary) || "";
            const rel = relTime((item && item.created_at) || "", now);
            return (
              <div key={key} style={{
                display: "flex", flexDirection: "column", gap: 4,
                padding: "8px 14px", borderBottom: "1px solid var(--line)",
              }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <span style={{
                    fontSize: 10, fontFamily: _MONO, letterSpacing: "0.08em",
                    color: km.color, background: km.bg, borderRadius: 4,
                    padding: "1px 6px", flexShrink: 0,
                  }}>
                    {km.label}
                  </span>
                  {rel && (
                    <span style={{ fontSize: 11, fontFamily: _MONO, color: _FG3, marginLeft: "auto" }}>
                      {rel}
                    </span>
                  )}
                </div>
                <div style={{ fontSize: 13, color: "var(--fg-1)" }}>{summary}</div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

window.InsightsCard = InsightsCard;
