/* briefs.jsx, the notifications hub behind the top-right bell.
   Read-only by design: market recaps, price alerts, and confirmations that a
   standing automation already ran — never anything to approve or reject.
   Approvals live in Stream → Needs you; this only points there. */

/* ---- archived-briefs store · "Mark as read" and swipe-to-archive both move a
   brief here. Persisted to localStorage; broadcasts so the Stream briefs board
   and the bell hub stay in sync. Keyed by brief id. -------------------------- */
const ARCHIVE_KEY = "yoshi_archived_briefs";
const readArchived = () => { try { return JSON.parse(localStorage.getItem(ARCHIVE_KEY) || "[]"); } catch (e) { return []; } };
const writeArchived = (arr) => { try { localStorage.setItem(ARCHIVE_KEY, JSON.stringify(arr)); } catch (e) {} window.dispatchEvent(new CustomEvent("yoshi-archived")); };
const useArchivedBriefs = () => {
  const [arch, setArch] = useState(readArchived);
  useEffect(() => {
    const f = () => setArch(readArchived());
    window.addEventListener("yoshi-archived", f);
    return () => window.removeEventListener("yoshi-archived", f);
  }, []);
  const toggle = (id) => { const cur = readArchived(); writeArchived(cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id]); };
  const add = (id) => { const cur = readArchived(); if (!cur.includes(id)) writeArchived([...cur, id]); };
  return [arch, toggle, add];
};

/* ---- bell badge count · live unread "Needs you" proposals. The app shells
   (app.jsx / web.jsx) broadcast their current proposal count whenever the deck
   changes, so every bell — mobile NavBar and web top bar — stays in sync. ---- */
let __bellCount = (typeof PROPOSALS !== "undefined" ? PROPOSALS.length : 0);
const setBellCount = (n) => { __bellCount = n; window.dispatchEvent(new CustomEvent("yoshi-bell")); };
const useBellCount = () => {
  const [n, setN] = useState(__bellCount);
  useEffect(() => {
    const f = () => setN(__bellCount);
    window.addEventListener("yoshi-bell", f);
    return () => window.removeEventListener("yoshi-bell", f);
  }, []);
  return n;
};
window.setBellCount = setBellCount;
window.useBellCount = useBellCount;

/* hover-revealed definition for the row's category icon (alert vs insight).
   Replaces the native title= tooltip with a styled popover. */
const BRIEF_TIPS = {
  insight: ["Insight", "Something Yoshi noticed for you proactively, like portfolio concentration or spending patterns.", "var(--accent)"],
  alert: ["Alert", "Something Yoshi wants to tell you about, like a price alert you set, market recap, or scheduled reports.", "var(--ink-2)"],
  done: ["Completed", "A standing automation that already ran. Nothing here needs a decision.", "var(--accent-pos)"]
};
const IconTip = ({ kind, children }) => {
  const [show, setShow] = useState(false);
  const def = BRIEF_TIPS[kind] || BRIEF_TIPS.alert;
  return (
    <span
      onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}
      onClick={(e) => e.stopPropagation()}
      style={{ width: 30, height: 30, flex: "none", display: "grid", placeItems: "center", marginTop: 1, position: "relative", color: def[2] }}>
      {children}
      {show && (
        <div style={{
          position: "absolute", top: "calc(100% + 6px)", left: -2, zIndex: 40, width: 196,
          background: "var(--ink)", color: "var(--bg)", padding: "9px 11px", borderRadius: 9,
          boxShadow: "0 6px 22px rgba(0,0,0,0.22)", pointerEvents: "none"
        }}>
          <div style={{ position: "absolute", top: -4, left: 12, width: 8, height: 8, background: "var(--ink)", transform: "rotate(45deg)" }} />
          <div style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 700, letterSpacing: "0.04em", textTransform: "uppercase", opacity: 0.65 }}>{def[0]}</div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, lineHeight: 1.4, marginTop: 3 }}>{def[1]}</div>
        </div>
      )}
    </span>
  );
};

/* leading row icon — mirrors home's recent-activity RowIcon: Yoshi mark for
   agent-generated briefs (insights / approvals), schematic glyph otherwise. */
const BriefRowIcon = ({ icon }) => {
  const yoshi = icon === "bolt";
  const p = useTheme();
  return (
    <span style={{ width: 24, height: 24, flex: "none", display: "grid", placeItems: "center", color: "var(--ink-2)", opacity: yoshi && p === "graphite" ? 0.72 : 1 }}>
      {yoshi ? <Logo size={15} /> : <Icon name={icon === "bulb" ? "trade" : (icon || "bell")} size={17} stroke={1.5} color="var(--ink-2)" />}
    </span>);
};

