// MedLink · Mobile Light
// Rollen-adaptive, mobil-optimierte Oberfläche für Smartphone und Tablet.
// Aktiviert wird sie über body.ml-mode (siehe MedLink.html).
// Nutzt window.STORE, window.BEWOHNER, window.ANFRAGEN, window.HEIMS und die
// existierenden medlink:*-Events — kein separater State.

(function () {
  const { useState, useEffect, useRef, useMemo } = React;

  // ------------------- Helpers -------------------
  const greet = () => {
    const h = new Date().getHours();
    if (h < 5) return "Guten Abend";
    if (h < 11) return "Guten Morgen";
    if (h < 18) return "Guten Tag";
    return "Guten Abend";
  };

  const fmtDate = () => {
    const d = new Date();
    const days = ["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"];
    const months = ["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"];
    return `${days[d.getDay()]}, ${d.getDate()}. ${months[d.getMonth()]}`;
  };

  const userNameFor = (role) => ({
    heim: "Schwester Anna Braun",
    arzt: "Dr. A. Schneider",
    apotheke: "Adler-Apotheke",
    therapeut: "Therapeut M. Maier",
  }[role] || "Nutzer:in");

  // First-name for a personal greeting ("Frau Maier", "Schwester Anna", "Dr. Schneider")
  const shortNameFor = (role) => {
    const full = userNameFor(role);
    if (role === "heim") return full.split(" ").slice(0, 2).join(" ");   // "Schwester Anna"
    if (role === "arzt") return full.replace("Dr. med. ", "Dr. ").replace(/\s*[A-Z]\.\s*/, " ").trim(); // "Dr. Schneider"
    if (role === "apotheke") return full;                                 // "Adler-Apotheke"
    if (role === "therapeut") return "Frau " + (full.split(" ").pop() || "Maier"); // "Frau Maier"
    return full;
  };

  const roleLabelFor = (role) => ({
    heim: "Pflegekraft · Haus Rosenhof",
    arzt: "Hausärztin · Multi-Heim",
    apotheke: "Apotheke · eRezept",
    therapeut: "Physiotherapie",
  }[role] || role);

  const roleTitleFor = (role) => ({
    heim: "Pflegeheim", arzt: "Arzt", apotheke: "Apotheke", therapeut: "Therapeut"
  }[role] || role);

  const initialsOf = (name) => {
    const parts = name.replace("Dr. ", "").replace("med. ", "").split(/[\s,]+/).filter(Boolean);
    return (parts[0]?.[0] || "") + (parts[1]?.[0] || "");
  };

  const bewName = (bid) => (window.BEWOHNER || []).find((b) => b.id === bid)?.name || bid;
  const heimName = (hid) => (window.HEIMS || []).find((h) => h.id === hid)?.name || hid;
  const heimShort = (hid) => (window.HEIMS || []).find((h) => h.id === hid)?.mark || "—";

  const statusLabel = (s) => ({
    offen: "offen", freigegeben: "freigegeben", abgelehnt: "abgelehnt", geliefert: "geliefert"
  }[s] || s);

  // Live "in X Minuten" countdown — re-evaluates every 30 s so it stays accurate
  const MLCountdown = ({ toHHMM }) => {
    const [, tick] = useState(0);
    useEffect(() => {
      const id = setInterval(() => tick((n) => n + 1), 30_000);
      return () => clearInterval(id);
    }, []);
    const [h, m] = (toHHMM || "14:30").split(":").map(Number);
    const now = new Date();
    const target = new Date();
    target.setHours(h, m, 0, 0);
    const diffMin = Math.round((target - now) / 60000);
    if (diffMin > 60) {
      const hours = Math.floor(diffMin / 60);
      const mins = diffMin % 60;
      return <>in {hours} Std{mins ? ` ${mins} Min` : ""}</>;
    }
    if (diffMin > 1) return <>in {diffMin} Minuten</>;
    if (diffMin === 1) return <>in 1 Minute</>;
    if (diffMin === 0) return <>jetzt gleich</>;
    if (diffMin >= -60) return <>läuft seit {Math.abs(diffMin)} Min</>;
    return <>bereits vorbei</>;
  };

  // ------------------- Tab configs -------------------
  const TABS = {
    heim: [
      { id: "start", label: "Start", icon: "home" },
      { id: "anfragen", label: "Anfragen", icon: "inbox" },
      { id: "bewohner", label: "Bewohner", icon: "users" },
      { id: "mehr", label: "Mehr", icon: "menu" },
    ],
    arzt: [
      { id: "start", label: "Start", icon: "home" },
      { id: "freigabe", label: "Freigabe", icon: "check" },
      { id: "bewohner", label: "Patienten", icon: "users" },
      { id: "mehr", label: "Mehr", icon: "menu" },
    ],
    apotheke: [
      { id: "start", label: "Start", icon: "home" },
      { id: "eingang", label: "Eingang", icon: "inbox" },
      { id: "ausgang", label: "Ausgang", icon: "truck" },
      { id: "mehr", label: "Mehr", icon: "menu" },
    ],
    therapeut: [
      { id: "start", label: "Start", icon: "home" },
      { id: "termine", label: "Termine", icon: "calendar" },
      { id: "doku", label: "Doku", icon: "clipboard" },
      { id: "mehr", label: "Mehr", icon: "menu" },
    ],
  };

  // ------------------- Top bar -------------------
  const MLTopBar = ({ role, onBell, onAvatar, scrolled, bellCount }) => {
    const name = userNameFor(role);
    const shortName = shortNameFor(role);
    return (
      <div className="ml-topbar" data-scrolled={scrolled}>
        <div className="ml-greet">
          <div className="ml-greet-hi">{greet()}, {shortName}</div>
          <div className="ml-greet-meta">{fmtDate()}</div>
        </div>
        <button className="ml-bell-btn" onClick={onBell} aria-label="Benachrichtigungen">
          <Icon name="bell" size={16} />
          {bellCount > 0 && <span className="ml-bell-dot" />}
        </button>
        <button className="ml-avatar-btn" onClick={onAvatar} aria-label={`${shortName} · Konto & Einstellungen`} title={shortName}>
          <span className="ml-avatar-initials">{initialsOf(name)}</span>
          <Icon name="chevronDown" size={10} className="ml-avatar-chev" />
        </button>
      </div>
    );
  };

  // ------------------- Bottom tabs -------------------
  const MLBottomTabs = ({ role, view, setView, badges }) => {
    const tabs = TABS[role] || TABS.heim;
    const go = (id) => {
      setView(id);
      window.dispatchEvent(new CustomEvent("medlink:setView", { detail: { portal: role, view: id } }));
    };
    return (
      <nav className="ml-tabs" role="navigation" aria-label="Hauptnavigation">
        {tabs.map((t) => (
          <button
            key={t.id}
            className="ml-tab"
            data-active={view === t.id}
            onClick={() => go(t.id)}
            aria-label={t.label}
          >
            <div className="ml-tab-ico">
              <Icon name={t.icon} size={18} />
            </div>
            <div className="ml-tab-lbl">{t.label}</div>
            {badges[t.id] > 0 && <div className="ml-tab-badge">{badges[t.id] > 99 ? "99+" : badges[t.id]}</div>}
          </button>
        ))}
      </nav>
    );
  };

  // ------------------- Start (Home) -------------------
  const MLStart = ({ role, heim, setView, onHeimChange }) => {
    window.useStoreVersion();

    const all = window.ANFRAGEN || [];
    const scoped = role === "heim" && heim !== "alle" ? all.filter((a) => a.heim === heim) : all;
    const offen = scoped.filter((a) => a.status === "offen");
    const freigegeben = scoped.filter((a) => a.status === "freigegeben");
    const geliefert = scoped.filter((a) => a.status === "geliefert");
    const kritisch = offen.filter((a) => a.prio === "U" || a.prio === "H");
    const kommissionierbar = freigegeben.filter((a) => !a.kommissioniert);

    const hero = (() => {
      if (role === "arzt") {
        return {
          eyebrow: "Freigabe",
          num: offen.length,
          head: offen.length === 1 ? "Anfrage wartet auf Sie" : "Anfragen warten auf Sie",
          meta: kritisch.length > 0
            ? `${kritisch.length} davon als kritisch markiert – bitte prüfen.`
            : "Keine kritischen Vorgänge.",
          action: "Warteliste öffnen",
          target: "freigabe",
          icon: "stethoscope",
        };
      }
      if (role === "apotheke") {
        return {
          eyebrow: "eRezept-Eingang",
          num: freigegeben.length,
          head: freigegeben.length === 1 ? "neues Rezept" : "neue Rezepte",
          meta: kommissionierbar.length > 0
            ? `${kommissionierbar.length} zur Kommissionierung bereit.`
            : "Alles kommissioniert. Nächste Tour 14:30.",
          action: "Eingang öffnen",
          target: "eingang",
          icon: "pill",
        };
      }
      if (role === "therapeut") {
        return {
          eyebrow: "Heute",
          num: 6,
          head: "Termine heute",
          meta: (
            <>
              Nächster Termin um <strong>14:30</strong> · Frau Kühn, Haus Rosenhof
              <span style={{ display: "block", marginTop: 4, fontSize: 12, color: "var(--fg-subtle)" }}>
                <MLCountdown toHHMM="14:30" />
              </span>
            </>
          ),
          action: "Tagesplan öffnen",
          target: "termine",
          icon: "calendar",
        };
      }
      // heim (default)
      return {
        eyebrow: "Heute",
        num: offen.length,
        head: offen.length === 1 ? "offene Anfrage" : "offene Anfragen",
        meta: kritisch.length > 0
          ? `${kritisch.length} mit hoher Priorität – Arzt-Rückmeldung ausstehend.`
          : "Alles im grünen Bereich. Visiten laufen planmäßig.",
        action: "Anfragen öffnen",
        target: "anfragen",
        icon: "inbox",
      };
    })();

    const kpis = (() => {
      if (role === "arzt") {
        return [
          { v: offen.length, l: "Warteschlange", tone: offen.length > 0 ? "accent" : "", go: () => setView("freigabe") },
          { v: kritisch.length, l: "Kritisch", tone: kritisch.length > 0 ? "warn" : "", go: () => setView("freigabe") },
          { v: 12, l: "Heute signiert", tone: "good" },
        ];
      }
      if (role === "apotheke") {
        return [
          { v: freigegeben.length, l: "Eingang", tone: "accent", go: () => setView("eingang") },
          { v: kommissionierbar.length, l: "Komm. bereit", tone: "" },
          { v: geliefert.length, l: "Heute geliefert", tone: "good", go: () => setView("ausgang") },
        ];
      }
      if (role === "therapeut") {
        return [
          // "Termine heute" is already the hero → avoid duplicate
          { v: 3, l: "Erledigt", tone: "good", go: () => setView("termine") },
          { v: 3, l: "Noch offen", tone: "accent", go: () => setView("termine") },
          { v: 2, l: "Doku ausstehend", tone: "warn", go: () => setView("doku") },
        ];
      }
      return [
        { v: offen.length, l: "Offen", tone: offen.length > 0 ? "accent" : "", go: () => setView("anfragen") },
        { v: freigegeben.length, l: "Freigegeben", tone: "good", go: () => setView("anfragen") },
        { v: (window.BEWOHNER || []).length, l: "Bewohner", tone: "", go: () => setView("bewohner") },
      ];
    })();

    const qas = (() => {
      if (role === "arzt") return [
        { icon: "check", l: "Freigabe", s: "Rezepte signieren", go: () => setView("freigabe") },
        { icon: "stethoscope", l: "Visite-Tour", s: "Diktat · Schritt für Schritt", go: () => window.dispatchEvent(new CustomEvent("medlink:startVisite")) },
        { icon: "users", l: "Patient:in", s: "Akte suchen", go: () => setView("bewohner") },
        { icon: "mic", l: "Sprach-Notiz", s: "Befund diktieren", go: () => window.dispatchEvent(new CustomEvent("medlink:openvoice")) },
      ];
      if (role === "apotheke") return [
        { icon: "inbox", l: "eRezept-Eingang", s: "Neue Rezepte prüfen", go: () => setView("eingang") },
        { icon: "pill", l: "Kommissionieren", s: "Etikett & Prüfung", go: () => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Kommissionierung geöffnet" })) },
        { icon: "truck", l: "Lieferungen", s: "Touren & Quittung", go: () => setView("ausgang") },
        { icon: "file", l: "Archiv", s: "Historie & Export", go: () => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Archiv geöffnet" })) },
      ];
      if (role === "therapeut") return [
        { icon: "clock", l: "Nächster Termin", s: "14:30 · Haus Rosenhof", go: () => setView("termine") },
        { icon: "edit", l: "Doku", s: "Sitzungsnotiz erfassen", go: () => setView("doku") },
        { icon: "users", l: "Patient:innen", s: "Therapie-Verlauf", go: () => setView("bewohner") },
        { icon: "mic", l: "Sprach-Notiz", s: "Diktat aufnehmen", go: () => window.dispatchEvent(new CustomEvent("medlink:openvoice")) },
      ];
      return [
        { icon: "plus", l: "Neue Anfrage", s: "Medikation · Visite", go: () => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Neue Anfrage wird geöffnet" })) },
        { icon: "stethoscope", l: "Visite-Tour", s: "Diktat · Schritt für Schritt", go: () => window.dispatchEvent(new CustomEvent("medlink:startVisite")) },
        { icon: "users", l: "Bewohner:in", s: "Akte & Medikation", go: () => setView("bewohner") },
        { icon: "phone", l: "Kontakte", s: "Arzt · Apotheke · Notruf", go: () => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Kontakte geöffnet" })) },
      ];
    })();

    const feedItems = useMemo(() => {
      const items = [];
      const latest = [...all].slice(0, 8);
      // Human-readable names first; the DB-id (ANF-…) goes to the meta line in small type.
      for (const a of latest) {
        const who = bewName(a.bewohner);
        if (a.status === "freigegeben" && a.freigabeZeit) {
          items.push({ kind: "approve", icon: "check", title: `${a.med} freigegeben`, sub: `${who} · ${a.id}`, time: a.freigabeZeit.split(" · ")[1] || "heute" });
        } else if (a.status === "abgelehnt") {
          items.push({ kind: "alert", icon: "warning", title: `Rückfrage zu ${a.med}`, sub: `${who} · ${a.ablehnungsgrund || "Arzt bittet um Rücksprache"}`, time: a.abgelehntZeit?.split(" · ")[1] || "heute" });
        } else if (a.status === "geliefert") {
          items.push({ kind: "delivery", icon: "truck", title: `${a.med} geliefert`, sub: `${who} · ${heimName(a.heim)}`, time: a.geliefertZeit?.split(" · ")[1] || "heute" });
        } else {
          items.push({ kind: "new", icon: "inbox", title: `Neue Anfrage: ${a.med}`, sub: `${who} · ${statusLabel(a.status)}`, time: a.erstellt || "gerade" });
        }
      }
      return items.slice(0, 5);
    }, [all.length, window.STORE?._version]);

    return (
      <div className="ml-stagger">
        {/* Heim-Chips: schnelles Umschalten zwischen Häusern */}
        {window.HeimTabs && role !== "apotheke" && (
          <HeimTabs variant="mobile" currentHeim={heim} />
        )}

        {/* Hero card */}
        <div className="ml-hero" onClick={() => setView(hero.target)} role="button">
          <div className="ml-hero-eyebrow">
            <Icon name={hero.icon} size={11} /> {hero.eyebrow}
          </div>
          <div className="ml-hero-head">
            <span className="ml-hero-num">{hero.num}</span>{hero.head}
          </div>
          <div className="ml-hero-meta">{hero.meta}</div>
          <button className="ml-hero-cta" onClick={(e) => { e.stopPropagation(); setView(hero.target); }}>
            {hero.action} <Icon name="arrowRight" size={12} />
          </button>
        </div>

        {/* KPI strip */}
        <div className="ml-kpis">
          {kpis.map((k, i) => (
            <div
              className={`ml-kpi ${k.go ? "ml-kpi-clickable" : ""}`}
              key={i}
              onClick={k.go}
              role={k.go ? "button" : undefined}
              tabIndex={k.go ? 0 : undefined}
            >
              <div className="ml-kpi-v" data-tone={k.tone}>{k.v}</div>
              <div className="ml-kpi-l">
                {k.l}
                {k.go && <Icon name="chevronRight" size={10} className="ml-kpi-chev" />}
              </div>
            </div>
          ))}
        </div>

        {/* Quick actions */}
        <div className="ml-sec-head">
          <div className="ml-sec-title">Schnellzugriff</div>
        </div>
        <div className="ml-quick">
          {qas.map((q, i) => (
            <button key={i} className="ml-quick-btn" onClick={q.go}>
              <div className="ml-quick-ico"><Icon name={q.icon} size={16} /></div>
              <div>
                <div className="ml-quick-lbl">{q.l}</div>
                <div className="ml-quick-sub">{q.s}</div>
              </div>
            </button>
          ))}
        </div>

        {/* Activity feed */}
        {feedItems.length > 0 && (
          <>
            <div className="ml-sec-head">
              <div className="ml-sec-title">Aktivität <em>· live</em></div>
              <button className="ml-sec-action" onClick={() => setView(role === "arzt" ? "freigabe" : role === "apotheke" ? "eingang" : "anfragen")}>
                Alle <Icon name="chevronRight" size={11} />
              </button>
            </div>
            <div className="ml-card ml-feed">
              {feedItems.map((it, i) => (
                <div key={i} className="ml-feed-item">
                  <div className="ml-feed-dot" data-k={it.kind}><Icon name={it.icon} size={13} /></div>
                  <div className="ml-feed-body">
                    <div className="ml-feed-title">{it.title}</div>
                    <div className="ml-feed-sub">{it.sub}</div>
                    <div className="ml-feed-time">{it.time}</div>
                  </div>
                </div>
              ))}
            </div>
          </>
        )}

        <div className="ml-footnote">
          <strong>MedLink Mobile Light</strong> · Datenhaltung in DE · DSGVO-konform
        </div>
      </div>
    );
  };

  // ------------------- Anfragen-Liste -------------------
  const MLAnfragen = ({ role, heim, title, defaultFilter }) => {
    window.useStoreVersion();
    const [filter, setFilter] = useState(defaultFilter || "offen");
    const all = window.ANFRAGEN || [];

    const scoped = useMemo(() => {
      let list = all.slice();
      if (role === "heim" && heim !== "alle") list = list.filter((a) => a.heim === heim);
      return list;
    }, [all, role, heim, window.STORE?._version]);

    const filtered = useMemo(() => {
      if (filter === "all") return scoped;
      return scoped.filter((a) => a.status === filter);
    }, [scoped, filter]);

    const counts = useMemo(() => ({
      offen: scoped.filter((a) => a.status === "offen").length,
      freigegeben: scoped.filter((a) => a.status === "freigegeben").length,
      abgelehnt: scoped.filter((a) => a.status === "abgelehnt").length,
      geliefert: scoped.filter((a) => a.status === "geliefert").length,
      all: scoped.length,
    }), [scoped]);

    return (
      <div>
        {title && (
          <div className="ml-sec-head">
            <div className="ml-sec-title">{title} <em>· {filtered.length}</em></div>
          </div>
        )}
        <div className="ml-chips">
          {[
            { id: "offen", l: "Offen" },
            { id: "freigegeben", l: "Freigegeben" },
            { id: "abgelehnt", l: "Abgelehnt" },
            { id: "geliefert", l: "Geliefert" },
            { id: "all", l: "Alle" },
          ].map((c) => (
            <button
              key={c.id}
              className="ml-chip"
              data-active={filter === c.id}
              onClick={() => setFilter(c.id)}
            >
              {c.l}<span className="ml-chip-count">{counts[c.id] || 0}</span>
            </button>
          ))}
        </div>
        {filtered.length === 0 ? (
          <div className="ml-empty">
            <div className="ml-empty-ico"><Icon name="inbox" size={20} /></div>
            <div className="ml-empty-head">Keine Einträge</div>
            <div className="ml-empty-sub">In dieser Ansicht gibt es aktuell nichts zu tun. Neue Vorgänge erscheinen hier automatisch.</div>
          </div>
        ) : (
          <div className="ml-stagger">
            {filtered.slice(0, 40).map((a) => (
              <MLAnfrageCard key={a.id} a={a} role={role} />
            ))}
          </div>
        )}
      </div>
    );
  };

  // Swipe-Wrapper mit Touch-Gesten. Reveal-Threshold 72px, Commit-Threshold 120px.
  const MLSwipeCard = ({ children, onLeft, onRight, leftLabel, rightLabel, leftIcon, rightIcon }) => {
    const wrapRef = useRef(null);
    const cardRef = useRef(null);
    const state = useRef({ x0: 0, y0: 0, dx: 0, active: false });

    const hasLeft = !!onLeft;
    const hasRight = !!onRight;
    if (!hasLeft && !hasRight) return <div className="ml-swipe-wrap">{children}</div>;

    const onTouchStart = (e) => {
      if (!e.touches || e.touches.length !== 1) return;
      const t = e.touches[0];
      state.current = { x0: t.clientX, y0: t.clientY, dx: 0, active: true, decided: false, horiz: false };
      wrapRef.current?.setAttribute("data-swiping", "true");
    };

    const onTouchMove = (e) => {
      if (!state.current.active || !e.touches || e.touches.length !== 1) return;
      const t = e.touches[0];
      const dx = t.clientX - state.current.x0;
      const dy = t.clientY - state.current.y0;
      if (!state.current.decided) {
        // Entscheiden, ob horizontal oder vertikal gescrollt wird.
        if (Math.abs(dx) < 6 && Math.abs(dy) < 6) return;
        state.current.horiz = Math.abs(dx) > Math.abs(dy);
        state.current.decided = true;
        if (!state.current.horiz) {
          state.current.active = false;
          wrapRef.current?.removeAttribute("data-swiping");
          return;
        }
      }
      // Horizontal — Richtung erlauben?
      let useDx = dx;
      if (useDx > 0 && !hasRight) useDx = 0;
      if (useDx < 0 && !hasLeft) useDx = 0;
      // Dämpfung über 120px
      const MAX = 160;
      if (Math.abs(useDx) > 120) {
        const over = Math.abs(useDx) - 120;
        useDx = Math.sign(useDx) * (120 + Math.min(40, over * 0.3));
      }
      useDx = Math.max(-MAX, Math.min(MAX, useDx));
      state.current.dx = useDx;
      if (cardRef.current) cardRef.current.style.transform = `translateX(${useDx}px)`;
    };

    const onTouchEnd = () => {
      if (!state.current.active) return;
      const dx = state.current.dx;
      const card = cardRef.current;
      wrapRef.current?.removeAttribute("data-swiping");
      state.current.active = false;
      const COMMIT = 96;
      if (card) {
        card.style.transform = "translateX(0)";
      }
      if (dx > COMMIT && hasRight) {
        setTimeout(() => onRight(), 80);
      } else if (dx < -COMMIT && hasLeft) {
        setTimeout(() => onLeft(), 80);
      }
    };

    const onTouchCancel = onTouchEnd;

    return (
      <div
        ref={wrapRef}
        className="ml-swipe-wrap"
        onTouchStart={onTouchStart}
        onTouchMove={onTouchMove}
        onTouchEnd={onTouchEnd}
        onTouchCancel={onTouchCancel}
      >
        <div className="ml-swipe-actions">
          {hasRight && (
            <div className="ml-swipe-act" data-side="right">
              <Icon name={rightIcon || "check"} size={18} />
              <span>{rightLabel || "Freigeben"}</span>
            </div>
          )}
          {hasLeft && (
            <div className="ml-swipe-act" data-side="left" style={{ marginLeft: "auto" }}>
              <Icon name={leftIcon || "x"} size={18} />
              <span>{leftLabel || "Ablehnen"}</span>
            </div>
          )}
        </div>
        <div ref={cardRef} style={{ position: "relative" }}>
          {children}
        </div>
      </div>
    );
  };

  const MLAnfrageCard = ({ a, role }) => {
    const stop = (fn) => (e) => { e.stopPropagation(); fn(); };
    const openBewohner = () => window.dispatchEvent(new CustomEvent("medlink:openBewohner", { detail: { id: a.bewohner } }));

    const statLbl = { offen: "Offen", freigegeben: "Freigegeben", abgelehnt: "Abgelehnt", geliefert: "Geliefert" }[a.status] || a.status;

    // Swipe-Aktionen pro Rolle/Status
    let swipeLeft = null, swipeRight = null, leftLbl = "", rightLbl = "", leftIco = "x", rightIco = "check";
    if (role === "arzt" && a.status === "offen") {
      swipeRight = () => window.STORE.approveAnfrage(a.id);
      swipeLeft = () => window.STORE.rejectAnfrage(a.id, "Rückfrage an Pflege");
      rightLbl = "Freigeben"; leftLbl = "Ablehnen";
    } else if (role === "apotheke" && a.status === "freigegeben" && !a.kommissioniert) {
      swipeRight = () => window.STORE.kommAnfrage(a.id);
      rightLbl = "Kommissionieren"; rightIco = "pill";
    } else if (role === "apotheke" && a.kommissioniert && a.status !== "geliefert") {
      swipeRight = () => window.STORE.deliverAnfrage(a.id);
      rightLbl = "Geliefert"; rightIco = "truck";
    }

    const card = (
      <div className="ml-card ml-anfrage" data-prio={a.prio} data-tappable="true" onClick={openBewohner}>
        <div className="ml-anfrage-pri" />
        <div className="ml-anfrage-body">
          <div className="ml-anfrage-head">
            <span className="ml-anfrage-id">{a.id} · {heimShort(a.heim)}</span>
            <span className="ml-anfrage-stat" data-s={a.status}>{statLbl}</span>
          </div>
          <div className="ml-anfrage-med">{a.med}</div>
          <div className="ml-anfrage-bew">
            {bewName(a.bewohner)}{a.dosis ? ` · ${a.dosis}` : ""}{a.typ ? ` · ${a.typ}` : ""}
          </div>

          {/* Role-specific actions */}
          {role === "arzt" && a.status === "offen" && (
            <div className="ml-anfrage-foot">
              <button className="ml-btn-secondary" onClick={stop(() => window.STORE.rejectAnfrage(a.id, "Rückfrage an Pflege"))}>
                Ablehnen
              </button>
              <button className="ml-btn-primary" onClick={stop(() => window.STORE.approveAnfrage(a.id))}>
                <Icon name="check" size={13} /> Freigeben
              </button>
            </div>
          )}
          {role === "apotheke" && a.status === "freigegeben" && !a.kommissioniert && (
            <div className="ml-anfrage-foot">
              <button className="ml-btn-secondary" onClick={stop(openBewohner)}>Details</button>
              <button className="ml-btn-primary" onClick={stop(() => window.STORE.kommAnfrage(a.id))}>
                <Icon name="pill" size={13} /> Kommissionieren
              </button>
            </div>
          )}
          {role === "apotheke" && a.kommissioniert && a.status !== "geliefert" && (
            <div className="ml-anfrage-foot">
              <button className="ml-btn-primary" onClick={stop(() => window.STORE.deliverAnfrage(a.id))}>
                <Icon name="truck" size={13} /> Als geliefert quittieren
              </button>
            </div>
          )}
          {role === "heim" && a.status === "offen" && (
            <div className="ml-anfrage-foot">
              <button className="ml-btn-secondary" onClick={stop(openBewohner)}>Akte öffnen</button>
              <button className="ml-btn-ghost" onClick={stop(() => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "An Arzt erinnert" })))}>
                <Icon name="bell" size={12} /> Erinnern
              </button>
            </div>
          )}
        </div>
      </div>
    );

    if (!swipeLeft && !swipeRight) return card;
    return (
      <MLSwipeCard
        onLeft={swipeLeft}
        onRight={swipeRight}
        leftLabel={leftLbl}
        rightLabel={rightLbl}
        leftIcon={leftIco}
        rightIcon={rightIco}
      >
        {card}
      </MLSwipeCard>
    );
  };

  // ------------------- Bewohner-Liste -------------------
  const MLBewohner = ({ heim, role }) => {
    const [q, setQ] = useState("");
    const all = window.BEWOHNER || [];

    const list = useMemo(() => {
      let l = all;
      if (heim !== "alle" && role === "heim") l = l.filter((b) => b.heim === heim);
      if (q.trim()) {
        const Q = q.toLowerCase();
        l = l.filter(
          (b) =>
            b.name.toLowerCase().includes(Q) ||
            b.id.toLowerCase().includes(Q) ||
            (b.zimmer || "").toLowerCase().includes(Q)
        );
      }
      return l;
    }, [q, heim, role, all.length]);

    const grouped = useMemo(() => {
      const g = {};
      list.forEach((b) => {
        const last = b.name.split(",")[0] || b.name;
        const key = (last[0] || "?").toUpperCase();
        if (!g[key]) g[key] = [];
        g[key].push(b);
      });
      return Object.keys(g).sort().map((k) => ({ letter: k, items: g[k] }));
    }, [list]);

    return (
      <div>
        <div className="ml-search">
          <Icon name="search" size={14} />
          <input
            value={q}
            onChange={(e) => setQ(e.target.value)}
            placeholder={role === "arzt" ? "Patient:in, Zimmer, ID …" : "Bewohner:in, Zimmer, ID …"}
          />
          {q && (
            <button
              style={{ border: 0, background: "transparent", color: "var(--fg-subtle)", cursor: "pointer", padding: 4 }}
              onClick={() => setQ("")}
              aria-label="Suche leeren"
            >
              <Icon name="x" size={12} />
            </button>
          )}
        </div>

        {list.length === 0 ? (
          <div className="ml-empty">
            <div className="ml-empty-ico"><Icon name="users" size={20} /></div>
            <div className="ml-empty-head">Keine Treffer</div>
            <div className="ml-empty-sub">Versuchen Sie einen anderen Namen oder eine andere Zimmernummer.</div>
          </div>
        ) : (
          grouped.map((grp) => (
            <div key={grp.letter}>
              <div className="ml-group-label">{grp.letter}</div>
              {grp.items.map((b) => (
                <div
                  key={b.id}
                  className="ml-row"
                  onClick={() => window.dispatchEvent(new CustomEvent("medlink:openBewohner", { detail: { id: b.id } }))}
                  role="button"
                >
                  <div className="ml-avatar">{(b.name.split(",")[0] || "?").trim()[0]}</div>
                  <div className="ml-row-body">
                    <div className="ml-row-name">{b.name}</div>
                    <div className="ml-row-sub">
                      Zi. {b.zimmer || "—"} · {heimName(b.heim)} · PG {b.pflegegrad || "—"}
                      {b.offen > 0 && ` · ${b.offen} offen`}
                    </div>
                  </div>
                  <Icon name="chevronRight" size={14} className="ml-row-chev" />
                </div>
              ))}
            </div>
          ))
        )}
        <div style={{ height: 12 }} />
      </div>
    );
  };

  // ------------------- Termine (Therapeut) -------------------
  const MLTermine = () => {
    // Static prototype data — matches existing TherapeutPortal style
    const termine = [
      { t: "09:00", p: "Frau Kühn",       typ: "Physio", heim: "ros", dauer: "30 Min", done: true },
      { t: "10:30", p: "Herr Becker",     typ: "Ergo",   heim: "ros", dauer: "45 Min", done: true },
      { t: "11:15", p: "Frau Roth",       typ: "Logo",   heim: "lin", dauer: "30 Min", done: true },
      { t: "14:30", p: "Frau Peters",     typ: "Physio", heim: "ros", dauer: "45 Min", next: true },
      { t: "16:00", p: "Herr Jansen",     typ: "Physio", heim: "mar", dauer: "30 Min" },
      { t: "17:15", p: "Frau Lenz",       typ: "Ergo",   heim: "mar", dauer: "45 Min" },
    ];
    const done = termine.filter((t) => t.done).length;
    const total = termine.length;

    return (
      <div className="ml-stagger" style={{ paddingBottom: 96 }}>
        <div className="ml-hero" style={{ paddingBottom: 14 }}>
          <div className="ml-hero-eyebrow"><Icon name="calendar" size={11} /> Heute · {fmtDate()}</div>
          <div className="ml-hero-head">
            <span className="ml-hero-num">{done}</span>
            <span style={{ color: "var(--fg-muted)", fontStyle: "normal" }}> / {total}</span> erledigt
          </div>
          <div className="ml-hero-meta">Nächster Termin um 14:30 · Frau Peters · Haus Rosenhof</div>
        </div>

        <div className="ml-sec-head">
          <div className="ml-sec-title">Heute</div>
        </div>
        {termine.map((tm, i) => (
          <div key={i} className={`ml-card ml-termin ${tm.done ? "is-done" : ""} ${tm.next ? "is-next" : ""}`} data-tappable="true">
            <div className="ml-termin-time" data-next={tm.next}>{tm.t}</div>
            <div className="ml-termin-body">
              <div className="ml-termin-name">{tm.p}</div>
              <div className="ml-termin-sub">{heimName(tm.heim)} · {tm.dauer}</div>
            </div>
            <div className="ml-termin-type">
              {tm.done && <Icon name="check" size={11} className="ml-termin-done" title="erledigt" />}
              {tm.next && <span className="ml-termin-pulse" title="jetzt" />}
              {tm.typ}
            </div>
          </div>
        ))}
      </div>
    );
  };

  // ------------------- Doku (Therapeut) -------------------
  const MLDoku = () => {
    const rows = [
      { p: "Frau Kühn",   t: "Physio-Sitzung · 21.04. 09:00", status: "offen" },
      { p: "Herr Becker", t: "Ergo-Sitzung · 21.04. 10:30",   status: "offen" },
      { p: "Frau Roth",   t: "Logo-Sitzung · 20.04. 16:00",   status: "entwurf" },
      { p: "Frau Peters", t: "Physio-Sitzung · 20.04. 14:30", status: "freigegeben" },
    ];
    const offen = rows.filter((r) => r.status !== "freigegeben").length;

    return (
      <div className="ml-stagger">
        <div className="ml-sec-head">
          <div className="ml-sec-title">Offene Dokumentation <em>· {offen}</em></div>
        </div>
        {rows.map((r, i) => (
          <div key={i} className="ml-card" style={{ padding: 14, marginBottom: 8 }} data-tappable="true">
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }}>
              <div style={{ fontSize: 14, fontWeight: 600, letterSpacing: "-0.008em" }}>{r.p}</div>
              <span className="ml-anfrage-stat" data-s={r.status}>{statusLabel(r.status)}</span>
            </div>
            <div style={{ fontSize: 12, color: "var(--fg-subtle)", marginTop: 4 }}>{r.t}</div>
            {r.status !== "freigegeben" && (
              <div style={{ marginTop: 12, display: "flex", gap: 6 }}>
                <button className="ml-btn-secondary" onClick={() => window.dispatchEvent(new CustomEvent("medlink:openvoice"))}>
                  <Icon name="mic" size={12} /> Diktat
                </button>
                <button className="ml-btn-primary" onClick={() => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Entwurf gespeichert" }))}>
                  <Icon name="edit" size={12} /> Erfassen
                </button>
              </div>
            )}
          </div>
        ))}
      </div>
    );
  };

  // ------------------- Push-Benachrichtigungs-Item -------------------
  const MLPushItem = () => {
    const [perm, setPerm] = useState(() => window.MedlinkPush?.permission?.() || "default");
    const [queueSize, setQueueSize] = useState(() => {
      try { return JSON.parse(localStorage.getItem("medlink.offline.queue") || "[]").length; }
      catch { return 0; }
    });
    useEffect(() => {
      const onQ = (e) => setQueueSize(e.detail?.size || 0);
      window.addEventListener("medlink:queue-change", onQ);
      return () => window.removeEventListener("medlink:queue-change", onQ);
    }, []);
    const supported = window.MedlinkPush?.supported?.();
    if (!supported) {
      return (
        <div className="ml-menu-item" style={{ opacity: 0.5 }}>
          <div className="ml-menu-ico"><Icon name="bell" size={14} /></div>
          <div className="ml-menu-lbl">Erinnerungen</div>
          <div className="ml-menu-val">In diesem Browser nicht verfügbar</div>
        </div>
      );
    }
    if (perm === "granted") {
      return (
        <div className="ml-menu-item" onClick={async () => {
          await window.MedlinkPush.notify("MedLink · Test", "Erinnerungen funktionieren.", "/");
        }}>
          <div className="ml-menu-ico" style={{ background: "var(--success-tint)", color: "var(--success)", borderColor: "var(--success-border)" }}>
            <Icon name="check" size={14} />
          </div>
          <div className="ml-menu-lbl">Erinnerungen am Handy</div>
          <div className="ml-menu-val">Aktiv</div>
          <Icon name="chevronRight" size={14} className="ml-menu-chev" />
        </div>
      );
    }
    if (perm === "denied") {
      return (
        <div className="ml-menu-item" onClick={() => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Bitte in den Browser-Einstellungen Benachrichtigungen für diese Seite erlauben." }))}>
          <div className="ml-menu-ico"><Icon name="bell" size={14} /></div>
          <div className="ml-menu-lbl">Erinnerungen erlauben</div>
          <div className="ml-menu-val">Browser-Einstellungen öffnen</div>
          <Icon name="chevronRight" size={14} className="ml-menu-chev" />
        </div>
      );
    }
    return (
      <div className="ml-menu-item" onClick={async () => {
        const res = await window.MedlinkPush.requestPermission();
        setPerm(res);
        if (res === "granted") {
          setTimeout(() => window.MedlinkPush.notify("MedLink", "Erinnerungen aktiviert."), 400);
        }
      }}>
        <div className="ml-menu-ico"><Icon name="bell" size={14} /></div>
        <div className="ml-menu-lbl">Erinnerungen einschalten</div>
        <div className="ml-menu-val">Einmaliger Tipp</div>
        <Icon name="chevronRight" size={14} className="ml-menu-chev" />
      </div>
    );
  };

  // ------------------- Install-Banner -------------------
  const MLInstall = () => {
    const [prompt, setPrompt] = useState(() => window.__medlinkInstallPrompt || null);
    const [installed, setInstalled] = useState(() => !!window.__medlinkIsStandalone);
    const [showIOS, setShowIOS] = useState(false);
    const isIOS = !!window.__medlinkIsIOS;

    useEffect(() => {
      const onReady = () => setPrompt(window.__medlinkInstallPrompt);
      const onInstalled = () => { setPrompt(null); setInstalled(true); };
      window.addEventListener("medlink:installable", onReady);
      window.addEventListener("medlink:installed", onInstalled);
      return () => {
        window.removeEventListener("medlink:installable", onReady);
        window.removeEventListener("medlink:installed", onInstalled);
      };
    }, []);

    if (installed) {
      return (
        <div className="ml-install ml-install-ok">
          <div className="ml-install-ico"><Icon name="check" size={16} /></div>
          <div>
            <div className="ml-install-head">Als App installiert</div>
            <div className="ml-install-sub">MedLink öffnet direkt vom Startbildschirm.</div>
          </div>
        </div>
      );
    }

    if (isIOS) {
      return (
        <>
          <div className="ml-install" onClick={() => setShowIOS((v) => !v)}>
            <div className="ml-install-ico"><Icon name="plus" size={16} /></div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div className="ml-install-head">Als App installieren</div>
              <div className="ml-install-sub">Kein App-Store nötig · direkt vom Startbildschirm öffnen</div>
            </div>
            <Icon name="chevronRight" size={14} style={{ color: "var(--fg-faint)", transform: showIOS ? "rotate(90deg)" : "none", transition: "transform .2s ease" }} />
          </div>
          {showIOS && (
            <div className="ml-install-ios">
              <ol>
                <li>Tippen Sie auf <strong>Teilen</strong> <span className="ml-kbd">⬆︎</span> in der Safari-Leiste.</li>
                <li>Wählen Sie <strong>Zum Home-Bildschirm</strong>.</li>
                <li>Tippen Sie auf <strong>Hinzufügen</strong> — fertig.</li>
              </ol>
              <div className="ml-install-note">
                MedLink öffnet sich danach im Vollbild, ohne Safari-Leiste, mit Offline-Fähigkeit.
              </div>
            </div>
          )}
        </>
      );
    }

    if (prompt) {
      return (
        <div
          className="ml-install"
          onClick={async () => {
            prompt.prompt();
            const { outcome } = await prompt.userChoice;
            if (outcome === "accepted") setInstalled(true);
            setPrompt(null);
          }}
        >
          <div className="ml-install-ico"><Icon name="plus" size={16} /></div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="ml-install-head">MedLink installieren</div>
            <div className="ml-install-sub">Als App auf diesem Gerät · offline nutzbar</div>
          </div>
          <Icon name="chevronRight" size={14} style={{ color: "var(--fg-faint)" }} />
        </div>
      );
    }

    return null;
  };

  // ------------------- Mehr (Settings) -------------------
  const MLMehr = ({ role, heim, ctx, lightMode, setLightMode, theme, setTheme }) => {
    const name = userNameFor(role);
    return (
      <div className="ml-stagger">
        <div className="ml-profile">
          <div className="ml-profile-avatar">{initialsOf(name)}</div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="ml-profile-name">{name}</div>
            <div className="ml-profile-role">{roleLabelFor(role)}</div>
            <div className="ml-profile-badge"><Icon name="shield" size={10} /> Extra-Schutz aktiv</div>
          </div>
          <button className="ml-logout-btn" onClick={ctx.onLogout} aria-label="Abmelden" title="Abmelden">
            <Icon name="back" size={14} /> Abmelden
          </button>
        </div>

        <MLInstall />

        <div className="ml-sec-head"><div className="ml-sec-title">Portal</div></div>
        <div className="ml-menu-group">
          <div className="ml-menu-item" onClick={ctx.onPortalSwitch}>
            <div className="ml-menu-ico"><Icon name="swap" size={14} /></div>
            <div className="ml-menu-lbl">Rolle wechseln</div>
            <div className="ml-menu-val">{roleTitleFor(role)}</div>
            <Icon name="chevronRight" size={14} className="ml-menu-chev" />
          </div>
          <div className="ml-menu-item" onClick={ctx.onHeimSwitch}>
            <div className="ml-menu-ico"><Icon name="home" size={14} /></div>
            <div className="ml-menu-lbl">Heim</div>
            <div className="ml-menu-val">{heimName(heim)}</div>
            <Icon name="chevronRight" size={14} className="ml-menu-chev" />
          </div>
        </div>

        <div className="ml-sec-head"><div className="ml-sec-title">Darstellung</div></div>
        <div className="ml-menu-group">
          <div className="ml-menu-item ml-menu-item-static">
            <div className="ml-menu-ico"><Icon name={theme === "dark" ? "moon" : "sun"} size={14} /></div>
            <div className="ml-menu-lbl">Design</div>
            <div className="ml-seg" role="tablist" aria-label="Design">
              {[["light", "Hell"], ["dark", "Dunkel"], ["auto", "Auto"]].map(([v, l]) => (
                <button
                  key={v}
                  type="button"
                  role="tab"
                  aria-selected={theme === v}
                  className="ml-seg-btn"
                  data-active={theme === v}
                  onClick={(e) => { e.stopPropagation(); setTheme(v); }}
                >
                  {l}
                </button>
              ))}
            </div>
          </div>
        </div>

        <div className="ml-sec-head"><div className="ml-sec-title">Werkzeuge</div></div>
        <div className="ml-menu-group">
          <MLPushItem />
          <div className="ml-menu-item" onClick={() => window.dispatchEvent(new CustomEvent("medlink:openvoice"))}>
            <div className="ml-menu-ico"><Icon name="mic" size={14} /></div>
            <div className="ml-menu-lbl">Sprach-Notiz</div>
            <Icon name="chevronRight" size={14} className="ml-menu-chev" />
          </div>
          <div className="ml-menu-item" onClick={() => window.dispatchEvent(new CustomEvent("medlink:search"))}>
            <div className="ml-menu-ico"><Icon name="search" size={14} /></div>
            <div className="ml-menu-lbl">Globale Suche</div>
            <Icon name="chevronRight" size={14} className="ml-menu-chev" />
          </div>
          <div className="ml-menu-item" onClick={() => window.dispatchEvent(new CustomEvent("medlink:bell"))}>
            <div className="ml-menu-ico"><Icon name="bell" size={14} /></div>
            <div className="ml-menu-lbl">Benachrichtigungen</div>
            <Icon name="chevronRight" size={14} className="ml-menu-chev" />
          </div>
        </div>

        <div className="ml-sec-head"><div className="ml-sec-title">Konto</div></div>
        <div className="ml-menu-group">
          <div className="ml-menu-item" onClick={() => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Datenschutz-Cockpit geöffnet" }))}>
            <div className="ml-menu-ico"><Icon name="shield" size={14} /></div>
            <div className="ml-menu-lbl">Datenschutz & Audit</div>
            <div className="ml-menu-val">DSGVO</div>
            <Icon name="chevronRight" size={14} className="ml-menu-chev" />
          </div>
          <div className="ml-menu-item" onClick={() => window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Support-Kanal geöffnet" }))}>
            <div className="ml-menu-ico"><Icon name="phone" size={14} /></div>
            <div className="ml-menu-lbl">Support</div>
            <Icon name="chevronRight" size={14} className="ml-menu-chev" />
          </div>
          <div className="ml-menu-item" onClick={ctx.onLogout}>
            <div className="ml-menu-ico"><Icon name="back" size={14} /></div>
            <div className="ml-menu-lbl" style={{ color: "var(--danger)" }}>Abmelden</div>
          </div>
        </div>

        <div className="ml-footnote">
          <strong>MedLink</strong> · Mobile Light v0.9 · Daten in Deutschland · BSI-Grundschutz
        </div>
      </div>
    );
  };

  // ------------------- Main Shell -------------------
  const MobileLight = ({ role, heim, ctx, lightMode, setLightMode, theme, setTheme }) => {
    const [view, setView] = useState("start");
    const [scrolled, setScrolled] = useState(false);
    const [ptr, setPtr] = useState({ active: false, spinning: false, progress: 0 });
    const viewRef = useRef(null);
    const ptrState = useRef({ y0: 0, dy: 0, active: false, horiz: false });

    window.useStoreVersion();

    // Reset to start when role changes
    useEffect(() => { setView("start"); }, [role]);

    // Scroll shadow + reset on view change
    useEffect(() => {
      const el = viewRef.current;
      if (!el) return;
      el.scrollTo({ top: 0 });
      setScrolled(false);
      const onScroll = () => setScrolled(el.scrollTop > 4);
      el.addEventListener("scroll", onScroll, { passive: true });
      return () => el.removeEventListener("scroll", onScroll);
    }, [view]);

    // Pull-to-refresh
    useEffect(() => {
      const el = viewRef.current;
      if (!el) return;
      const THRESHOLD = 72;

      const onStart = (e) => {
        if (el.scrollTop > 0 || !e.touches || e.touches.length !== 1) return;
        ptrState.current = { y0: e.touches[0].clientY, dy: 0, active: true, horiz: false, decided: false };
      };
      const onMove = (e) => {
        const s = ptrState.current;
        if (!s.active || !e.touches || e.touches.length !== 1) return;
        const dy = e.touches[0].clientY - s.y0;
        if (!s.decided) {
          if (Math.abs(dy) < 6) return;
          s.decided = true;
          if (dy < 0) { s.active = false; return; }
        }
        if (dy <= 0) { s.dy = 0; setPtr({ active: false, spinning: false, progress: 0 }); return; }
        // Dämpfung
        const raw = dy;
        const damped = raw < THRESHOLD ? raw : THRESHOLD + (raw - THRESHOLD) * 0.35;
        s.dy = damped;
        setPtr({ active: damped > 8, spinning: false, progress: Math.min(1, damped / THRESHOLD) });
      };
      const onEnd = () => {
        const s = ptrState.current;
        if (!s.active) { ptrState.current = { y0: 0, dy: 0, active: false }; return; }
        const commit = s.dy >= THRESHOLD;
        s.active = false;
        if (commit) {
          setPtr({ active: true, spinning: true, progress: 1 });
          // Simulierter Refresh — STORE bumpen und kurz warten.
          setTimeout(() => {
            try { window.STORE?.bump?.(); } catch {}
            window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Aktualisiert" }));
            setPtr({ active: false, spinning: false, progress: 0 });
          }, 700);
        } else {
          setPtr({ active: false, spinning: false, progress: 0 });
        }
      };

      el.addEventListener("touchstart", onStart, { passive: true });
      el.addEventListener("touchmove", onMove, { passive: true });
      el.addEventListener("touchend", onEnd, { passive: true });
      el.addEventListener("touchcancel", onEnd, { passive: true });
      return () => {
        el.removeEventListener("touchstart", onStart);
        el.removeEventListener("touchmove", onMove);
        el.removeEventListener("touchend", onEnd);
        el.removeEventListener("touchcancel", onEnd);
      };
    }, [view]);

    // Listen for medlink:setView from existing bridges
    useEffect(() => {
      const handler = (e) => {
        const v = e.detail?.view;
        if (!v) return;
        const map = {
          heim: { anfragen: "anfragen", bewohner: "bewohner", dashboard: "start" },
          arzt: { queue: "freigabe", heute: "start", bewohner: "bewohner" },
          apotheke: { eingang: "eingang", ausgang: "ausgang" },
          therapeut: { termine: "termine", doku: "doku" },
        };
        const mapped = map[role]?.[v] || v;
        if (TABS[role]?.some((t) => t.id === mapped)) setView(mapped);
      };
      window.addEventListener("medlink:setView", handler);
      return () => window.removeEventListener("medlink:setView", handler);
    }, [role]);

    // Guard invalid view
    const effView = useMemo(() => {
      if (view === "mehr") return "mehr";
      // Shortcut targets that aren't in the tab strip but are still valid screens
      if (view === "bewohner") return "bewohner";
      if (TABS[role]?.some((t) => t.id === view)) return view;
      return "start";
    }, [role, view]);

    const badges = useMemo(() => {
      const all = window.ANFRAGEN || [];
      const scoped = role === "heim" && heim !== "alle" ? all.filter((a) => a.heim === heim) : all;
      const offen = scoped.filter((a) => a.status === "offen").length;
      const freigegebenUnkomm = scoped.filter((a) => a.status === "freigegeben" && !a.kommissioniert).length;
      if (role === "heim") return { anfragen: offen };
      if (role === "arzt") return { freigabe: offen };
      if (role === "apotheke") return { eingang: freigegebenUnkomm };
      return {};
    }, [role, heim, window.STORE?._version]);

    const bellCount = useMemo(() => {
      const all = window.ANFRAGEN || [];
      return all.filter((a) => a.status === "offen").length;
    }, [window.STORE?._version]);

    const fabIcon = role === "heim" ? "plus" : "mic";
    const fabAction = () => {
      if (role === "heim") {
        window.dispatchEvent(new CustomEvent("medlink:toast", { detail: "Neue Anfrage wird geöffnet" }));
      } else {
        window.dispatchEvent(new CustomEvent("medlink:openvoice"));
      }
    };

    // First-run onboarding — 3 tooltips pointing at hero, quick actions, bottom tabs.
    const ONBOARD_KEY = "medlink.onboarding.ml.v1";
    const [obStep, setObStep] = useState(() => {
      try { return localStorage.getItem(ONBOARD_KEY) === "done" ? -1 : 0; }
      catch { return -1; }
    });
    const obSteps = [
      { sel: ".ml-hero",   title: "Ihre Tagesübersicht", body: "Das Wichtigste für heute — Termine, Anfragen, offene Aufgaben.", pos: "below" },
      { sel: ".ml-quick",  title: "Schnellzugriff",      body: "Die häufigsten Aktionen liegen hier. Einmal tippen, schon sind Sie drin.", pos: "below" },
      { sel: ".ml-tabs",   title: "Navigation unten",    body: "Zwischen Bereichen wechseln Sie hier — immer erreichbar mit dem Daumen.", pos: "above" },
    ];
    const completeOnboard = () => {
      try { localStorage.setItem(ONBOARD_KEY, "done"); } catch {}
      setObStep(-1);
    };
    // Only show on Start-tab so the target elements exist
    const obActive = obStep >= 0 && obStep < obSteps.length && effView === "start";

    return (
      <div className="ml-app">
        <MLTopBar
          role={role}
          scrolled={scrolled}
          bellCount={bellCount}
          onBell={() => window.dispatchEvent(new CustomEvent("medlink:bell"))}
          onAvatar={() => setView("mehr")}
        />
        <div className="ml-ptr" data-active={ptr.active} data-spinning={ptr.spinning}>
          <div
            className="ml-ptr-ring"
            style={ptr.spinning ? undefined : { transform: `rotate(${Math.round(ptr.progress * 300)}deg)` }}
          />
        </div>
        <div ref={viewRef} className="ml-view">
          {effView === "start" && <MLStart role={role} heim={heim} setView={setView} />}
          {effView === "anfragen" && <MLAnfragen role={role} heim={heim} title="Anfragen" defaultFilter="offen" />}
          {effView === "freigabe" && <MLAnfragen role={role} heim={heim} title="Warteschlange" defaultFilter="offen" />}
          {effView === "eingang" && <MLAnfragen role={role} heim={heim} title="eRezept-Eingang" defaultFilter="freigegeben" />}
          {effView === "ausgang" && <MLAnfragen role={role} heim={heim} title="Lieferungen" defaultFilter="geliefert" />}
          {effView === "bewohner" && <MLBewohner heim={heim} role={role} />}
          {effView === "termine" && <MLTermine />}
          {effView === "doku" && <MLDoku />}
          {effView === "mehr" && (
            <MLMehr
              role={role}
              heim={heim}
              ctx={ctx}
              lightMode={lightMode}
              setLightMode={setLightMode}
              theme={theme}
              setTheme={setTheme}
            />
          )}
        </div>

        {effView !== "mehr" && effView !== "start" && (
          <button className="ml-fab" onClick={fabAction} aria-label="Aktion">
            <Icon name={fabIcon} size={20} />
          </button>
        )}

        <MLBottomTabs role={role} view={effView} setView={setView} badges={badges} />

        {obActive && (
          <MLOnboardingTooltip
            step={obSteps[obStep]}
            index={obStep}
            total={obSteps.length}
            onNext={() => setObStep((s) => s + 1 >= obSteps.length ? -1 : s + 1)}
            onSkip={completeOnboard}
            onDone={completeOnboard}
          />
        )}
      </div>
    );
  };

  // First-run onboarding tooltip — positioned near a target element
  const MLOnboardingTooltip = ({ step, index, total, onNext, onSkip, onDone }) => {
    const [rect, setRect] = useState(null);
    useEffect(() => {
      const measure = () => {
        const el = document.querySelector(step.sel);
        if (!el) { setRect(null); return; }
        const r = el.getBoundingClientRect();
        setRect({ top: r.top, left: r.left, width: r.width, height: r.height, bottom: r.bottom });
        el.classList.add("ml-ob-highlight");
      };
      measure();
      const to = setTimeout(measure, 120);
      window.addEventListener("resize", measure);
      window.addEventListener("scroll", measure, true);
      return () => {
        clearTimeout(to);
        window.removeEventListener("resize", measure);
        window.removeEventListener("scroll", measure, true);
        document.querySelectorAll(".ml-ob-highlight").forEach((el) => el.classList.remove("ml-ob-highlight"));
      };
    }, [step.sel]);
    if (!rect) return null;
    const isAbove = step.pos === "above";
    const top = isAbove ? Math.max(12, rect.top - 12 - 160) : Math.min(window.innerHeight - 200, rect.bottom + 12);
    const isLast = index === total - 1;
    return (
      <>
        <div className="ml-ob-scrim" onClick={onSkip} />
        <div className="ml-ob-tip" style={{ top, left: 16, right: 16 }}>
          <div className="ml-ob-step">Schritt {index + 1} von {total}</div>
          <div className="ml-ob-title">{step.title}</div>
          <div className="ml-ob-body">{step.body}</div>
          <div className="ml-ob-actions">
            <button type="button" className="ml-ob-skip" onClick={onSkip}>Überspringen</button>
            <button type="button" className="ml-ob-next" onClick={isLast ? onDone : onNext}>
              {isLast ? "Verstanden" : "Weiter"}
            </button>
          </div>
        </div>
      </>
    );
  };

  window.MobileLight = MobileLight;
})();
