/* Leap Assistant — the surface. A persistent bubble bottom-right that auto-expands
   into a briefing panel on arrival (once per session), greets by time-of-day + name,
   and shows the ranked action cards from window.LeapDigest. Each card deep-links into
   the app. (Phase 1 deterministic, Phase 2 LLM-narrated.)

   Phase 3 adds the conversational copilot + support: when the server reports the LLM
   is configured (live only), the footer becomes a real "Ask Leap" chat. The copilot
   answers from a curated knowledge base (with citations) and can PROPOSE writes (e.g.
   a draft post) that the user confirms inline before anything is created —
   "agentic with confirmation". Graceful: no key → the footer stays a "Soon" teaser. */
function Assistant({ onNav }) {
  const S = window.CroprStore;
  const A = window.LeapAPI;
  const [open, setOpen] = React.useState(false);
  const [expanded, setExpanded] = React.useState(false); // show all cards vs top 5
  const [enhanced, setEnhanced] = React.useState(null);  // Phase 2: LLM-composed briefing (live only)
  const enhReq = React.useRef(0);
  const [, force] = React.useReducer((n) => n + 1, 0);

  // Phase 3 copilot state.
  const [copilotOn, setCopilotOn] = React.useState(null); // null unknown · true · false
  const [mode, setMode] = React.useState("briefing");     // "briefing" | "chat"
  const [thread, setThread] = React.useState([]);          // {role, content, citations?, pendingActions?, error?}
  const [draft, setDraft] = React.useState("");
  const [sending, setSending] = React.useState(false);
  const chatReq = React.useRef(0);
  const threadEndRef = React.useRef(null);

  // Weekly plan & wrap (a second briefing cadence).
  const [panelTab, setPanelTab] = React.useState("today"); // "today" | "week"
  const [weekMode, setWeekMode] = React.useState(() => (window.LeapDigest && window.LeapDigest.defaultWeeklyMode ? window.LeapDigest.defaultWeeklyMode() : "plan")); // "plan" | "wrap"
  const [weekEnh, setWeekEnh] = React.useState(null); // LLM-narrated weekly (live), tagged by mode
  const weekReq = React.useRef(0);

  // Per-user preferences (Phase 4 — learning).
  const [prefsOpen, setPrefsOpen] = React.useState(false);
  const [prefs, setPrefs] = React.useState(null); // { tone, length, notes }
  const [prefsSaving, setPrefsSaving] = React.useState(false);

  const SEEN_KEY = "leap_assistant_seen"; // per-session: auto-open once
  const ACK_KEY = "leap_assistant_ack_fp"; // cross-session: the digest state the user last saw

  const live = () => Boolean(window.LEAP && window.LEAP.live && A && A.assistant);
  const resolveOrgId = () => {
    try { const c = S.getActiveClient && S.getActiveClient(); return (c && c._orgId) ? c._orgId : (window.LeapGate && window.LeapGate.orgId ? window.LeapGate.orgId() : null); }
    catch (e) { return null; }
  };
  // Engagement telemetry (Phase 5) — best-effort, live-only (no-op offline).
  const track = (kind, topic) => { try { window.LeapAnalytics && window.LeapAnalytics.track(kind, topic); } catch (e) { /* */ } };

  // Progressive enhancement: render the deterministic digest instantly, then ask
  // the server (which holds the Claude key) to narrate it. Live mode only; on any
  // failure or when the LLM isn't configured, we simply keep the deterministic copy.
  React.useEffect(() => {
    if (!open) { setEnhanced(null); return; }
    if (!live() || !window.LeapDigest) return;
    const orgId = resolveOrgId();
    if (!orgId) return;
    const d = window.LeapDigest.build();
    const payload = {
      digest: { greeting: d.greeting, scope: d.scope, cards: d.cards.map((c) => ({ topic: c.topic, title: c.title, detail: c.detail, assist: c.assist })) },
      context: { name: window.LeapDigest.firstName(), localHour: new Date().getHours(), scope: d.scope },
    };
    const my = ++enhReq.current;
    A.assistant.briefing(orgId, payload)
      .then((r) => { if (my === enhReq.current && r && r.enabled && Array.isArray(r.cards)) setEnhanced(r); })
      .catch(() => { /* keep deterministic */ });
  }, [open]);

  // Is the copilot configured? (server-side key). Decides whether the footer is a
  // live chat or the "Soon" teaser. Live only; assume off otherwise.
  React.useEffect(() => {
    if (!open) return;
    if (!live()) { setCopilotOn(false); return; }
    const orgId = resolveOrgId();
    if (!orgId) { setCopilotOn(false); return; }
    let cancelled = false;
    A.assistant.status(orgId)
      .then((r) => { if (!cancelled) setCopilotOn(Boolean(r && r.enabled)); })
      .catch(() => { if (!cancelled) setCopilotOn(false); });
    return () => { cancelled = true; };
  }, [open]);

  // Progressive enhancement for the weekly view: deterministic instantly, then the
  // server narrates the plan/wrap. Live + configured only; failures keep the plain one.
  React.useEffect(() => {
    if (!open || mode !== "briefing" || panelTab !== "week") return;
    setWeekEnh(null);
    if (!live() || !copilotOn || !window.LeapDigest || !window.LeapDigest.buildWeekly) return;
    const orgId = resolveOrgId();
    if (!orgId) return;
    const w = window.LeapDigest.buildWeekly(weekMode);
    const payload = {
      kind: "weekly-" + w.mode,
      digest: { greeting: w.title, scope: w.scope, cards: w.cards.map((c) => ({ topic: c.topic, title: c.title, detail: c.detail, assist: c.assist })) },
      context: { name: window.LeapDigest.firstName(), scope: w.scope },
    };
    const my = ++weekReq.current;
    A.assistant.briefing(orgId, payload)
      .then((r) => { if (my === weekReq.current && r && r.enabled && Array.isArray(r.cards)) setWeekEnh({ mode: w.mode, greeting: r.greeting, intro: r.intro, cards: r.cards }); })
      .catch(() => { /* keep deterministic */ });
  }, [open, mode, panelTab, weekMode, copilotOn]);

  // Count a weekly view when the Week tab is showing a given mode.
  React.useEffect(() => {
    if (open && mode === "briefing" && !prefsOpen && panelTab === "week") track("weekly_view", weekMode);
  }, [open, mode, prefsOpen, panelTab, weekMode]);

  // Load the user's saved preferences when the prefs sheet opens (live only).
  React.useEffect(() => {
    if (!open || !prefsOpen || !live()) { if (prefsOpen && !prefs) setPrefs({ tone: "", length: "", notes: "" }); return; }
    const orgId = resolveOrgId();
    if (!orgId) { setPrefs({ tone: "", length: "", notes: "" }); return; }
    let cancelled = false;
    A.assistant.profile(orgId)
      .then((r) => { if (!cancelled) { const p = (r && r.profile) || {}; setPrefs({ tone: p.tone || "", length: p.length || "", notes: p.notes || "" }); } })
      .catch(() => { if (!cancelled) setPrefs({ tone: "", length: "", notes: "" }); });
    return () => { cancelled = true; };
  }, [open, prefsOpen]);

  // Keep the digest live (badge + panel) as the store changes — always, so the
  // bubble badge reflects the current state even when the panel is closed.
  React.useEffect(() => {
    if (!S || !S.subscribe) return;
    return S.subscribe(() => force());
  }, []);

  // Proactive, not naggy: surface on arrival only when there's something to show
  // AND it's new since the user last looked (digest fingerprint changed) — or the
  // very first visit. At most once per session; the bubble badge carries the rest.
  React.useEffect(() => {
    let seen = false;
    try { seen = sessionStorage.getItem(SEEN_KEY) === "1"; } catch (e) { /* */ }
    if (seen) return;
    const t = setTimeout(() => {
      if (!window.LeapDigest) { markSeen(); return; }
      const d = window.LeapDigest.build();
      const f = digestFp(d);
      let ack = null; try { ack = localStorage.getItem(ACK_KEY); } catch (e) { /* */ }
      const somethingNew = d && d.cards && d.cards.length > 0 && ack !== f;
      if (somethingNew) { setOpen(true); acknowledge(f); track("open", "auto"); }
      markSeen();
    }, 700);
    return () => clearTimeout(t);
  }, []);

  // Keep the newest chat turn in view.
  React.useEffect(() => {
    if (mode === "chat" && threadEndRef.current) threadEndRef.current.scrollIntoView({ behavior: "smooth", block: "end" });
  }, [thread, sending, mode]);

  const markSeen = () => { try { sessionStorage.setItem(SEEN_KEY, "1"); } catch (e) { /* */ } };
  const acknowledge = (f) => { try { if (f) localStorage.setItem(ACK_KEY, f); } catch (e) { /* */ } };
  const close = () => { setOpen(false); markSeen(); };
  const toggle = () => { setOpen((o) => { const n = !o; if (n) { markSeen(); acknowledge(digestFp(window.LeapDigest ? window.LeapDigest.build() : null)); track("open", "manual"); } return n; }); };

  // Build the digest every render (cheap) so the bubble badge stays live even while
  // the panel is closed. hasNew = something changed since the user last opened it.
  const digest = window.LeapDigest ? window.LeapDigest.build() : null;
  let ackFp = null; try { ackFp = localStorage.getItem(ACK_KEY); } catch (e) { /* */ }
  const digestCount = digest && digest.cards ? digest.cards.length : 0;
  const hasNew = digestCount > 0 && ackFp !== digestFp(digest);
  // Merge the LLM copy (title/detail/assist) onto the deterministic cards, matched
  // by topic — the deep-link action always stays the deterministic one.
  const enhMap = {};
  if (enhanced && enhanced.cards) enhanced.cards.forEach((c) => { enhMap[c.topic] = c; });
  const merge = (c) => { const e = enhMap[c.topic]; return e ? { ...c, title: e.title || c.title, detail: e.detail || c.detail, assist: e.assist || c.assist } : c; };
  const cards = digest ? digest.cards : [];
  const shown = (expanded ? cards : cards.slice(0, 5)).map(merge);
  const greeting = (enhanced && enhanced.greeting) || (digest && digest.greeting) || "Hello";
  const subtitle = (enhanced && enhanced.intro) || (cardsSummary(cards) + (digest && digest.scope ? " · " + digest.scope : ""));
  const go = (view) => { if (onNav && view) onNav(view); close(); };

  // Weekly view (built only when that tab is active). LLM narration merges the
  // same way as the daily briefing — by topic, deterministic action preserved.
  const weekly = (open && panelTab === "week" && window.LeapDigest && window.LeapDigest.buildWeekly) ? window.LeapDigest.buildWeekly(weekMode) : null;
  const weekEnhActive = weekEnh && weekEnh.mode === weekMode ? weekEnh : null;
  const weekEnhMap = {};
  if (weekEnhActive && weekEnhActive.cards) weekEnhActive.cards.forEach((c) => { weekEnhMap[c.topic] = c; });
  const mergeWeek = (c) => { const e = weekEnhMap[c.topic]; return e ? { ...c, title: e.title || c.title, detail: e.detail || c.detail, assist: e.assist || c.assist } : c; };
  const weekCards = weekly ? weekly.cards.map(mergeWeek) : [];
  const weekTitle = (weekEnhActive && weekEnhActive.greeting) || (weekly && weekly.title) || "This week";
  const weekSubtitle = (weekEnhActive && weekEnhActive.intro) || (weekly ? (weekly.subtitle + (weekly.scope ? " · " + weekly.scope : "")) : "");

  const T = { // local tokens — matches the DS
    accent: "var(--accent)", surface: "var(--surface, #fff)", fg1: "var(--fg1, #0f172a)",
    fg2: "var(--fg2, #475569)", fg3: "var(--fg3, #94a3b8)", border: "var(--border, #e5e7eb)",
    weak: "var(--accent-weak, #E6F4F6)",
  };

  // One action card — shared by the daily briefing and the weekly plan/wrap.
  const renderCard = (c, i) => (
    <div key={c.topic + i} style={{ background: T.surface, border: "1px solid " + T.border, borderRadius: 13, padding: "12px 13px", marginBottom: 9 }}>
      <div style={{ display: "flex", gap: 10 }}>
        <span style={{ width: 30, height: 30, borderRadius: 9, background: T.weak, display: "grid", placeItems: "center", flex: "none", marginTop: 1 }}>
          <Icon name={c.icon} size={16} color={T.accent} />
        </span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 700, color: T.fg1, lineHeight: 1.3 }}>{c.title}</div>
          {c.detail && <div style={{ fontSize: 12, color: T.fg2, marginTop: 3, lineHeight: 1.45 }}>{c.detail}</div>}
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 9, flexWrap: "wrap" }}>
            {c.action && <button onClick={() => { track("card_action", c.topic); go(c.action.view); }} style={{ border: "1px solid " + T.accent, background: T.accent, color: "#fff", borderRadius: 9, padding: "5px 11px", fontSize: 12, fontWeight: 600, cursor: "pointer", fontFamily: "inherit" }}>{c.action.label}</button>}
            {c.assist && (
              copilotOn
                ? <button onClick={() => { track("assist_click", c.topic); setMode("chat"); setDraft(c.assist); }} style={{ border: 0, background: "transparent", fontSize: 11.5, color: T.accent, display: "inline-flex", alignItems: "center", gap: 4, cursor: "pointer", fontFamily: "inherit", padding: 0 }}><Icon name="sparkles" size={12} color={T.accent} />{c.assist}</button>
                : <span title="The Leap copilot arrives in the next update" style={{ fontSize: 11.5, color: T.accent, display: "inline-flex", alignItems: "center", gap: 4 }}><Icon name="sparkles" size={12} color={T.accent} />{c.assist}</span>
            )}
          </div>
        </div>
      </div>
    </div>
  );

  // ── copilot ────────────────────────────────────────────────────────────────
  const chatContext = () => {
    let houseRules = [], brandVoice = null;
    try { houseRules = (S.getHouseRules && S.getHouseRules()) || []; } catch (e) { /* */ }
    try { const b = S.getBrand && S.getBrand(); brandVoice = (b && b.brandVoice) || null; } catch (e) { /* */ }
    const cs = (digest ? digest.cards : []).slice(0, 10).map((c) => ({ topic: c.topic, title: c.title, detail: c.detail }));
    return {
      name: window.LeapDigest ? window.LeapDigest.firstName() : undefined,
      scope: digest ? digest.scope : null,
      houseRules, brandVoice, digest: { cards: cs },
    };
  };

  const send = async () => {
    const text = draft.trim();
    if (!text || sending) return;
    const orgId = resolveOrgId();
    if (!orgId) return;
    const next = thread.concat({ role: "user", content: text });
    setThread(next);
    setDraft("");
    setSending(true);
    track("chat_message", null);
    const payload = { messages: next.map((m) => ({ role: m.role, content: m.content })), context: chatContext() };
    const my = ++chatReq.current;
    try {
      const r = await A.assistant.chat(orgId, payload);
      if (my !== chatReq.current) return;
      if (r && r.enabled) {
        setThread((t) => t.concat({ role: "assistant", content: r.reply || "", citations: r.citations || [], pendingActions: r.pendingActions || [] }));
      } else {
        setCopilotOn(false);
        setThread((t) => t.concat({ role: "assistant", content: "The copilot isn't available right now.", error: true }));
      }
    } catch (e) {
      if (my === chatReq.current) setThread((t) => t.concat({ role: "assistant", content: "Something went wrong — please try again.", error: true }));
    } finally {
      if (my === chatReq.current) setSending(false);
    }
  };

  const confirmAction = async (msgIdx, action) => {
    const orgId = resolveOrgId();
    if (!orgId) return;
    // optimistic: mark pending
    setThread((t) => t.map((m, i) => i === msgIdx ? { ...m, pendingActions: m.pendingActions.map((p) => p === action ? { ...p, busy: true } : p) } : m));
    try {
      const r = await A.assistant.act(orgId, { type: action.type, args: action.args });
      track("action_confirmed", action.type);
      setThread((t) => t.map((m, i) => i === msgIdx ? { ...m, pendingActions: m.pendingActions.map((p) => (p.busy && p.type === action.type ? { ...p, busy: false, done: true } : p)) } : m));
      // A saved preference should take effect immediately — refresh + re-narrate.
      if (action.type === "remember_preference" && r && r.profile) { setPrefs({ tone: r.profile.tone || "", length: r.profile.length || "", notes: r.profile.notes || "" }); setEnhanced(null); setWeekEnh(null); }
      // Every other action ran server-side through /act (bypassing the client store):
      // pull content + notifications back so the Posts/Queue/Campaign views and the
      // bell reflect it immediately, no reload. Notifications are now server-backed,
      // so a sent nudge shows up via this hydrate (no client-only mirror needed).
      if (r && r.ok && action.type !== "remember_preference" && window.LeapSync && window.LeapSync.hydrateContent) {
        try { Promise.resolve(window.LeapSync.hydrateContent()).then(() => { setEnhanced(null); setWeekEnh(null); }); } catch (e) { /* UI still refreshes on next reload */ }
      }
      try { if (S.emit) S.emit(); } catch (e) { /* refresh app views if the store re-broadcasts */ }
    } catch (e) {
      setThread((t) => t.map((m, i) => i === msgIdx ? { ...m, pendingActions: m.pendingActions.map((p) => (p.busy ? { ...p, busy: false, failed: true } : p)) } : m));
    }
  };

  const dismissAction = (msgIdx, action) => {
    track("action_dismissed", action.type);
    setThread((t) => t.map((m, i) => i === msgIdx ? { ...m, pendingActions: m.pendingActions.map((p) => p === action ? { ...p, dismissed: true } : p) } : m));
  };

  const openChat = () => { setMode("chat"); };

  const savePrefs = async () => {
    const orgId = resolveOrgId();
    if (!orgId || prefsSaving) return;
    setPrefsSaving(true);
    try {
      const p = prefs || {};
      const r = await A.assistant.saveProfile(orgId, { tone: p.tone || null, length: p.length || null, notes: p.notes || null });
      if (r && r.profile) setPrefs({ tone: r.profile.tone || "", length: r.profile.length || "", notes: r.profile.notes || "" });
      track("prefs_saved", null);
      setPrefsOpen(false);
      setEnhanced(null); setWeekEnh(null); // re-narrate with the new prefs next time
    } catch (e) { /* keep the form open on failure */ }
    finally { setPrefsSaving(false); }
  };

  return (
    <div style={{ position: "fixed", right: 22, bottom: 22, zIndex: 80, fontFamily: "inherit" }}>
      {open && (
        <div style={{ width: 384, maxWidth: "calc(100vw - 44px)", maxHeight: "min(72vh, 640px)", display: "flex", flexDirection: "column",
          background: T.surface, border: "1px solid " + T.border, borderRadius: 18, overflow: "hidden",
          boxShadow: "0 24px 60px rgba(11,59,66,.20), 0 4px 12px rgba(11,59,66,.10)", marginBottom: 12 }}>
          {/* header */}
          <div style={{ padding: "16px 18px 14px", background: "var(--gradient-core, linear-gradient(135deg,#0B3B42,#0EA5B7))", color: "#fff", position: "relative" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              {(mode === "chat" || prefsOpen) ? (
                <button onClick={() => (prefsOpen ? setPrefsOpen(false) : setMode("briefing"))} aria-label="Back" style={{ width: 30, height: 30, borderRadius: 9, background: "rgba(255,255,255,.16)", border: 0, display: "grid", placeItems: "center", flex: "none", cursor: "pointer" }}>
                  <Icon name="chevron-left" size={17} color="#fff" />
                </button>
              ) : (
                <span style={{ width: 30, height: 30, borderRadius: 9, background: "rgba(255,255,255,.16)", display: "grid", placeItems: "center", flex: "none" }}>
                  <Icon name="sparkles" size={17} color="#fff" />
                </span>
              )}
              <div style={{ flex: 1, minWidth: 0 }}>
                {prefsOpen ? (
                  <React.Fragment>
                    <div style={{ fontSize: 15.5, fontWeight: 800, fontFamily: "var(--font-display, inherit)", lineHeight: 1.2 }}>Preferences</div>
                    <div style={{ fontSize: 11.5, opacity: .85, marginTop: 1 }}>How I should work with you</div>
                  </React.Fragment>
                ) : mode === "chat" ? (
                  <React.Fragment>
                    <div style={{ fontSize: 15.5, fontWeight: 800, fontFamily: "var(--font-display, inherit)", lineHeight: 1.2 }}>Ask Leap</div>
                    <div style={{ fontSize: 11.5, opacity: .85, marginTop: 1 }}>Your copilot &amp; support{digest && digest.scope ? " · " + digest.scope : ""}</div>
                  </React.Fragment>
                ) : (
                  <React.Fragment>
                    <div style={{ fontSize: 15.5, fontWeight: 800, fontFamily: "var(--font-display, inherit)", lineHeight: 1.2 }}>{greeting}</div>
                    <div style={{ fontSize: 11.5, opacity: .85, marginTop: 1 }}>{subtitle}</div>
                  </React.Fragment>
                )}
              </div>
              {mode !== "chat" && !prefsOpen && (
                <button onClick={() => setPrefsOpen(true)} aria-label="Preferences" title="Preferences" style={{ border: 0, background: "transparent", color: "#fff", cursor: "pointer", padding: 4, opacity: .85 }}>
                  <Icon name="settings" size={17} color="#fff" />
                </button>
              )}
              <button onClick={close} aria-label="Close" style={{ border: 0, background: "transparent", color: "#fff", cursor: "pointer", padding: 4, opacity: .85 }}>
                <Icon name="x" size={18} color="#fff" />
              </button>
            </div>
          </div>

          {prefsOpen ? (
            /* ── preferences ── */
            <div style={{ flex: 1, overflowY: "auto", padding: 14, background: "var(--bg-subtle, #f8fafc)" }}>
              <div style={{ fontSize: 12.5, color: T.fg2, lineHeight: 1.5, marginBottom: 14 }}>Tell me how you like to work. I’ll apply this to your briefings and to Ask&nbsp;Leap.</div>
              {/* tone */}
              <div style={{ marginBottom: 14 }}>
                <label style={{ display: "block", fontSize: 11.5, fontWeight: 700, color: T.fg2, textTransform: "uppercase", letterSpacing: ".03em", marginBottom: 6 }}>Tone</label>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 6 }}>
                  {["punchy", "warm", "formal", "playful"].map((t) => (
                    <button key={t} onClick={() => setPrefs((p) => ({ ...(p || {}), tone: t }))} style={{ border: "1px solid " + ((prefs && prefs.tone) === t ? T.accent : T.border), background: (prefs && prefs.tone) === t ? T.weak : T.surface, color: (prefs && prefs.tone) === t ? T.accent : T.fg2, borderRadius: 999, padding: "5px 11px", fontSize: 12, fontWeight: 600, cursor: "pointer", fontFamily: "inherit", textTransform: "capitalize" }}>{t}</button>
                  ))}
                </div>
                <input value={(prefs && prefs.tone) || ""} onChange={(e) => setPrefs((p) => ({ ...(p || {}), tone: e.target.value }))} placeholder="…or describe it in your words"
                  style={{ width: "100%", boxSizing: "border-box", border: "1px solid " + T.border, borderRadius: 9, padding: "8px 10px", fontSize: 12.5, color: T.fg1, fontFamily: "inherit", background: T.surface, outline: "none" }} />
              </div>
              {/* length */}
              <div style={{ marginBottom: 14 }}>
                <label style={{ display: "block", fontSize: 11.5, fontWeight: 700, color: T.fg2, textTransform: "uppercase", letterSpacing: ".03em", marginBottom: 6 }}>Length</label>
                <div style={{ display: "flex", gap: 6 }}>
                  {[["brief", "Brief"], ["standard", "Standard"], ["detailed", "Detailed"]].map(([k, lbl]) => (
                    <button key={k} onClick={() => setPrefs((p) => ({ ...(p || {}), length: k }))} style={{ flex: 1, border: "1px solid " + ((prefs && prefs.length) === k ? T.accent : T.border), background: (prefs && prefs.length) === k ? T.weak : T.surface, color: (prefs && prefs.length) === k ? T.accent : T.fg2, borderRadius: 9, padding: "7px 8px", fontSize: 12, fontWeight: 600, cursor: "pointer", fontFamily: "inherit" }}>{lbl}</button>
                  ))}
                </div>
              </div>
              {/* notes */}
              <div style={{ marginBottom: 4 }}>
                <label style={{ display: "block", fontSize: 11.5, fontWeight: 700, color: T.fg2, textTransform: "uppercase", letterSpacing: ".03em", marginBottom: 6 }}>Remember about me</label>
                <textarea value={(prefs && prefs.notes) || ""} onChange={(e) => setPrefs((p) => ({ ...(p || {}), notes: e.target.value }))} placeholder="e.g. We're a specialty coffee roaster; keep it down-to-earth and never salesy." rows={3}
                  style={{ width: "100%", boxSizing: "border-box", border: "1px solid " + T.border, borderRadius: 9, padding: "8px 10px", fontSize: 12.5, color: T.fg1, fontFamily: "inherit", background: T.surface, outline: "none", resize: "vertical", lineHeight: 1.45 }} />
              </div>
            </div>
          ) : mode === "chat" ? (
            /* ── chat thread ── */
            <div style={{ flex: 1, overflowY: "auto", padding: 12, background: "var(--bg-subtle, #f8fafc)" }}>
              {thread.length === 0 && (
                <div style={{ padding: "10px 6px 14px", color: T.fg2, fontSize: 12.5, lineHeight: 1.5 }}>
                  <div style={{ fontWeight: 700, color: T.fg1, marginBottom: 6 }}>How can I help?</div>
                  Ask me how anything in Leap works, or ask me to draft a post. I’ll always check with you before creating anything.
                  <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 10 }}>
                    {["How do approvals work?", "Draft a post about our autumn special", "What are house rules?"].map((s) => (
                      <button key={s} onClick={() => setDraft(s)} style={{ border: "1px solid " + T.border, background: T.surface, color: T.fg2, borderRadius: 999, padding: "5px 10px", fontSize: 11.5, cursor: "pointer", fontFamily: "inherit" }}>{s}</button>
                    ))}
                  </div>
                </div>
              )}
              {thread.map((m, i) => (
                <div key={i} style={{ marginBottom: 10, display: "flex", justifyContent: m.role === "user" ? "flex-end" : "flex-start" }}>
                  <div style={{ maxWidth: "86%" }}>
                    <div style={{ background: m.role === "user" ? T.accent : T.surface, color: m.role === "user" ? "#fff" : (m.error ? "var(--danger, #d92d20)" : T.fg1),
                      border: m.role === "user" ? "1px solid " + T.accent : "1px solid " + T.border, borderRadius: 13,
                      borderBottomRightRadius: m.role === "user" ? 4 : 13, borderBottomLeftRadius: m.role === "user" ? 13 : 4,
                      padding: "9px 12px", fontSize: 13, lineHeight: 1.5, whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
                      {m.content}
                    </div>
                    {/* citations */}
                    {m.citations && m.citations.length > 0 && (
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 5, marginTop: 5 }}>
                        {m.citations.map((c) => (
                          <span key={c.slug} title="From the Leap help base" style={{ display: "inline-flex", alignItems: "center", gap: 4, fontSize: 10.5, color: T.fg2, background: T.weak, borderRadius: 999, padding: "2px 8px" }}>
                            <Icon name="book-open" size={11} color={T.accent} />{c.title}
                          </span>
                        ))}
                      </div>
                    )}
                    {/* proposed actions — confirm before anything happens */}
                    {m.pendingActions && m.pendingActions.map((p, pi) => (
                      p.dismissed ? null : (
                        <div key={pi} style={{ marginTop: 7, border: "1px solid " + T.border, background: T.surface, borderRadius: 12, padding: "10px 11px" }}>
                          <div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 4 }}>
                            <Icon name={p.done ? "check-circle-2" : "sparkles"} size={14} color={p.done ? "var(--success, #12B76A)" : T.accent} />
                            <span style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".03em", color: p.done ? "var(--success, #12B76A)" : T.accent }}>
                              {p.done ? "Done" : "Needs your OK"}
                            </span>
                          </div>
                          <div style={{ fontSize: 12.5, color: T.fg1, fontWeight: 600, lineHeight: 1.35 }}>{p.label}</div>
                          {p.summary && <div style={{ fontSize: 12, color: T.fg2, marginTop: 2, lineHeight: 1.4 }}>“{p.summary}”</div>}
                          {p.failed && <div style={{ fontSize: 11.5, color: "var(--danger, #d92d20)", marginTop: 5 }}>Couldn’t complete that — try again.</div>}
                          {!p.done && (
                            <div style={{ display: "flex", gap: 8, marginTop: 9 }}>
                              <button disabled={p.busy} onClick={() => confirmAction(i, p)} style={{ border: "1px solid " + T.accent, background: T.accent, color: "#fff", borderRadius: 9, padding: "5px 12px", fontSize: 12, fontWeight: 600, cursor: p.busy ? "default" : "pointer", opacity: p.busy ? .7 : 1, fontFamily: "inherit" }}>{p.busy ? "Working…" : "Confirm"}</button>
                              <button disabled={p.busy} onClick={() => dismissAction(i, p)} style={{ border: "1px solid " + T.border, background: "transparent", color: T.fg2, borderRadius: 9, padding: "5px 12px", fontSize: 12, cursor: "pointer", fontFamily: "inherit" }}>Not now</button>
                            </div>
                          )}
                        </div>
                      )
                    ))}
                  </div>
                </div>
              ))}
              {sending && (
                <div style={{ display: "flex", justifyContent: "flex-start", marginBottom: 10 }}>
                  <div style={{ background: T.surface, border: "1px solid " + T.border, borderRadius: 13, borderBottomLeftRadius: 4, padding: "9px 13px", fontSize: 13, color: T.fg3 }}>Leap is thinking…</div>
                </div>
              )}
              <div ref={threadEndRef} />
            </div>
          ) : (
            /* ── briefing: Today | Week ── */
            <div style={{ flex: 1, overflowY: "auto", padding: 12, background: "var(--bg-subtle, #f8fafc)" }}>
              {/* Today | Week segmented control */}
              <div style={{ display: "flex", gap: 4, padding: 3, background: "var(--surface, #fff)", border: "1px solid " + T.border, borderRadius: 11, marginBottom: 11 }}>
                {[["today", "Today"], ["week", "This week"]].map(([k, lbl]) => (
                  <button key={k} onClick={() => setPanelTab(k)} style={{ flex: 1, border: 0, borderRadius: 8, padding: "6px 8px", fontSize: 12.5, fontWeight: 700, cursor: "pointer", fontFamily: "inherit",
                    background: panelTab === k ? T.accent : "transparent", color: panelTab === k ? "#fff" : T.fg2 }}>{lbl}</button>
                ))}
              </div>

              {panelTab === "today" ? (
                cards.length === 0 ? (
                  <div style={{ padding: "28px 16px", textAlign: "center", color: T.fg3 }}>
                    <Icon name="check-circle-2" size={26} color="var(--success, #12B76A)" />
                    <div style={{ marginTop: 8, fontSize: 13.5, color: T.fg2 }}>You're all caught up. Nothing needs you right now.</div>
                  </div>
                ) : (
                  <React.Fragment>
                    {shown.map(renderCard)}
                    {cards.length > 5 && (
                      <button onClick={() => setExpanded((e) => !e)} style={{ width: "100%", border: "1px dashed " + T.border, background: "transparent", color: T.fg2, borderRadius: 10, padding: "8px", fontSize: 12.5, cursor: "pointer", fontFamily: "inherit" }}>
                        {expanded ? "Show less" : "Show " + (cards.length - 5) + " more"}
                      </button>
                    )}
                  </React.Fragment>
                )
              ) : (
                /* Week view: plan/wrap toggle + title + stats + cards */
                <React.Fragment>
                  <div style={{ marginBottom: 10 }}>
                    <div style={{ fontSize: 14.5, fontWeight: 800, color: T.fg1, fontFamily: "var(--font-display, inherit)" }}>{weekTitle}</div>
                    {weekSubtitle && <div style={{ fontSize: 12, color: T.fg2, marginTop: 2 }}>{weekSubtitle}</div>}
                  </div>
                  <div style={{ display: "flex", gap: 6, marginBottom: 11 }}>
                    {[["plan", "Plan ahead"], ["wrap", "Week wrap"]].map(([k, lbl]) => (
                      <button key={k} onClick={() => setWeekMode(k)} style={{ border: "1px solid " + (weekMode === k ? T.accent : T.border), background: weekMode === k ? T.weak : "transparent", color: weekMode === k ? T.accent : T.fg2, borderRadius: 999, padding: "4px 11px", fontSize: 11.5, fontWeight: 700, cursor: "pointer", fontFamily: "inherit" }}>{lbl}</button>
                    ))}
                  </div>
                  {weekly && weekly.stats && weekly.stats.length > 0 && (
                    <div style={{ display: "flex", gap: 8, marginBottom: 11 }}>
                      {weekly.stats.map((st, i) => (
                        <div key={i} style={{ flex: 1, background: T.surface, border: "1px solid " + T.border, borderRadius: 11, padding: "9px 10px", textAlign: "center" }}>
                          <div style={{ fontSize: 18, fontWeight: 800, color: T.accent, fontFamily: "var(--font-display, inherit)", lineHeight: 1.1 }}>{st.value}</div>
                          <div style={{ fontSize: 10.5, color: T.fg3, marginTop: 2, textTransform: "uppercase", letterSpacing: ".03em" }}>{st.label}</div>
                        </div>
                      ))}
                    </div>
                  )}
                  {weekCards.length === 0
                    ? <div style={{ padding: "22px 16px", textAlign: "center", color: T.fg3, fontSize: 13 }}>Nothing to show for {weekMode === "plan" ? "the week ahead" : "this week"} yet.</div>
                    : weekCards.map(renderCard)}
                </React.Fragment>
              )}
            </div>
          )}

          {/* footer — copilot / preferences */}
          <div style={{ padding: 11, borderTop: "1px solid " + T.border, background: T.surface }}>
            {prefsOpen ? (
              <div style={{ display: "flex", gap: 8 }}>
                <button onClick={savePrefs} disabled={prefsSaving} style={{ flex: 1, border: 0, borderRadius: 10, padding: "10px", fontSize: 13, fontWeight: 700, cursor: prefsSaving ? "default" : "pointer", opacity: prefsSaving ? .7 : 1, color: "#fff", fontFamily: "inherit", background: "var(--gradient-core, linear-gradient(135deg,#0B3B42,#0EA5B7))" }}>{prefsSaving ? "Saving…" : "Save preferences"}</button>
                <button onClick={() => setPrefsOpen(false)} disabled={prefsSaving} style={{ border: "1px solid " + T.border, background: "transparent", color: T.fg2, borderRadius: 10, padding: "10px 14px", fontSize: 13, cursor: "pointer", fontFamily: "inherit" }}>Cancel</button>
              </div>
            ) : mode === "chat" ? (
              <form onSubmit={(e) => { e.preventDefault(); send(); }} style={{ display: "flex", alignItems: "center", gap: 8, border: "1px solid " + T.border, borderRadius: 11, padding: "5px 6px 5px 12px", background: "var(--bg-subtle, #f8fafc)" }}>
                <input value={draft} onChange={(e) => setDraft(e.target.value)} placeholder="Ask Leap anything…" autoFocus
                  style={{ flex: 1, border: 0, background: "transparent", outline: "none", fontSize: 13, color: T.fg1, fontFamily: "inherit" }} />
                <button type="submit" disabled={!draft.trim() || sending} aria-label="Send" style={{ width: 32, height: 32, borderRadius: 9, border: 0, cursor: (!draft.trim() || sending) ? "default" : "pointer", opacity: (!draft.trim() || sending) ? .5 : 1,
                  background: "var(--gradient-core, linear-gradient(135deg,#0B3B42,#0EA5B7))", color: "#fff", display: "grid", placeItems: "center", flex: "none" }}>
                  <Icon name="arrow-up" size={16} color="#fff" />
                </button>
              </form>
            ) : copilotOn ? (
              <button onClick={openChat} style={{ width: "100%", display: "flex", alignItems: "center", gap: 8, border: "1px solid " + T.border, borderRadius: 11, padding: "9px 12px", color: T.fg2, background: "var(--bg-subtle, #f8fafc)", cursor: "pointer", fontSize: 12.5, fontFamily: "inherit", textAlign: "left" }}>
                <Icon name="sparkles" size={14} color={T.accent} />
                <span>Ask Leap anything…</span>
                <Icon name="arrow-up-right" size={13} color={T.fg3} style={{ marginLeft: "auto" }} />
              </button>
            ) : (
              <div title="The conversational copilot arrives in the next update" style={{ display: "flex", alignItems: "center", gap: 8, border: "1px solid " + T.border, borderRadius: 11, padding: "9px 12px", color: T.fg3, background: "var(--bg-subtle, #f8fafc)", cursor: "not-allowed", fontSize: 12.5 }}>
                <Icon name="sparkles" size={14} color={T.fg3} />
                <span>Ask Leap anything…</span>
                <span style={{ marginLeft: "auto", fontSize: 10.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".04em", color: T.accent, background: T.weak, borderRadius: 999, padding: "2px 7px" }}>Soon</span>
              </div>
            )}
          </div>
        </div>
      )}
      {/* the bubble + badge */}
      <div style={{ position: "relative", width: 54, height: 54, marginLeft: "auto", float: "right" }}>
        <button onClick={toggle} aria-label={digestCount > 0 ? "Leap Assistant, " + digestCount + " things to look at" : "Leap Assistant"} style={{ display: "flex", width: 54, height: 54, borderRadius: 999, border: 0, cursor: "pointer",
          background: "var(--gradient-core, linear-gradient(135deg,#0B3B42,#0EA5B7))", color: "#fff", alignItems: "center", justifyContent: "center",
          boxShadow: "0 10px 26px rgba(14,165,183,.42)" }}>
          <Icon name={open ? "chevron-down" : "sparkles"} size={23} color="#fff" />
        </button>
        {!open && digestCount > 0 && (
          <span aria-hidden="true" style={{ position: "absolute", top: -3, right: -3, minWidth: 21, height: 21, padding: "0 5px", borderRadius: 999, color: "#fff", fontSize: 11, fontWeight: 800, display: "grid", placeItems: "center", boxSizing: "border-box", lineHeight: 1,
            background: hasNew ? "var(--danger, #ef4444)" : T.accent,
            ...(hasNew ? { animation: "leapPulse 2s ease-in-out infinite" } : { boxShadow: "0 0 0 2px var(--surface, #fff)" }) }}>
            {digestCount > 9 ? "9+" : digestCount}
          </span>
        )}
      </div>
      <style>{"@keyframes leapPulse{0%,100%{box-shadow:0 0 0 2px var(--surface,#fff),0 0 0 3px rgba(239,68,68,.55)}50%{box-shadow:0 0 0 2px var(--surface,#fff),0 0 0 9px rgba(239,68,68,0)}}"}</style>
    </div>
  );
}

// A short, stable fingerprint of the digest's actionable state — topic + headline
// (headlines encode the counts, e.g. "4 posts scheduled") + priority. Used to tell
// whether anything MATERIALLY changed since the user last opened the assistant.
function digestFp(d) {
  if (!d || !d.cards || !d.cards.length) return "empty";
  var s = d.cards.map(function (c) { return (c.topic || "") + ":" + (c.title || "") + ":" + (c.priority || ""); }).join("|");
  var h = 0;
  for (var i = 0; i < s.length; i++) { h = (h * 31 + s.charCodeAt(i)) | 0; }
  return String(h);
}

function cardsSummary(cards) {
  if (!cards || !cards.length) return "You're all caught up";
  const n = cards.length;
  return n + " thing" + (n === 1 ? "" : "s") + " to look at";
}

window.Assistant = Assistant;