const BriefRow = ({ b, last, onOpen, selected }) =>
<button className="press" onClick={() => onOpen(b)}
style={{
  width: "100%", textAlign: "left", background: selected ? "var(--bg-2)" : "var(--bg)", border: "none",
  display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 12, alignItems: "center",
  padding: "13px 18px", cursor: "pointer", position: "relative"
}}>
    {selected && <span style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: 3, background: "var(--accent)" }} />}
    <BriefRowIcon icon={b.icon} />
    <div style={{ minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{b.title}</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{b.body}</div>
    </div>
    <div style={{ textAlign: "right", whiteSpace: "nowrap" }}>
      {b.value != null ?
    <Money value={b.value} size={12.5} sign={b.tone === "in"} color={b.tone === "in" ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" /> :
    null}
      {b.when && <div style={{ fontFamily: "var(--f-mono)", fontSize: 9.5, color: "var(--ink-3)", marginTop: 3 }}>{b.when}</div>}
    </div>
    {!last && <span style={{ position: "absolute", left: 18, right: 18, bottom: 0, height: 1, background: "var(--rule)" }} />}
  </button>;


/* swipe a row left to archive (or restore, when already archived) */
const SwipeRow = ({ archived, onAction, last, children, label, icon, bg }) => {
  const [dx, setDx] = useState(0);
  const startX = useRef(null);
  const moved = useRef(false);
  const lbl = label || (archived ? "Restore" : "Archive");
  const ic = icon || (archived ? "back" : "inbox");
  const back = bg || (archived ? "var(--accent-pos)" : "var(--ink)");
  const onDown = (e) => { startX.current = e.clientX; moved.current = false; };
  const onMove = (e) => {
    if (startX.current == null) return;
    let d = e.clientX - startX.current;
    if (Math.abs(d) > 4) moved.current = true;
    setDx(Math.max(-112, Math.min(0, d)));
  };
  const end = () => {
    if (startX.current == null) return;
    const fire = dx <= -56;
    startX.current = null; setDx(0);
    if (fire) onAction();
  };
  return (
    <div style={{ position: "relative", overflow: "hidden", background: "var(--bg)" }}>
      {dx < 0 &&
      <div style={{ position: "absolute", inset: 0, display: "flex", justifyContent: "flex-end", alignItems: "center", paddingRight: 22, background: back }}>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6, color: "var(--bg)", fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600 }}>
          <Icon name={ic} size={15} color="var(--bg)" />{lbl}
        </span>
      </div>}
      <div onPointerDown={onDown} onPointerMove={onMove} onPointerUp={end} onPointerLeave={end} onPointerCancel={end}
        onClickCapture={(e) => { if (moved.current) { e.stopPropagation(); e.preventDefault(); } }}
        style={{ position: "relative", background: "var(--bg)", transform: `translateX(${dx}px)`, transition: startX.current == null ? "transform 200ms cubic-bezier(0.16,1,0.3,1)" : "none", touchAction: "pan-y" }}>
        {children}
      </div>
    </div>
  );
};


const BRIEF_TYPE = { recap: "Market recap", alert: "Price alert", opportunity: "Opportunity", nudge: "Heads-up", automation: "Automation", report: "Scheduled report" };

/* a single brief, opened like an email — full read + a chat-with-Yoshi CTA */
/* the ordered "catch up" feed (error alerts, then insights) — matches the
   openable order in BriefsBoard so Mark-as-read advances brief→brief */
const BRIEF_FEED = () => [
  ...ERROR_ALERTS.map((e) => ({ ...e, kind: "alert", icon: "bell" })),
  ...BRIEF_INSIGHTS.map((it) => ({ id: "ins-" + it.label, kind: "insight", icon: "bulb", title: it.label, body: it.read, ask: it.ask, stat: it.stat, tone: it.tone })),
  ...BRIEFS.filter((b) => b.folder === "alerts").map((b) => ({ ...b, kind: "insight", icon: "bulb" })),
];

/* per-brief conversation · chatting inside an alert/insight brief keeps you in
   the brief (not the global chat) and persists, so returning shows the context. */
const BRIEF_THREAD_KEY = "yoshi_brief_threads";
const readBriefThreads = () => { try { return JSON.parse(localStorage.getItem(BRIEF_THREAD_KEY) || "{}"); } catch (e) { return {}; } };
const useBriefThread = (id) => {
  const [msgs, setMsgs] = useState(() => readBriefThreads()[id] || []);
  useEffect(() => { setMsgs(readBriefThreads()[id] || []); }, [id]);
  const persist = (next) => { const all = readBriefThreads(); all[id] = next; try { localStorage.setItem(BRIEF_THREAD_KEY, JSON.stringify(all)); } catch (e) {} setMsgs(next); };
  return [msgs, persist];
};
const briefFollowup = (t) => {
  const s = (t || "").toLowerCase();
  if (/why|how|explain|what/.test(s)) return "Good question. The short version: it follows from your current balances and the rule you set. I can show the exact numbers or draft a change if you'd like.";
  if (/yes|do it|go ahead|sure|please/.test(s)) return "On it. I'll put together a proposal you can approve, and flag it in your stream before anything runs.";
  return "Got it. I'll look into that and come back with something specific you can act on.";
};

const BriefBubble = ({ m }) => {
  const isUser = m.from === "user";
  return (
    <div className="yo-enter" style={{ display: "flex", justifyContent: isUser ? "flex-end" : "flex-start" }}>
      <div style={{ maxWidth: "84%", padding: "9px 12px", borderRadius: isUser ? "13px 13px 4px 13px" : "13px 13px 13px 4px", background: isUser ? "var(--accent)" : "var(--bg-card)", color: isUser ? "var(--accent-ink)" : "var(--ink)", border: isUser ? "none" : "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: 13.5, lineHeight: 1.45 }}>{m.t}</div>
    </div>);
};
const BriefTyping = () =>
<div style={{ display: "flex", justifyContent: "flex-start" }}>
    <div style={{ padding: "11px 13px", background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: "13px 13px 13px 4px", display: "flex", gap: 4 }}>
      {[0, 1, 2].map((i) => <span key={i} className="yo-pulse" style={{ width: 6, height: 6, borderRadius: 999, background: "var(--ink-3)", animationDelay: `${i * 200}ms` }} />)}
    </div>
  </div>;

const StatusTag = ({ label, color }) =>
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "4px 10px", borderRadius: 999, border: `1px solid ${color}`, color, fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", whiteSpace: "nowrap", flex: "none" }}>
    <span style={{ width: 5, height: 5, borderRadius: 999, background: color }} /> {label}
  </span>;

const BriefDetail = ({ b, onBack, onClose, nav, onAdvance, hasNext, onArchive, embedded, onExecute, onDecline, onAskProposal }) => {
  const [msg, setMsg] = useState("");
  const [gate, setGate] = useState(false);
  const [arch, toggleArchived, addArchived] = useArchivedBriefs();
  const isArchived = arch.includes(b.id);
  // archived & resolved briefs are read-only — no chat field (kept conversation shows as history)
  const isClosed = isArchived || !!b.outcome;
  const showFooter = !b.outcome || !!b.autoId || !isClosed;
  const [thread, setThread] = useBriefThread(b.id);
  const [typing, setTyping] = useState(false);
  const scrollRef = useRef(null);
  const acted = useRef(false);
  useEffect(() => { if (acted.current) { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; } }, [thread, typing]);
  const send = () => {
    const t = msg.trim();
    if (!t) return;
    // a Needs-you proposal: chatting forks the real proposal thread instead
    if (b.proposal && onAskProposal) { setMsg(""); onAskProposal(t); return; }
    setMsg(""); acted.current = true;
    const first = thread.length === 0;
    const base = [...thread, { from: "user", t, time: "now" }];
    setThread(base);
    setTyping(true);
    setTimeout(() => { setTyping(false); setThread([...base, { from: "agent", t: first && b.ask && b.ask[1] ? b.ask[1] : briefFollowup(t), time: "now" }]); }, 1100);
  };
  const attachDoc = (doc) => {
    acted.current = true;
    const base = [...thread, { from: "user", t: `Attached ${doc.name}`, time: "now" }];
    setThread(base);
    setTyping(true);
    setTimeout(() => { setTyping(false); setThread([...base, { from: "agent", t: attachReplyFor(doc), time: "now" }]); }, 1100);
  };
  const advance = onAdvance || onBack;
  const markRead = () => { addArchived(b.id); if (onArchive) onArchive(b.id); advance(); };
  const markUnread = () => { toggleArchived(b.id); if (onArchive) onArchive(b.id); advance(); };
  const tag = b.outcome === "approved" ? ["Approved", "var(--accent-pos)"] :
  b.outcome === "declined" ? ["Declined", "var(--signal-neg)"] :
  isArchived ? ["Archived", "var(--ink-3)"] : null;
  return (
    <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 330, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Brief">
      {!embedded && window.StatusBar && <window.StatusBar />}
      {!embedded &&
      <NavBar title="Briefs" onBack={onBack}
      right={<button className="press" onClick={onClose} style={{ background: "none", border: "none", color: "var(--ink-3)", display: "flex" }}><Icon name="close" size={20} /></button>} />}
      <div className="scroll" ref={scrollRef} style={{ padding: "18px 20px 20px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
          <Icon name={b.icon || "bell"} size={14} color={b.kind === "insight" ? "var(--accent)" : "var(--ink-3)"} stroke={1.6} />
          <Eyebrow>{b.proposal ? "Needs you" : b.kind === "insight" ? "Insight" : BRIEF_TYPE[b.type] || "Brief"}{b.when ? " · " + b.when : ""}</Eyebrow>
          {tag && <span style={{ marginLeft: "auto" }}><StatusTag label={tag[0]} color={tag[1]} /></span>}
        </div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 23, fontWeight: 600, letterSpacing: "-0.025em", marginTop: 9, lineHeight: 1.2 }}>{b.title}</div>
        {b.value != null &&
        <div style={{ marginTop: 10 }}>
            <Money value={b.value} size={20} weight={500} sign={b.tone === "in"} color={b.tone === "in" ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
          </div>
        }
        {b.value == null && b.stat && !b.outcome &&
        <div style={{ marginTop: 10, fontFamily: "var(--f-mono)", fontSize: 22, fontWeight: 500, fontVariantNumeric: "tabular-nums", color: b.tone === "pos" ? "var(--accent-pos)" : b.tone === "warn" ? "var(--signal-neg)" : "var(--ink)" }}>{b.stat}</div>
        }
        <div style={{ height: embedded ? 0 : 1, background: "var(--rule)", margin: embedded ? "16px 0 2px" : "16px 0" }} />
        {b.news ?
        <window.NewsBrief data={b.news} /> :
        <>
        <p style={{ fontFamily: "var(--f-display)", fontSize: 14.5, lineHeight: 1.6, color: "var(--ink)", margin: 0 }}>{b.body}</p>
        {b.ask && <p style={{ fontFamily: "var(--f-display)", fontSize: 13.5, lineHeight: 1.6, color: "var(--ink-2)", margin: "12px 0 0" }}>{b.ask[1]}</p>}
        </>}
        {b.legs &&
        <div style={{ marginTop: 18 }}>
          <Eyebrow style={{ marginBottom: 8 }}>What we proposed</Eyebrow>
          <div style={{ border: "1px solid var(--rule)" }}>
            {b.legs.map((leg, i) => {
              const [tag, label, detail, amount] = leg;
              const tagColor = tag === "SELL" ? "var(--signal-neg)" : tag === "BUY" ? "var(--accent-pos)" : "var(--ink-2)";
              return (
                <div key={i} style={{ display: "grid", gridTemplateColumns: "44px 1fr auto", gap: 8, alignItems: "center", padding: "9px 11px", borderBottom: i === b.legs.length - 1 ? "none" : "1px dashed var(--rule)" }}>
                  <span style={{ fontFamily: "var(--f-display)", fontSize: 9.5, fontWeight: 700, letterSpacing: "0.08em", color: tagColor }}>{tag}</span>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, fontWeight: 500 }}>{label}</div>
                    <div style={{ fontFamily: "var(--f-display)", fontSize: 10.5, color: "var(--ink-3)" }}>{detail}</div>
                  </div>
                  <span style={{ fontFamily: "var(--f-mono)", fontSize: 12, color: "var(--ink)", whiteSpace: "nowrap" }}>{amount}</span>
                </div>);
            })}
          </div>
          {b.net != null &&
          <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginTop: 11 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-3)" }}>Net</span>
            <Money value={b.net} size={14} sign color={b.net >= 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
          </div>}
        </div>
        }
        {!b.proposal &&
        <div style={{ display: "flex", gap: 8, alignItems: "flex-start", marginTop: 16, padding: "10px 12px", background: "var(--bg-2)" }}>
          <Icon name="shield" size={15} color="var(--ink-3)" stroke={1.5} style={{ marginTop: 1 }} />
          <span style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", lineHeight: 1.45 }}>Just a heads-up, and nothing here needs your approval.</span>
        </div>}

        {/* in-brief conversation — persists, so returning to this brief shows the context */}
        {thread.length > 0 &&
        <div style={{ marginTop: 18 }}>
          <div style={{ height: 1, background: "var(--rule)", margin: "0 0 14px" }} />
          <Eyebrow style={{ marginBottom: 11 }}>Conversation</Eyebrow>
          <div style={{ display: "flex", flexDirection: "column", gap: 9 }}>
            {thread.map((m, i) => <BriefBubble key={i} m={m} />)}
            {typing && <BriefTyping />}
          </div>
        </div>}
      </div>
      {showFooter &&
      <div style={{ flex: "none", borderTop: "1px solid var(--rule)", paddingBottom: isClosed ? 22 : 0 }}>
        {/* catch-up actions — the outcome tag sits in the header; archived gets a revert */}
        {b.outcome ? null :
        b.proposal && onExecute ?
        <>
        <div style={{ display: "flex", gap: 8, padding: "12px 20px 0" }}>
          <Btn kind="ghost" full onClick={() => onDecline && onDecline(b.id)}>Decline</Btn>
          <Btn full onClick={() => setGate(true)}>Approve &amp; execute</Btn>
        </div>
        {gate && <PasskeyGate title={b.title} amount={b.proposal && b.proposal.net ? Math.abs(b.proposal.net) : null} detail="Use your passkey to authorize this. Yoshi executes immediately after." cta="Authorize with passkey" onSuccess={() => { setGate(false); onExecute(b.id); }} onCancel={() => setGate(false)} />}
        </> :
        isArchived ?
        <div style={{ padding: "12px 20px 0" }}>
          <Btn full onClick={markUnread}>Mark as unread</Btn>
        </div> :
        <div style={{ display: "flex", gap: 8, padding: "12px 20px 0" }}>
          <Btn kind="ghost" full onClick={advance}>Keep unread</Btn>
          <Btn full onClick={markRead}>Mark as read</Btn>
        </div>}
        {b.autoId && <div style={{ padding: "8px 20px 0" }}><Btn kind="ghost" full onClick={() => {onClose();nav.automation(b.autoId);}}>View automation</Btn></div>}
        {/* chat field — only on live briefs; archived/resolved briefs are read-only */}
        {!isClosed &&
        <div style={{ margin: "12px 20px 22px" }}>
          <YoshiComposer value={msg} onChange={setMsg} onSend={send} placeholder={thread.length ? "Reply…" : "Ask Yoshi about this…"} onAttach={attachDoc} />
        </div>}
      </div>}
    </div>);

};

/* a proposal teaser in the "Needs you" folder — opens the approval sheet */
const PropRow = ({ p, onReview, last }) =>
<button className="press" onClick={onReview} style={{
  width: "100%", textAlign: "left", background: "none", border: "none",
  borderBottom: last ? "none" : "1px solid var(--rule)", cursor: "pointer", padding: "13px 18px", display: "block"
}}>
    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
      <LiveDot />
      <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-3)" }}>{p.agent}</span>
      <span style={{ marginLeft: "auto", fontFamily: "var(--f-mono)", fontSize: 12.5, color: p.net >= 0 ? "var(--accent-pos)" : "var(--ink)" }}>{signed(p.net)}</span>
    </div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, marginTop: 4, letterSpacing: "-0.01em" }}>{p.title}</div>
    <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 6, fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600, color: "var(--accent)" }}>Review <Icon name="back" size={13} color="var(--accent)" style={{ transform: "scaleX(-1)" }} /></div>
  </button>;


/* the briefs body (collapsible sections + swipe-to-archive + new-brief CTA),
   reused by the full-screen BriefsHub and the Stream tab's Briefs view. */

/* Alerts are now strictly things that went wrong — failed moves, unfilled
   orders. Rare by design; an empty Alerts section is the healthy state. */
const ERROR_ALERTS = [
  { id: "err-returned", title: "Transfer returned", when: "Today",
    body: "Your $2,000 move to Chase ••4417 was returned two days after it sent. The full amount is already back in your Cash balance.",
    stat: "Returned", tone: "warn",
    ask: ["Why was my Chase transfer returned?", "Chase sent the $2,000 back. The name or account number on the transfer didn't match what they have on file. The full amount is already back in your Cash balance and no fee was charged. Want me to double-check the details and retry, or send it somewhere else?"] },
  { id: "err-expired", title: "A proposal expired", when: "1h",
    body: "Yoshi's “Move idle cash to Treasuries” proposal expired before you approved it. Nothing was bought and no cash moved.",
    stat: "Expired", tone: "warn",
    legs: [["BUY", "26-wk Treasuries", "6 buys", "$15,000"], ["YIELD", "Average", "Per year", "≈ 5.10%"]], net: 64.00,
    ask: ["What happened to the Treasuries proposal?", "The proposal to move $15,000 into 26-week Treasuries expired before you approved it. Yields shifted, so the ≈5.10% I quoted no longer held. Nothing was bought and no cash moved. Want me to reprice it at today's rate and send a fresh one to approve?"] },
  { id: "err-trade-failed", title: "A trade failed", when: "2h",
    body: "Your rebalance couldn't execute. NVDA moved past your limit between approval and the trade, so none of the legs settled.",
    stat: "Failed", tone: "warn",
    ask: ["Why did my rebalance fail?", "NVDA moved past your limit in the moment between your approval and the trade, so the venue rejected the order and none of the legs settled. No cash moved. I've re-priced at the current market. Want me to send a fresh proposal you can approve?"] },
  { id: "err-partial", title: "An order partially filled", when: "Yesterday",
    body: "Your “Buy the dip” automation bought 4.0 of 6.1 VOO shares before liquidity ran out at your limit. The remaining 2.1 were cancelled.",
    stat: "Partial", tone: "warn",
    ask: ["What happened with my VOO order?", "Your “Buy the dip” automation filled 4.0 of 6.1 VOO shares at an average of $545.10 before VOO ran out at your limit; the remaining 2.1 shares were cancelled at close and no further cash was used. Want me to finish the rest at the current price?"] },
];

/* examples of resolved proposals — kept in Archived for the record */
const APPROVED_EXAMPLE = {
  id: "apr-rebalance", kind: "alert", icon: "bolt", outcome: "approved",
  title: "Rebalance your investments",
  body: "You approved this on May 24. Yoshi executed it the same day.",
  stat: "Approved", tone: "pos", when: "May 24",
  ask: ["What did the rebalance do?", "On May 24 you approved trimming NVDA and BTC into VOO. It executed the same day, locking in about $1,154 and bringing your mix back to target. It's archived here for the record."],
  legs: [["SELL", "NVDA", "12 sh", "\u2248 $1,706"], ["SELL", "BTC", "0.018", "\u2248 $1,731"], ["BUY", "VOO", "6.1 sh", "\u2248 $3,344"]],
  net: 1153.66
};
const REJECTED_EXAMPLE = {
  id: "rej-treasuries", kind: "alert", icon: "bolt", outcome: "declined",
  title: "Earn more on idle cash",
  body: "You declined this on May 26. Reason: prefer to keep the cash liquid.",
  stat: "Declined", tone: "warn", when: "May 26",
  ask: ["Why did I decline the Treasuries move?", "You declined moving $15,000 into 26-week Treasuries on May 26, noting you'd rather keep the cash liquid. It's archived here for the record. Want me to set a reminder to revisit it, or draft a smaller version?"],
  legs: [["BUY", "26-wk Treasuries", "6 buys", "$15,000"], ["YIELD", "Average", "Per year", "\u2248 5.10%"]],
  net: 64.00
};

const BriefsBoard = ({ nav, onOpen, onClose = () => {}, proposals = [], onApprove, selectedId }) => {
  const [secs, setSecs] = useState({ needsyou: true, alerts: true, insights: true, archived: false });
  const [arch, archive] = useArchivedBriefs();
  const [q, setQ] = useState("");
  const toggle = (k) => setSecs((s) => ({ ...s, [k]: !s[k] }));

  // Needs you = approvals (mirrors Home). Alerts = errors only. Insights =
  // everything Yoshi surfaces for you (proactive reads, recaps, price alerts).
  const ALL = [
    ...proposals.map((p) => ({ id: p.id, kind: "approve", icon: "bolt", title: p.title, body: p.why.split(". ")[0] + ".", value: p.net, tone: p.net >= 0 ? "in" : "out", sec: "needsyou", when: p.agent, agent: p.agent, legs: p.legs, net: p.net, proposal: p })),
    ...ERROR_ALERTS.map((e) => ({ ...e, kind: "alert", icon: "bell", sec: "alerts" })),
    ...(window.MORNING_BRIEF ? [{ id: "daily-brief", kind: "insight", icon: "bulb", title: "Today's read", body: window.MORNING_BRIEF.body, ask: window.MORNING_BRIEF.ask, when: "Today", sec: "insights" }] : []),
    ...BRIEF_INSIGHTS.map((it) => ({ id: "ins-" + it.label, kind: "insight", icon: "bulb", title: it.label, body: it.read, ask: it.ask, stat: it.stat, tone: it.tone, sec: "insights" })),
    ...BRIEFS.filter((b) => b.folder === "alerts").map((b) => ({ ...b, kind: "insight", icon: "bulb", sec: "insights" })),
  ];
  const ql = q.trim().toLowerCase();
  const matchQ = (x) => !ql || ((x.title || "") + " " + (x.body || "")).toLowerCase().includes(ql);
  const inSec = (k) => (k === "archived" ? [APPROVED_EXAMPLE, REJECTED_EXAMPLE, ...ALL.filter((x) => arch.includes(x.id))] : ALL.filter((x) => x.sec === k && !arch.includes(x.id))).filter(matchQ);
  const SECTIONS = [["needsyou", "Needs you"], ["alerts", "Alerts"], ["insights", "Insights"], ["archived", "Archived"]];

  const emptyText = (k) =>
    k === "archived" ? "Nothing archived yet. Swipe a brief left to tuck it away here." :
    "You're all caught up.";

  const newBrief = () => {onClose();nav.ask("Set up a new brief.", "Sure, tell me what you'd like to keep an eye on. I can send a market recap on a schedule, ping you when a holding crosses a price, flag unusual spending, or summarize how an automation is running. So what should this brief watch, and how often?");};

  return (
    <>
      <div style={{ padding: "12px 18px 2px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "9px 12px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 999 }}>
          <Icon name="search" size={16} color="var(--ink-3)" stroke={1.6} />
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search briefs"
            style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13.5 }} />
          {q && <button className="press" onClick={() => setQ("")} aria-label="Clear search" style={{ flex: "none", background: "none", border: "none", display: "flex", color: "var(--ink-3)", cursor: "pointer" }}><Icon name="close" size={15} /></button>}
        </div>
      </div>
      {SECTIONS.map(([k, label]) => {
        const items = inSec(k);
        const on = secs[k];
        return (
          <div key={k} style={{ paddingTop: 6 }}>
            <button className="press" onClick={() => toggle(k)} style={{ width: "100%", display: "flex", alignItems: "baseline", gap: 8, padding: "16px 18px 10px", background: "none", border: "none", cursor: "pointer", textAlign: "left" }}>
              <span style={{ fontFamily: "var(--f-display)", fontSize: window.__YOSHI_WEB ? 17 : 14, fontWeight: 700, letterSpacing: "-0.005em" }}>{label}</span>
              <span style={{ fontFamily: "var(--f-mono)", fontSize: 12.5, fontWeight: 600, color: "var(--accent)" }}>{items.length}</span>
              <Icon name="down" size={16} color="var(--ink-3)" style={{ marginLeft: "auto", transform: on ? "rotate(180deg)" : "none", transition: "transform 180ms ease" }} />
            </button>
            {on && (items.length ? (
              <div>
                {items.map((b, i) => (
                  <SwipeRow key={b.id} archived={k === "archived"} last={i === items.length - 1} onAction={() => archive(b.id)}>
                    <BriefRow b={b} last={i === items.length - 1} selected={b.id === selectedId} onOpen={k === "needsyou" && !window.__YOSHI_WEB ? (x) => onApprove && onApprove(x.id) : onOpen} />
                  </SwipeRow>
                ))}
              </div>
            ) : (
              <div style={{ padding: "0 18px 16px 30px", fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5 }}>
                {emptyText(k)}
              </div>
            ))}
          </div>);
      })}

      <div style={{ height: 6 }} />
      <div style={{ padding: "18px 18px 0" }}>
        <button className="press" onClick={newBrief} style={{
          width: "100%", textAlign: "left", display: "flex", alignItems: "center", gap: 11, padding: "12px 14px",
          background: "var(--bg-card)", border: "1px dashed var(--rule-2)", borderRadius: 12, cursor: "pointer"
        }}>
          <span style={{ width: 30, height: 30, flex: "none", borderRadius: 999, border: "1px solid var(--rule-2)", display: "grid", placeItems: "center", color: "var(--ink-2)" }}>
            <Icon name="plus" size={16} stroke={1.7} />
          </span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 600 }}>Set up a new brief</div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", marginTop: 3 }}>Tell Yoshi what to watch: a recap, a price alert, a spend flag</div>
          </div>
          <Icon name="back" size={15} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
        </button>
      </div>
    </>);

};

const BriefsHub = ({ onClose, nav, proposals = [], onApprove, initialBriefId, initialBrief }) => {
  // deep-link target: the home "Today's read" tile opens this brief's detail
  // directly, and Back returns to the bell inbox (the board below).
  const refreshedAt = (() => { try { return parseInt(localStorage.getItem("yoshi_brief_refreshed_at") || "0", 10) || Date.now(); } catch (e) { return Date.now(); } })();
  const asOf = new Date(refreshedAt).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
  const dailyBrief = window.MORNING_BRIEF ? { id: "daily-brief", kind: "insight", icon: "bulb", title: "Today's read", body: window.MORNING_BRIEF.body, ask: window.MORNING_BRIEF.ask, when: "Last updated " + asOf } : null;
  const [open, setOpen] = useState(() => initialBrief || (initialBriefId === "daily-brief" ? dailyBrief : null));
  const feed = BRIEF_FEED();
  const idx = open ? feed.findIndex((x) => x.id === open.id) : -1;
  const hasNext = idx >= 0 && idx < feed.length - 1;
  const advance = () => { if (hasNext) setOpen(feed[idx + 1]); else setOpen(null); };
  return (
    <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Briefs">
      {window.StatusBar && <window.StatusBar />}
      <NavBar title="Briefs" border={false} left={<YouButton nav={nav} />}
      right={<button className="press" onClick={onClose} style={{ background: "none", border: "none", color: "var(--ink-3)", display: "flex" }}><Icon name="close" size={20} /></button>} />

      <div className="scroll" style={{ padding: "0 0 24px" }}>
        <BriefsBoard nav={nav} onOpen={setOpen} onClose={onClose} proposals={proposals} onApprove={onApprove} />
      </div>

      {open && <BriefDetail b={open} onBack={() => setOpen(null)} onClose={onClose} nav={nav} onAdvance={advance} hasNext={hasNext} />}
    </div>);

};

/* the bell sheet — recent brief alerts, with a tap-through to the full Stream */
const AlertsSheet = ({ onClose, nav, onSeeAll }) => {
  const [open, setOpen] = useState(null);
  const [cleared, setCleared] = useState([]);
  const alerts = BRIEFS.filter((b) => b.folder === "alerts" && (b.type === "recap" || b.type === "alert" || b.type === "report")).map((b) => ({ ...b, kind: "alert", icon: "bell" }));
  const shown = alerts.filter((b) => !cleared.includes(b.id));
  return (
    <>
      <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)", zIndex: 300, animation: "scrim-in 200ms ease both" }} />
      <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 301, background: "var(--bg)", borderTop: "1px solid var(--accent)", maxHeight: "82%", display: "flex", flexDirection: "column", animation: "sheet-in 320ms cubic-bezier(0.16,1,0.30,1) both" }}>
        <div style={{ display: "flex", justifyContent: "center", paddingTop: 8, flex: "none" }}><span style={{ width: 36, height: 4, background: "var(--rule-2)", borderRadius: 999 }} /></div>
        <div style={{ padding: "10px 20px 10px", display: "flex", alignItems: "baseline", gap: 9, flex: "none", borderBottom: "1px solid var(--rule)" }}>
          <span style={{ fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em" }}>Alerts</span>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 11.5, color: "var(--ink-3)" }}>{shown.length} new</span>
          <button className="press" onClick={onClose} style={{ marginLeft: "auto", background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>
        </div>
        <div className="scroll" style={{ padding: "0 0 8px" }}>
          {shown.length ? shown.map((b, i) => (
            <SwipeRow key={b.id} last={i === shown.length - 1} label="Clear" icon="close" onAction={() => setCleared((c) => [...c, b.id])}>
              <BriefRow b={b} onOpen={setOpen} />
            </SwipeRow>
          )) : (
            <div style={{ padding: "40px 24px", textAlign: "center", fontFamily: "var(--f-display)", fontSize: 13, color: "var(--ink-3)" }}>You're all caught up.</div>
          )}
          <div style={{ padding: "14px 18px 18px" }}>
            <button className="press" onClick={() => { onClose(); onSeeAll && onSeeAll(); }} style={{ width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 6, padding: "11px 0", background: "none", border: "1px solid var(--rule-2)", borderRadius: 10, cursor: "pointer", fontFamily: "var(--f-display)", fontSize: 12.5, fontWeight: 600, color: "var(--ink-2)" }}>
              See all briefs in Stream <Icon name="back" size={14} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
            </button>
          </div>
        </div>
      </div>
      {open && <BriefDetail b={open} onBack={() => setOpen(null)} onClose={onClose} nav={nav} />}
    </>);

};

Object.assign(window, { BriefsHub, BriefsBoard, BriefDetail, AlertsSheet, BRIEF_FEED });