/* home.jsx, the dashboard. Reads top→bottom:
   header · net worth + live sparkline · allocation · quick actions ·
   what's pending (top proposal) · recent activity · automations. */

/* net-worth across horizons. 1D reuses the live intraday series (so the
   agent-action markers line up); the rest are generated to hit a plausible
   period return ending at today's balance. */
const NW_RANGES = ["1D", "1M", "6M", "YTD", "1Y", "5Y", "All"];
const NW_META = {
  "1D": { pct: DAY_PCT, label: "today", len: 34, vol: 0.0016, markers: true },
  "1M": { pct: 3.6, label: "past month", len: 32, vol: 0.007, seed: 222 },
  "6M": { pct: 14.6, label: "past 6 months", len: 48, vol: 0.014, seed: 226 },
  "YTD": { pct: 9.8, label: "year to date", len: 44, vol: 0.013, seed: 232 },
  "1Y": { pct: 22.4, label: "past year", len: 52, vol: 0.017, seed: 230 },
  "5Y": { pct: 96.2, label: "past 5 years", len: 64, vol: 0.03, seed: 244 },
  "All": { pct: 142.0, label: "all time", len: 72, vol: 0.034, seed: 240 }
};
const buildNW = (range) => {
  const m = NW_META[range];
  if (range === "1D") return { data: NW_SERIES, abs: DAY_ABS, pct: DAY_PCT, label: m.label, markers: NW_MARKERS };
  const start = NET_WORTH / (1 + m.pct / 100);
  return { data: series(m.seed, m.len, start, NET_WORTH, m.vol), abs: NET_WORTH - start, pct: m.pct, label: m.label, markers: [] };
};

const ACTIVITY_FILTERS = [
["all", "All"], ["automations", "Automations"], ["spending", "Spending"],
["investments", "Investments"], ["transfers", "Transfers"], ["income", "Income"]];


/* forward-looking schedule — shown above recent activity in one endless feed */
const COMING_UP = [
{ icon: "bolt", cat: "automations", autoId: "a1", title: "Weekly investing · VTI", sub: "Automation", when: "Fri · Jun 5", amount: 500, tone: "out" },
{ icon: "card", cat: "spending", title: "Netflix", sub: "Subscription · Yoshi card ••4417", when: "Sun · Jun 8", amount: 24.99, tone: "out" },
{ icon: "bank", cat: "income", title: "Pay day", sub: "Direct deposit to Yoshi brokerage ••3456", when: "Mon · Jun 15", amount: 8200, tone: "in" }];


/* clearer feed section header — sentence case, legible, replaces the tiny
   tracked eyebrow so category titles read at a glance. */
const SecTitle = ({ children, color = "var(--ink)" }) =>
<div style={{ fontFamily: "var(--f-display)", fontSize: window.__YOSHI_WEB ? 17 : 14, fontWeight: 700, letterSpacing: "-0.005em", color, display: "inline-flex", alignItems: "center" }}>{children}</div>;


/* ---- Yoshi's morning read ------------------------------------------------ */
const Cite = ({ n }) =>
<sup style={{ fontFamily: "var(--f-display)", fontSize: 9, fontWeight: 700, color: "var(--accent)", marginLeft: 2, letterSpacing: 0 }}>{n}</sup>;


const BRIEF_DISMISS_KEY = "yoshi_brief_dismissed";
const TODAY_KEY = new Date().toISOString().slice(0, 10);

const BRIEF_REFRESH_KEY = "yoshi_brief_refreshed_at";
const BRIEF_COOLDOWN_MS = 4 * 60 * 60 * 1000; // refresh on open, but no more than every 4h

/* indeterminate "generating" loader — living amber blobs that morph, drift,
   and lean toward your finger/cursor while Yoshi writes the brief. */
const BriefBlob = () => {
  const [nudge, setNudge] = useState({ x: 0, y: 0 });
  const ref = useRef(null);
  const mark = (typeof window !== "undefined" && window.__resources && window.__resources.logoBlack) || "assets/logo-mark-black.png";
  const onMove = (e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const pt = e.touches ? e.touches[0] : e;
    const dx = (pt.clientX - (r.left + r.width / 2)) / (r.width / 2);
    const dy = (pt.clientY - (r.top + r.height / 2)) / (r.height / 2);
    const cl = (v) => Math.max(-1, Math.min(1, v));
    setNudge({ x: cl(dx) * 9, y: cl(dy) * 6 });
  };
  const reset = () => setNudge({ x: 0, y: 0 });
  return (
    <div ref={ref} onPointerMove={onMove} onPointerLeave={reset} onTouchMove={onMove} onTouchEnd={reset}
      style={{ position: "relative", height: 56, display: "grid", placeItems: "center", touchAction: "none", cursor: "crosshair" }}>
      <div style={{ transform: `translate(${nudge.x}px, ${nudge.y}px)`, transition: "transform 260ms cubic-bezier(0.16,1,0.30,1)" }}>
        <div style={{ position: "relative", width: 48, height: 48 }}>
          {/* soft amber aura — organic morph behind the mark */}
          <div style={{ position: "absolute", inset: -2, background: "var(--accent)", opacity: 0.2, filter: "blur(7px)", animation: "yo-blob-morph 5s ease-in-out infinite, yo-blob-breathe 2.8s ease-in-out infinite" }} />
          {/* the Yoshi mark, recolored amber via mask, gently breathing */}
          <div style={{ position: "absolute", inset: 5, background: "var(--accent)",
            WebkitMaskImage: `url("${mark}")`, maskImage: `url("${mark}")`,
            WebkitMaskRepeat: "no-repeat", maskRepeat: "no-repeat",
            WebkitMaskPosition: "center", maskPosition: "center",
            WebkitMaskSize: "contain", maskSize: "contain",
            animation: "yo-blob-breathe 2.6s ease-in-out infinite" }} />
        </div>
      </div>
    </div>
  );
};

const MorningRead = ({ nav }) => {
  const [dismissed, setDismissed] = useState(() => {
    try { return localStorage.getItem(BRIEF_DISMISS_KEY) === TODAY_KEY; } catch (e) { return false; }
  });

  // generating shimmer on open — Yoshi "writes" the brief, then it resolves
  const [generating, setGenerating] = useState(true);
  useEffect(() => {
    const t = setTimeout(() => setGenerating(false), 2000);
    return () => clearTimeout(t);
  }, []);

  // "as of" timestamp — stamps on app open, but holds for a 4-hour cooldown
  const [refreshedAt] = useState(() => {
    const now = Date.now();
    try {
      const prev = parseInt(localStorage.getItem(BRIEF_REFRESH_KEY) || "0", 10);
      if (prev && now - prev < BRIEF_COOLDOWN_MS) return prev;
      localStorage.setItem(BRIEF_REFRESH_KEY, String(now));
    } catch (e) {}
    return now;
  });
  const asOf = new Date(refreshedAt).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });

  const hl = (window.HOLDINGS || []).filter((h) => h.kind !== "crypto");
  const crypto = (window.HOLDINGS || []).filter((h) => h.kind === "crypto");
  const top = [...hl].sort((a, b) => Math.abs(b.dch) - Math.abs(a.dch))[0];
  const btc = crypto.find((h) => h.ticker === "BTC") || crypto[0];
  const dayAbs = (window.HOLDINGS || []).reduce((s, h) => s + h.value * h.dch / 100, 0);
  const totalVal = (window.HOLDINGS || []).reduce((s, h) => s + h.value, 0);
  const dayPct = totalVal ? dayAbs / totalVal * 100 : 0;
  const up = dayAbs >= 0;
  const topUp = top && top.dch >= 0;

  const s1 = top ? `Your portfolio ${up ? "gained" : "lost"} ${usd(Math.abs(dayAbs), 0)} today (${Math.abs(dayPct).toFixed(1)}%). ${top.ticker} was your ${topUp ? "biggest winner" : "biggest drag"}, ${topUp ? "up" : "down"} ${Math.abs(top.dch).toFixed(1)}%.` : null;
  const s2 = top ? top.ticker === "NVDA" ? "Data center demand is the main driver. Analysts bumped their estimates higher after last week's earnings." : top.ticker === "AAPL" ? "Services revenue beat expectations and the Street raised its price targets." : top.ticker === "BTC" ? "Spot ETF inflows have been positive for eight straight sessions." : `${top.name.split(" ")[0]} moved with broader sector momentum today.` : null;
  const s3 = btc ? `Your crypto is ${btc.dch >= 0 ? "roughly flat to positive" : "slightly lower"} today. ${btc.ticker} is ${btc.dch >= 0 ? "up" : "down"} ${Math.abs(btc.dch).toFixed(1)}% with no major news behind it.` : null;

  const body = [s1, s2, s3].filter(Boolean).join(" ");
  // expose to window so BriefsBoard can show it under Insights
  window.MORNING_BRIEF = { body, ask: ["Give me more detail on my portfolio moves today.", `${body} Want me to dig into any position, or flag anything worth acting on?`] };

  const dismiss = () => { try { localStorage.setItem(BRIEF_DISMISS_KEY, TODAY_KEY); } catch (e) {} setDismissed(true); };
  const ask = () => nav.ask(window.MORNING_BRIEF.ask[0], window.MORNING_BRIEF.ask[1]);

  const [dragX, setDragX] = useState(0);
  const [flying, setFlying] = useState(false);
  const touchStart = useRef(null);
  const dragged = useRef(false);
  const open = () => { if (!dragged.current && !generating) nav.sheet({ type: "briefs", brief: "daily-brief" }); };

  const onTouchStart = (e) => { touchStart.current = e.touches[0].clientX; dragged.current = false; };
  const onTouchMove  = (e) => {
    if (touchStart.current == null) return;
    const dx = e.touches[0].clientX - touchStart.current;
    if (Math.abs(dx) > 6) dragged.current = true;
    setDragX(dx);
  };
  const onTouchEnd   = () => {
    if (Math.abs(dragX) > 80) {
      const dir = dragX > 0 ? 1 : -1;
      setFlying(true);
      setDragX(dir * 420);
      setTimeout(dismiss, 280);
    } else {
      setDragX(0);
    }
    touchStart.current = null;
  };

  const cardStyle = {
    transform: `translateX(${dragX}px)`,
    opacity: flying ? 0 : Math.max(0, 1 - Math.abs(dragX) / 180),
    transition: dragX === 0 || flying ? "transform 280ms cubic-bezier(0.16,1,0.30,1), opacity 280ms ease" : "none",
  };

  if (dismissed) return null;

  return (
    <section style={{ padding: "4px 18px 10px", overflow: "hidden" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: window.__YOSHI_WEB ? 17 : 14, fontWeight: 700, letterSpacing: "-0.01em" }}>Today's read</div>
      </div>
      <div
        className="press" role="button" onClick={open}
        onTouchStart={generating ? undefined : onTouchStart} onTouchMove={generating ? undefined : onTouchMove} onTouchEnd={generating ? undefined : onTouchEnd}
        style={{ ...cardStyle, border: "1px solid var(--rule-2)", borderRadius: 12, padding: "14px 16px 11px", background: "var(--bg-card)", cursor: generating ? "default" : "pointer" }}>
        {generating ? (
          <>
            <BriefBlob />
            <div style={{ textAlign: "center", marginTop: 4, fontFamily: "var(--f-mono)", fontSize: 10, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--ink-3)", animation: "yo-shimmer 1.6s ease-in-out infinite" }}>Powering up</div>
          </>
        ) : (
          <>
        <p style={{ fontFamily: "var(--f-display)", fontSize: 13.5, lineHeight: 1.65, color: "var(--ink)", margin: 0 }}>
          {s1} {s2}<Cite n="1" /> {s3 && <>{s3}<Cite n="2" /></>}
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 9.5, letterSpacing: "0.02em", color: "var(--ink-3)", whiteSpace: "nowrap" }}>{"\u2002"}· AS OF {asOf}</span>
        </p>
          </>
        )}
      </div>
    </section>);
};

const Home = ({ nav, proposals, completed, onApprove, onSeeStream, onBell }) => {
  const [marker, setMarker] = useState(null);
  const [scrubVal, setScrubVal] = useState(null);
  const [page, setPage] = useState(0);
  const [range, setRange] = useState("1D");

  const nw = useMemo(() => buildNW(range), [range]);

  const display = scrubVal != null ? scrubVal : NET_WORTH;

  return (
    <div style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
      {/* header strip — same geometry as NavBar so the bell sits identically across tabs */}
      <div style={{ flex: "none", padding: "6px 16px 10px", display: "flex", alignItems: "center", gap: 10, minHeight: 44 }}>
        <YouButton nav={nav} />
        <span style={{ marginLeft: "auto" }}><window.BellButton nav={nav} onClick={onBell} /></span>
      </div>

      <div className="scroll">
        {/* BAND 1 · net worth + sparkline */}
        <section style={{ padding: window.__YOSHI_WEB ? "22px 18px 4px" : "6px 18px 4px" }}>
          <Eyebrow style={window.__YOSHI_WEB ? { fontSize: 14, letterSpacing: "0.1em" } : undefined}>{scrubVal != null ? "Earlier" : "Yoshi balance"}</Eyebrow>
          <div style={{ marginTop: 5, lineHeight: 1 }}>
            <Money value={display} size={38} weight={500} />
          </div>
          <div style={{ marginTop: 7, display: "flex", alignItems: "baseline", gap: 7 }}>
            <Delta abs={nw.abs} pct={nw.pct} size={13} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)" }}>{nw.label.charAt(0).toUpperCase() + nw.label.slice(1)}</span>
          </div>

          {window.__YOSHI_WEB && window.WebNetWorthChart ? (
            <window.WebNetWorthChart range={range} ranges={NW_RANGES} nw={nw} nav={nav}
              setRange={(r) => { setRange(r); setMarker(null); setScrubVal(null); }}
              onScrub={(v) => setScrubVal(v)} />
          ) : (
          <>
          <div style={{ marginTop: -2 }}>
            {(() => {
              const chartColor = nw.data[nw.data.length - 1] >= nw.data[0] ? "var(--accent-pos)" : "var(--signal-neg)";
              return <Chart data={nw.data} height={70} accent={chartColor} fillColor={chartColor} markers={nw.markers} activeMarker={marker} onPickMarker={setMarker}
                scrub onScrub={(idx) => setScrubVal(idx == null ? null : nw.data[idx])} padY={4} />;
            })()}
          </div>

          {/* horizon toggle, understated text buttons, no heavy chrome */}
          <div style={{ display: "flex", gap: 3, marginTop: 2 }}>
            {NW_RANGES.map((r) => {
              const on = r === range;
              return (
                <button key={r} className="press" onClick={() => {setRange(r);setMarker(null);setScrubVal(null);}} style={{
                  flex: 1, padding: "3px 0", background: on ? "var(--bg-2)" : "transparent", border: "none", borderRadius: 7,
                  fontFamily: "var(--f-mono)", fontSize: 11, fontWeight: 500, letterSpacing: "0.02em",
                  color: on ? "var(--ink)" : "var(--ink-3)", cursor: "pointer", fontVariantNumeric: "tabular-nums"
                }}>{r}</button>);

            })}
          </div>
          </>
          )}

          {/* marker tooltip, tap to open the automation in Stream */}
          {marker != null && nw.markers[marker] &&
          <div style={{ marginTop: 10 }}>
              <button className="press" onClick={() => nw.markers[marker].autoId && nav.automation(nw.markers[marker].autoId)} style={{
              width: "100%", textAlign: "left", padding: "9px 11px", background: "var(--bg-2)", border: "none", borderRadius: 10,
              display: "flex", alignItems: "center", gap: 8, cursor: "pointer"
            }}>
                <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--accent)" }}>{nw.markers[marker].time}</span>
                <span style={{ fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600 }}>{nw.markers[marker].name}</span>
                <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 5, fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)" }}>
                  {nw.markers[marker].detail}
                  <Icon name="back" size={14} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
                </span>
              </button>
            </div>
          }
        </section>

        {/* allocation row */}
        <Allocation nav={nav} />

        {/* quick actions — web has these in the top bar */}
        {!window.__YOSHI_WEB &&
        <section style={{ padding: "4px 18px 4px", display: "flex", justifyContent: "center", gap: 10 }}>
          <QuickAction icon="trade" label="Trade" onClick={() => nav.sheet({ type: "trade" })} />
          <QuickAction icon="swap" label="Transfer" onClick={() => nav.sheet({ type: "transfer" })} />
          <QuickAction icon="easel" label="Agents" onClick={() => nav.sheet({ type: "connect" })} />
        </section>}

        {/* BAND 2 · pending, a scroll-snap carousel of proposals */}
        <section style={{ padding: "6px 0 6px" }}>
          <div style={{ display: "flex", alignItems: "baseline", marginBottom: 5, padding: "0 18px" }}>
            {proposals.length === 0 ?
            <SecTitle color="var(--accent-pos)"><Icon name="check" size={13} stroke={2.2} color="var(--accent-pos)" style={{ display: "inline-block", verticalAlign: "-2px" }} /> &nbsp;You're caught up</SecTitle> :
            <SecTitle color="var(--accent)"><LiveDot /> &nbsp;Needs you</SecTitle>}
          </div>
          {proposals.length === 0 ? null : proposals.length === 1 ?
          <div style={{ padding: "0 18px" }}><PendingCard p={proposals[0]} onReview={() => onApprove(proposals[0].id)} /></div> :

          <>
              <PendingWheel key={proposals.length} proposals={proposals} onReview={onApprove} onActive={setPage} />
              <div style={{ display: "flex", justifyContent: "center", gap: 6, marginTop: 6 }}>
                {proposals.map((_, i) =>
              <span key={i} style={{ width: i === Math.min(page, proposals.length - 1) ? 16 : 6, height: 6, borderRadius: 999, background: i === Math.min(page, proposals.length - 1) ? "var(--accent)" : "var(--rule-2)", transition: "width 200ms var(--ease), background 200ms" }} />
              )}
              </div>
            </>
          }
        </section>

        {/* MORNING READ */}
        <MorningRead nav={nav} />

        {/* BAND 3 · Stream — on web the stream lives in its own right pillar
              (StreamPanel, mounted by web.jsx); inline here on mobile only. */}
        {!window.__YOSHI_WEB && <StreamPanel nav={nav} completed={completed} onSeeStream={onSeeStream} />}
        <div style={{ height: 14 }} />
      </div>
    </div>);

};

const QuickAction = ({ icon, logo, label, onClick }) =>
<button className="press" onClick={onClick} style={{
  background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: 10, padding: "9px 6px 8px",
  width: 92, flex: "none",
  display: "flex", flexDirection: "column", alignItems: "center", gap: 5, cursor: "pointer"
}}>
    {logo ? <Logo size={19} /> : <Icon name={icon} size={19} stroke={1.5} color="var(--ink)" />}
    <span style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, letterSpacing: "0.01em" }}>{label}</span>
  </button>;


const Allocation = ({ nav }) => {
  const segs = [
  ["Cash", CASH_TOTAL, "var(--alloc-cash)", false, "cash"],
  ["Stocks", INVEST_TOTAL, "var(--alloc-invest)", false, "investments"],
  ["Crypto", CRYPTO_TOTAL, "var(--alloc-crypto)", false, "investments"]];

  const total = segs.reduce((s, x) => s + x[1], 0);
  return (
    <section style={{ padding: "4px 18px 9px" }}>
      <div style={{ display: "flex", height: window.__YOSHI_WEB ? 26 : 6, gap: 2, marginBottom: window.__YOSHI_WEB ? 9 : 6, borderRadius: window.__YOSHI_WEB ? 7 : 0, overflow: "hidden" }}>
        {segs.map(([l, v, c]) => <div key={l} style={{ background: c, width: `${v / total * 100}%`, display: window.__YOSHI_WEB ? "flex" : "block", alignItems: "center", justifyContent: "center" }}>{window.__YOSHI_WEB && <span style={{ fontFamily: "var(--f-mono)", fontSize: 10.5, fontWeight: 700, color: "#14130f", fontVariantNumeric: "tabular-nums", letterSpacing: "-0.01em", whiteSpace: "nowrap" }}>{usd(v, 0)}</span>}</div>)}
      </div>
      <div style={{ display: "flex", gap: 2 }}>
        {segs.map(([l, v, c, debt, sv]) =>
        <button key={l} className="press" onClick={() => nav.studio(sv)}
        style={{ background: "none", border: "none", cursor: "pointer", width: `${v / total * 100}%`, display: "flex", flexDirection: "column", alignItems: "center", padding: 0 }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 5 }}>
              <span style={{ width: 7, height: 7, background: c, flex: "none" }} />
              <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-2)" }}>{(v / total * 100).toFixed(0)}%</span>
              <span style={{ fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600, color: "var(--ink-2)" }}>{l}</span>
            </div>
            {!window.__YOSHI_WEB && <span style={{ fontFamily: "var(--f-mono)", fontSize: 10, color: "var(--ink-3)", marginTop: 2 }}>{usd(v, 2)}</span>}
          </button>
        )}
      </div>
    </section>);

};

const PendingCard = ({ p, onReview, fill }) => {
  const web = window.__YOSHI_WEB;
  return (
<div style={{ border: "1px solid var(--accent)", background: "var(--bg-card)", padding: web ? "15px 17px 16px" : "11px 12px 12px", borderRadius: 12, boxShadow: "0 0 0 4px color-mix(in srgb, var(--accent) 13%, transparent)", width: "100%", height: fill ? "100%" : undefined, boxSizing: "border-box", display: "flex", flexDirection: "column" }}>
    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
      <span style={{ width: 6, height: 6, borderRadius: 999, flex: "none", background: /^yoshi/i.test(p.agent) ? "var(--accent)" : "transparent", border: /^yoshi/i.test(p.agent) ? "none" : "1.5px solid var(--ink-3)" }} />
      <span style={{ fontFamily: "var(--f-display)", fontSize: web ? 10.5 : 9, fontWeight: 600, letterSpacing: "0.06em", color: "var(--ink-3)" }}>{p.agent}</span>
    </div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: web ? 19 : 15, fontWeight: 600, letterSpacing: "-0.02em", marginTop: web ? 9 : 6 }}>{p.title}</div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: web ? 14 : 11.5, color: "var(--ink-2)", marginTop: web ? 7 : 4, lineHeight: web ? 1.5 : 1.45 }}>{p.why}</div>
    <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginTop: "auto", paddingTop: web ? 11 : 8, borderTop: "1px dashed var(--rule)" }}>
      <span style={{ fontFamily: "var(--f-display)", fontSize: web ? 10 : 9, fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-3)" }}>Net</span>
      <Money value={p.net} size={web ? 15 : 13} sign color={p.net >= 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
    </div>
    <Btn full onClick={onReview} style={{ marginTop: web ? 12 : 9, borderRadius: 9, fontSize: web ? 14.5 : 14 }}>Review <Icon name="arrow" size={15} color="var(--accent-ink)" /></Btn>
  </div>);
};


/* ---- pending wheel · a flat 2D carousel of proposals ----------------------
   Cards slide horizontally; drag to scroll, neighbours peek in slightly scaled
   and dimmed at the edges, the active card sits centred. Snaps on release. */
const WHEEL_W = 264;
/* A native horizontal scroll-snap carousel, scroll/swipe left or right to move
   between proposals; each card snaps to centre. Neighbours peek, scaled + dimmed
   by their distance from centre. Cards stretch to equal height so the CTA always fits. */
const PendingWheel = ({ proposals, onReview, onActive }) => {
  const N = proposals.length;
  const web = window.__YOSHI_WEB;
  const CARD_W = web ? 318 : WHEEL_W; // wider, squarer tiles on web
  const ref = useRef(null);
  const [sx, setSx] = useState(0);
  const STRIDE = CARD_W + 14;

  const onScroll = () => {
    const el = ref.current;if (!el) return;
    setSx(el.scrollLeft);
    onActive && onActive(Math.max(0, Math.min(N - 1, Math.round(el.scrollLeft / STRIDE))));
  };

  return (
    <div ref={ref} className="hcar" onScroll={onScroll}
    style={{ display: "flex", gap: 14, overflowX: "auto", scrollSnapType: "x mandatory", alignItems: "stretch", padding: web ? "8px 18px 8px 56px" : `8px calc(50% - ${CARD_W / 2}px)`, WebkitOverflowScrolling: "touch" }}>
      {proposals.map((p, i) => {
        const off = i - sx / STRIDE;
        const ao = Math.min(1, Math.abs(off));
        return (
          <div key={p.id} style={{
            flex: "none", width: CARD_W, scrollSnapAlign: web ? "start" : "center",
            transform: `scale(${1 - ao * 0.07})`, opacity: 1 - ao * (web ? 0.62 : 0.42),
            transition: "transform 160ms linear, opacity 160ms linear"
          }}>
            <PendingCard p={p} onReview={() => onReview(p.id)} fill />
          </div>);

      })}
    </div>);

};

const NothingPending = ({ last }) =>
<div style={{ border: "1px solid var(--rule)", background: "var(--bg-2)", padding: "14px" }}>
    <Eyebrow>All clear · last move</Eyebrow>
    <div style={{ fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, marginTop: 5 }}>{last.title}</div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-2)", marginTop: 3 }}>{last.detail} · {last.when}</div>
  </div>;


/* ---- coming-up row (forward-looking schedule, no icon) ------------------- */
/* ---- leading category icon · helps scan the feed by type -----------------
   Agent/automation rows carry the Yoshi mark; everything else gets a schematic
   category glyph in a quiet hairline tile. */
const RowIcon = ({ icon, auto }) => {
  const yoshi = auto || 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 || "receipt"} size={17} stroke={1.5} color="var(--ink-2)" />}
    </span>);

};

const ComingRow = ({ c, last, nav, onDetail }) => {
  const isAuto = !!c.autoId;
  const open = () => {
    // web Stream pillar: everything opens in-pillar (full detail)
    if (onDetail) { onDetail(isAuto ? { kind: "auto", id: c.autoId } : { kind: "txn", tx: c }); return; }
    isAuto ? nav.sheet({ type: "automation", id: c.autoId }) : nav.sheet({ type: "txn", tx: c });
  };
  return (
    <button className="press" onClick={open} style={{ width: "100%", textAlign: "left", background: "none", border: "none", display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 11, alignItems: "center", padding: "9px 0", cursor: "pointer", borderBottom: last ? "none" : "1px solid var(--rule)" }}>
      <RowIcon icon={c.icon} auto={isAuto} />
      <div style={{ minWidth: 0 }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.title}</div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.sub}</div>
      </div>
      <div style={{ textAlign: "right", whiteSpace: "nowrap" }}>
        <Money value={c.tone === "out" ? -c.amount : c.amount} size={12.5} sign={c.tone === "in"} color={c.tone === "in" ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
        <div style={{ fontFamily: "var(--f-mono)", fontSize: 9.5, color: "var(--ink-3)", marginTop: 3 }}>{c.when}</div>
      </div>
    </button>);

};

/* ---- recent-activity transaction row (flat ledger, no pill) --------------- */
const TxnRow = ({ t, last, nav, onDetail }) =>
<button className="press" onClick={() => onDetail ? onDetail({ kind: "txn", tx: t }) : nav.sheet({ type: "txn", tx: t })} style={{ width: "100%", textAlign: "left", background: "none", border: "none", display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 11, alignItems: "center", padding: "9px 0", cursor: "pointer", borderBottom: last ? "none" : "1px solid var(--rule)" }}>
    <RowIcon icon={t.icon} />
    <div style={{ minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{t.title}</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{t.detail}</div>
    </div>
    <div style={{ textAlign: "right", whiteSpace: "nowrap" }}>
      {t.net !== 0 ?
    <Money value={t.net} size={12.5} sign color={t.net > 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" /> :
    <span style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)" }}>moved</span>}
      <div style={{ fontFamily: "var(--f-mono)", fontSize: 9.5, color: "var(--ink-3)", marginTop: 3 }}>{t.when}</div>
    </div>
  </button>;

/* ---- transaction / scheduled-item detail · a receipt-style bottom sheet --- */
const TXN_CATEGORY = { receipt: "Spending", down: "Income", swap: "Transfer", trade: "Investment", bolt: "Automation" };

const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

/* Past activity for this merchant / transaction type — a short statement of
   prior occurrences. Recurring charges repeat at the same amount; variable
   merchants vary a little (deterministic per merchant, so it stays stable). */
const txHistory = (tx, amount) => {
  const recurring = /subscription|autopay|membership|recurring/i.test(`${tx.detail || ""} ${tx.sub || ""}`);
  const baseM = (() => { const i = MONTHS.findIndex((m) => new RegExp(m, "i").test(tx.when || "")); return i >= 0 ? i : 4; })();
  const dayM = (tx.when || "").match(/\b(\d{1,2})\b/);
  const day = dayM ? dayM[1] : "8";
  const seed = [...(tx.title || "x")].reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 7);
  const mag = Math.abs(amount) || 0;
  const rows = [];
  for (let i = 1; i <= 3; i++) {
    const mi = ((baseM - i) % 12 + 12) % 12;
    const jitter = recurring ? 0 : (((seed >> (i * 4)) % 31) - 15) / 100; // ±15%
    rows.push({ when: `${MONTHS[mi]} ${day}`, amt: Math.round(mag * (1 + jitter) * 100) / 100 });
  }
  return { recurring, rows, mag };
};

const TxnHistory = ({ tx, amount }) => {
  const { recurring, rows, mag } = txHistory(tx, amount);
  if (!rows.length) return null;
  const neg = amount < 0;
  return (
    <div style={{ marginTop: 22 }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginBottom: 11 }}>
        <Eyebrow>Past activity</Eyebrow>
        <span style={{ marginLeft: "auto", fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)" }}>{recurring ? "Recurring · monthly" : "Recent"}</span>
      </div>
      <div style={{ border: "1px solid var(--rule)" }}>
        {rows.map((r, i) =>
        <div key={i} style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", padding: "11px 14px", borderBottom: i === rows.length - 1 ? "none" : "1px dashed var(--rule)" }}>
          <span style={{ fontFamily: "var(--f-display)", fontSize: 13, color: "var(--ink-2)" }}>{r.when}</span>
          {mag > 0 ?
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 13, color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>{neg ? "−" : "+"}{usd(r.amt)}</span> :
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-3)", letterSpacing: "0.04em", textTransform: "uppercase" }}>Ran</span>}
        </div>
        )}
      </div>
    </div>);

};

const TxnDetailSheet = ({ tx, onClose, nav }) => {
  const upcoming = tx.net == null;
  const amount = tx.net != null ? tx.net : tx.tone === "out" ? -tx.amount : tx.amount;
  const category = TXN_CATEGORY[tx.icon] || (tx.cat ? tx.cat.charAt(0).toUpperCase() + tx.cat.slice(1) : "Activity");
  const isTransfer = tx.icon === "swap";
  const method = tx.detail || tx.sub || "";
  const ask = () => {onClose();nav.ask(`Tell me about this ${upcoming ? "scheduled item" : "transaction"}: ${tx.title}.`, `Here's what I have on "${tx.title}" — ${upcoming ? "it's scheduled" : "it settled"} ${tx.when}, ${amount >= 0 ? "a credit" : "a debit"} of ${usd(Math.abs(amount))} via ${method || category}. Want me to find related activity, set an alert, or categorize it differently?`);};
  return (
    <>
      {!window.__YOSHI_WEB && <div className="yo-focus-backdrop" onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)", zIndex: 300 }} />}
      <div style={window.__YOSHI_WEB ? { position: "absolute", inset: 0, zIndex: 301, background: "var(--bg)", display: "flex", flexDirection: "column" } : { position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 301, background: "var(--bg)", borderTop: "1px solid var(--accent)", maxHeight: "88%", display: "flex", flexDirection: "column" }} data-screen-label="Transaction detail">
        {!window.__YOSHI_WEB && <div style={{ display: "flex", justifyContent: "center", paddingTop: 8, flex: "none" }}><span style={{ width: 36, height: 4, background: "var(--rule-2)", borderRadius: 999 }} /></div>}
        <div className="scroll" style={{ padding: window.__YOSHI_WEB ? "18px 22px 26px" : "12px 20px 26px" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em", flex: 1, minWidth: 0 }}>{tx.title}</span>
            <button className="press" onClick={onClose} aria-label="Close" style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>
          </div>
          <div style={{ marginTop: 14 }}>
            <Money value={amount} size={38} weight={500} sign={amount > 0} color={amount > 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
          </div>
          <Eyebrow style={{ marginTop: 7, color: upcoming ? "var(--accent)" : "var(--ink-3)" }}>{upcoming ? "Scheduled" : "Settled"} · {tx.when}</Eyebrow>

          <div style={{ marginTop: 18, border: "1px solid var(--rule)" }}>
            <ReviewRow label="Status" value={upcoming ? "Scheduled" : "Settled"} accent={upcoming ? "var(--accent)" : "var(--accent-pos)"} />
            <ReviewRow label="Date" value={tx.when} />
            <ReviewRow label="Category" value={category} />
            {isTransfer && tx.rail && <ReviewRow label="Method" value={tx.rail} />}
            {method && <ReviewRow label={isTransfer ? "Account" : "Method"} value={method} />}
            <ReviewRow label="Amount" value={`${amount >= 0 ? "+" : "−"}${usd(Math.abs(amount))}`} last />
          </div>

          <TxnHistory tx={tx} amount={amount} />

          <div style={{ marginTop: 14 }}><Btn kind="ghost" full onClick={ask}>Ask Yoshi about this</Btn></div>
        </div>
      </div>
    </>);

};


/* ---- automation status pill + row ----------------------------------------- */
const StatusPill = ({ status }) => {
  const map = {
    active: ["●", "Active", "var(--accent)"],
    watching: ["○", "Watching", "var(--ink-3)"],
    paused: ["○", "Paused", "var(--ink-3)"]
  };
  const [dot, label, color] = map[status] || map.active;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 3, padding: "1px 5px", borderRadius: 999, border: "1px solid var(--rule-2)", fontFamily: "var(--f-display)", fontSize: 7.5, fontWeight: 700, letterSpacing: "0.05em", textTransform: "uppercase", color, flex: "none" }}>
      <span style={{ fontSize: 5.5, lineHeight: 1 }}>{dot}</span>{label}
    </span>);

};

const AutomationRow = ({ a, onOpen }) =>
<button className="press" onClick={onOpen} style={{
  width: "100%", textAlign: "left", background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: 10,
  display: "grid", gridTemplateColumns: "1fr auto auto", gap: 9, alignItems: "center", padding: "10px 11px 11px"
}}>
    <div style={{ minWidth: 0 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
        <span style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600, letterSpacing: "-0.01em", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{a.name}</span>
        <StatusPill status={a.status} />
      </div>
    </div>
    <div style={{ textAlign: "right" }}>
      <div style={{ fontFamily: "var(--f-mono)", fontSize: 13, color: a.measure.startsWith("+") ? "var(--accent-pos)" : "var(--ink-2)", fontVariantNumeric: "tabular-nums" }}>{a.measure}</div>
    </div>
    <Icon name="back" size={15} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
  </button>;


/* ---- automation row in the merged feed (flat, tappable, ◆ marker) ---------
   Mirrors TxnRow's two-line layout on both sides so row heights line up. */
const FeedAutoRow = ({ a, onOpen, last }) =>
<button className="press" onClick={onOpen} style={{
  width: "100%", textAlign: "left", background: "none", border: "none",
  display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 11, alignItems: "center", padding: "9px 0", cursor: "pointer", borderBottom: last ? "none" : "1px solid var(--rule)"
}}>
    <RowIcon auto />
    <div style={{ minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{a.name}</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{a.cadence}</div>
    </div>
    <div style={{ textAlign: "right", whiteSpace: "nowrap" }}>
      <div style={{ fontFamily: "var(--f-mono)", fontSize: 12.5, color: a.measure.startsWith("+") ? "var(--accent-pos)" : "var(--ink-3)", fontVariantNumeric: "tabular-nums" }}>{a.measure}</div>
      <div style={{ fontFamily: "var(--f-mono)", fontSize: 9.5, color: "var(--ink-3)", marginTop: 3 }}>{a.sub}</div>
    </div>
  </button>;


/* ---- automation detail sheet ---------------------------------------------- */
/* ============================================================
   Automation chat — the in-sheet thread used on MOBILE (web routes to the
   Automations inbox instead). Pinned context attaches under the header, chat
   runs below, and a concrete change drafts a proposal you review + approve.
   ============================================================ */
const AUTO_CHIPS_M = ["Pause it", "Change the amount", "Raise the threshold", "Explain the risk"];

const autoReply = (a, t) => {
  const s = t.toLowerCase();
  if (/attached/.test(s)) return `Got it. I've read it and tied it to "${a.name}" as context. Ask me anything about it.`;
  if (/risk|safe|why|explain|exposure/.test(s)) return `"${a.name}" only does this: ${a.summary} It runs on the schedule you approved and stays inside your limits. I flag anything outside the guardrails before it runs.`;
  if (/pause|stop|turn it off|disable|halt/.test(s)) return `I can pause it. I'll put the change up for you to approve; nothing runs until you turn it back on.`;
  if (/amount|raise|lower|threshold|limit|change|schedule|less|more/.test(s)) return `Sure. Tell me the new value for "${a.name}", an amount or a threshold, and I'll write up the revised rule for you to approve.`;
  return `On "${a.name}": ${a.summary} ${a.next}. I can change the amount, the threshold, the schedule, or pause it. What would you like?`;
};

const autoDraftChange = (a, text) => {
  const s = text.toLowerCase();
  if (/\b(pause|stop|turn it off|disable|halt)\b/.test(s)) {
    return { kind: "Pause", from: a.status === "active" ? "Active" : a.status === "watching" ? "Watching" : "Paused", to: "Paused", newSummary: a.summary, newStatus: "paused" };
  }
  const m = text.replace(/,/g, "").match(/\$?\s?(\d+(?:\.\d+)?)\s?(k)?/i);
  const bareNumber = /^\s*\$?\s?\d[\d.]*\s?k?\s*$/i.test(text.trim());
  const hasContext = /\$|amount|threshold|limit|raise|lower|change|buy|invest|move|sweep|above|to\b|by\b/.test(s);
  if (m && (hasContext || bareNumber)) {
    let val = parseFloat(m[1]); if (/k/i.test(m[2] || "")) val *= 1000;
    const newAmt = "$" + val.toLocaleString("en-US");
    const old = a.summary.match(/\$[\d,]+(?:\.\d+)?/);
    const newSummary = old ? a.summary.replace(old[0], newAmt) : a.summary;
    return { kind: "Amount", from: old ? old[0] : "current", to: newAmt, newSummary, newStatus: a.status };
  }
  return null;
};

const AutoPinBar = ({ a }) => (
  <div>
    <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
      <span style={{ width: 6, height: 6, borderRadius: 999, flex: "none", background: a.status === "paused" ? "transparent" : "var(--accent)", border: a.status === "paused" ? "1.5px solid var(--ink-3)" : "none" }} />
      <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--accent)" }}>Automation · pinned</span>
      <span style={{ marginLeft: "auto", fontFamily: "var(--f-display)", fontSize: 9.5, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-3)" }}>{a.status === "active" ? "Active" : a.status === "watching" ? "Watching" : "Paused"}</span>
    </div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, marginTop: 6, letterSpacing: "-0.012em" }}>{a.name}</div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", marginTop: 4, lineHeight: 1.5 }}>{a.summary}</div>
    <div style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-3)", marginTop: 6 }}>{a.next}</div>
  </div>
);

const AutoPropTile = ({ change, done, onReview }) => (
  <div style={{ alignSelf: "flex-start", maxWidth: "88%", border: `1px solid ${done ? "var(--rule)" : "var(--accent)"}`, background: "var(--bg-card)", borderRadius: 12, opacity: done ? 0.6 : 1, transition: "opacity 280ms ease, border-color 280ms ease" }}>
    <div style={{ padding: "11px 13px 12px" }}>
      <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: done ? "var(--ink-3)" : "var(--accent)" }}>{done ? "Applied" : "Proposed change"}</span>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, marginTop: 5 }}>{change.kind === "Pause" ? "Pause this automation" : "Change the " + change.kind.toLowerCase()}</div>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 8, fontFamily: "var(--f-mono)", fontSize: 12 }}>
        <span style={{ color: "var(--ink-3)" }}>{change.from}</span>
        <Icon name="arrow" size={13} color="var(--ink-3)" />
        <span style={{ color: done ? "var(--ink-3)" : "var(--ink)", fontWeight: 600 }}>{change.to}</span>
      </div>
      {done ?
      <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", marginTop: 9 }}>Applied to your automation.</div> :
      <Btn full onClick={onReview} style={{ marginTop: 11, padding: "10px" }}>Review change <Icon name="arrow" size={16} color="var(--accent-ink)" /></Btn>}
    </div>
  </div>
);

const ABubble = ({ m }) => {
  const u = m.from === "user";
  return (
    <div className="yo-enter" style={{ display: "flex", justifyContent: u ? "flex-end" : "flex-start" }}>
      <div style={{ maxWidth: "82%", padding: "10px 13px", borderRadius: u ? "14px 14px 4px 14px" : "14px 14px 14px 4px", background: u ? "var(--accent)" : "var(--bg-card)", color: u ? "var(--accent-ink)" : "var(--ink)", border: u ? "none" : "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: 14, lineHeight: 1.45 }}>{m.t}</div>
    </div>);
};
const ATyping = () =>
  <div style={{ display: "flex", justifyContent: "flex-start" }}>
    <div style={{ padding: "12px 14px", background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: "14px 14px 14px 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 AutoThreadLayer = ({ a, msgs, typing, scrollRef, msg, setMsg, onSend, onAttach, onReview }) => (
  <>
    <div style={{ flex: "none", background: "var(--bg)", padding: "12px 20px", borderBottom: "1px solid var(--rule)" }}>
      <AutoPinBar a={a} />
    </div>
    <div className="scroll" ref={scrollRef} style={{ padding: "14px 16px 8px", display: "flex", flexDirection: "column", gap: 9 }}>
      {msgs.map((m, i) => m.kind === "proposal"
        ? <div key={i} style={{ display: "flex" }}><AutoPropTile change={m.change} done={m.done} onReview={() => onReview(i)} /></div>
        : <ABubble key={i} m={m} />)}
      {typing && <ATyping />}
    </div>
    <div style={{ flex: "none", borderTop: "1px solid var(--rule)" }}>
      <div style={{ display: "flex", gap: 7, overflowX: "auto", padding: "10px 16px 6px" }}>
        {AUTO_CHIPS_M.map((q) => <button key={q} className="press" onClick={() => onSend(q)} style={{ flex: "none", padding: "7px 12px", borderRadius: 999, background: "color-mix(in srgb, var(--ink) 8%, var(--bg-2))", border: "none", fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 500, color: "var(--ink)", whiteSpace: "nowrap", cursor: "pointer" }}>{q}</button>)}
      </div>
      <div style={{ padding: "4px 14px 16px" }}>
        <YoshiComposer value={msg} onChange={setMsg} onSend={onSend} placeholder="Message Yoshi about this…" onAttach={onAttach} />
      </div>
    </div>
  </>
);

const AutoReviewModal = ({ change, name, onCancel, onApply }) => {
  const lbl = { fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--ink-3)", flex: "none" };
  const val = { fontFamily: "var(--f-mono)", fontSize: 13, textAlign: "right", fontVariantNumeric: "tabular-nums" };
  return (
    <div style={{ position: "absolute", inset: 0, zIndex: 60, display: "flex", alignItems: "center", justifyContent: "center", padding: 22 }} data-screen-label="Change review">
      <div onClick={onCancel} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)", animation: "scrim-in 200ms ease both" }} />
      <div role="dialog" aria-modal="true" style={{ position: "relative", width: "100%", maxWidth: 360, background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 16, overflow: "hidden", boxShadow: "0 30px 80px -28px rgba(0,0,0,0.6)", animation: "modal-in 240ms cubic-bezier(0.16,1,0.30,1) both" }}>
        <div style={{ padding: "20px 22px 0" }}>
          <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--accent)" }}>Review change</span>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 18, fontWeight: 600, letterSpacing: "-0.02em", marginTop: 8 }}>{change.kind === "Pause" ? "Pause this automation" : "Change the " + change.kind.toLowerCase()}</div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-3)", marginTop: 4 }}>{name}</div>
        </div>
        <div style={{ margin: "16px 22px", border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden" }}>
          <div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "12px 14px", borderBottom: "1px dashed var(--rule)" }}>
            <span style={lbl}>Current</span><span style={{ ...val, color: "var(--ink-3)" }}>{change.from}</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "12px 14px" }}>
            <span style={lbl}>New</span><span style={{ ...val, color: "var(--ink)", fontWeight: 600 }}>{change.to}</span>
          </div>
        </div>
        <div style={{ padding: "0 22px", fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-2)", lineHeight: 1.5 }}>{change.kind === "Pause" ? "Nothing runs until you turn it back on." : change.newSummary}</div>
        <div style={{ padding: "16px 22px 20px", display: "flex", gap: 9 }}>
          <Btn kind="ghost" full onClick={onCancel}>Cancel</Btn>
          <Btn full onClick={onApply}>{change.kind === "Pause" ? "Pause automation" : "Apply change"}</Btn>
        </div>
      </div>
    </div>);
};

const AutomationSheet = ({ automation, onClose, nav, flash, embedded, onThread }) => {
  const [msg, setMsg] = useState("");
  // mobile chats in-sheet; web (onThread) routes to the Automations inbox.
  const [msgs, setMsgs] = useState([]);
  const [typing, setTyping] = useState(false);
  const [applied, setApplied] = useState(null);  // approved rule overrides for this automation
  const [review, setReview] = useState(null);     // index of the change msg under review
  const scrollRef = useRef(null);
  const a = applied ? { ...automation, ...applied } : automation;
  const threadOpen = !onThread && (msgs.length > 0 || typing);
  useEffect(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [msgs, typing]);
  const pushChat = (text) => {
    setMsgs((m) => [...m, { from: "user", t: text }]);
    setTyping(true);
    setTimeout(() => {
      setTyping(false);
      const change = autoDraftChange(a, text);
      setMsgs((m) => {
        const arr = [...m];
        if (change) {
          arr.push({ from: "agent", t: change.kind === "Pause" ? `I've drafted the pause for "${a.name}". Review it when you're ready.` : `Here's the revised rule for "${a.name}", ready for you to review.` });
          arr.push({ kind: "proposal", change });
        } else { arr.push({ from: "agent", t: autoReply(a, text) }); }
        return arr;
      });
    }, 1000);
  };
  const send = (text) => {
    const t = (typeof text === "string" ? text : msg).trim(); if (!t) return;
    setMsg("");
    if (onThread) { onThread(t); return; }
    pushChat(t);
  };
  const attachDoc = (doc) => {
    const t = `Attached ${doc.name}`;
    if (onThread) { onThread(t); return; }
    pushChat(t);
  };
  const applyChange = () => {
    if (review == null) return;
    const change = msgs[review] && msgs[review].change;
    if (!change) { setReview(null); return; }
    setApplied((p) => ({ ...(p || {}), summary: change.newSummary, status: change.newStatus }));
    setMsgs((m) => {
      const next = m.map((x, i) => (i === review ? { ...x, done: true } : x));
      next.push({ from: "agent", t: change.kind === "Pause" ? `Done. "${a.name}" is paused; tell me when to turn it back on.` : `Done. "${a.name}" now reads ${change.to}. It takes effect on the next run.` });
      return next;
    });
    flash((change.kind === "Pause" ? "Paused · " : "Updated · ") + a.name);
    setReview(null);
  };
  return (
    <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Automation">
      {!embedded && window.StatusBar && <window.StatusBar />}
      {!embedded &&
      <NavBar title="Automation" onBack={threadOpen ? () => { setMsgs([]); setTyping(false); setReview(null); } : onClose}
        right={<button className="press" onClick={onClose} style={{ background: "none", border: "none", color: "var(--ink-3)", display: "flex" }}><Icon name="close" size={20} /></button>} />}
      {threadOpen ? (
        <AutoThreadLayer a={a} msgs={msgs} typing={typing} scrollRef={scrollRef} msg={msg} setMsg={setMsg} onSend={send} onAttach={attachDoc} onReview={(i) => setReview(i)} />
      ) : (
      <React.Fragment>
      <div className="scroll" style={{ padding: "10px 20px 0" }}>
        {/* "Needs you" vocabulary — agent dot + eyebrow, then title + read */}
        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <span style={{ width: 6, height: 6, borderRadius: 999, flex: "none", background: a.status === "active" ? "var(--accent)" : "transparent", border: a.status === "active" ? "none" : "1.5px solid var(--ink-3)" }} />
          <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-3)" }}>Yoshi agent · {a.status === "active" ? "Active" : a.status === "watching" ? "Watching" : "Paused"}</span>
        </div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 22, fontWeight: 600, letterSpacing: "-0.025em", marginTop: 10, lineHeight: 1.2 }}>{a.name}</div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 14, color: "var(--ink-2)", marginTop: 8, lineHeight: 1.55 }}>{a.summary}</div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-3)", marginTop: 8 }}>{a.next}</div>
        <div style={{ height: 1, background: "var(--rule)", margin: "16px 0" }} />
        {(() => {
          const hi = a.perf.findIndex(([k, v]) => v.replace(/\s/g, "").startsWith(a.measure.replace(/\s/g, "")));
          const headline = hi >= 0 ? a.perf[hi] : ["Return", a.measure];
          const rest = a.perf.filter((_, i) => i !== hi);
          const pos = headline[1].trim().startsWith("+");
          return (
            <div style={{ border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", marginBottom: 20 }}>
              {/* hero return */}
              <div style={{ display: "flex", alignItems: "baseline", gap: 10, padding: "13px 14px", borderBottom: rest.length ? "1px solid var(--rule)" : "none" }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-3)" }}>{headline[0]}</div>
                  <div style={{ fontFamily: "var(--f-mono)", fontSize: 24, fontWeight: 500, marginTop: 4, fontVariantNumeric: "tabular-nums", color: pos ? "var(--accent-pos)" : "var(--ink)" }}>{headline[1]}</div>
                </div>
                <span style={{ marginLeft: "auto", fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", whiteSpace: "nowrap" }}>{a.sub}</span>
              </div>
              {/* supporting stats — pad to a full grid so row/column dividers
                   never drop out on an odd stat count */}
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
                {(rest.length % 2 ? [...rest, null] : rest).map((cell, i) => {
                  const c = i % 2,r = Math.floor(i / 2);
                  return (
                    <div key={i} style={{ padding: "11px 12px", minHeight: 52, borderTop: r > 0 ? "1px dashed var(--rule)" : "none", borderLeft: c > 0 ? "1px dashed var(--rule)" : "none" }}>
                    {cell &&
                      <>
                      <div style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-3)" }}>{cell[0]}</div>
                      <div style={{ fontFamily: "var(--f-mono)", fontSize: 13.5, fontWeight: 500, marginTop: 3, color: cell[1].trim().startsWith("+") ? "var(--accent-pos)" : "var(--ink)", fontVariantNumeric: "tabular-nums" }}>{cell[1]}</div>
                    </>}
                  </div>);
                })}
              </div>
            </div>);
        })()}
        {a.ledger && a.ledger.length > 0 &&
        <div style={{ marginBottom: 20 }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--ink-3)", marginBottom: 8 }}>Run history</div>
          <div style={{ border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden" }}>
            {a.ledger.map((row, i) =>
            <div key={i} style={{ display: "grid", gridTemplateColumns: "56px 1fr auto", gap: 10, alignItems: "center", padding: "11px 13px", borderTop: i ? "1px solid var(--rule)" : "none" }}>
              <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-3)", whiteSpace: "nowrap" }}>{row[0]}</span>
              <span style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{row[1]}</span>
              <span style={{ fontFamily: "var(--f-mono)", fontSize: 12.5, fontWeight: 500, whiteSpace: "nowrap", color: row[2].trim().startsWith("\u2212") ? "var(--signal-neg)" : row[2].trim().startsWith("+") ? "var(--accent-pos)" : "var(--ink-2)" }}>{row[2]}</span>
            </div>
            )}
          </div>
        </div>}
      </div>

      <div style={{ flex: "none", borderTop: "1px solid var(--rule)" }}>
        <div style={{ padding: "12px 20px 20px", display: "flex", gap: 8 }}>
          <Btn kind="ghost" full onClick={() => {onClose();flash("Cancelled · " + a.name);}}>Cancel</Btn>
          <Btn full onClick={() => {onClose();flash("Paused · " + a.name);}}>Pause</Btn>
        </div>
        {/* free-form Yoshi chat — same bar used in briefs */}
        <div style={{ margin: "0 20px 0" }}>
          <YoshiComposer value={msg} onChange={setMsg} onSend={send} placeholder="Ask Yoshi about this…" onAttach={attachDoc} />
        </div>
        <div style={{ height: 20 }} />
      </div>
      </React.Fragment>
      )}
      {review != null && msgs[review] && msgs[review].change && <AutoReviewModal change={msgs[review].change} name={a.name} onCancel={() => setReview(null)} onApply={applyChange} />}
    </div>);

};

window.AutomationSheet = AutomationSheet;
window.BriefBlob = BriefBlob;

/* ============================================================
   StreamPanel — the activity stream, lifted out of Home so the web shell can
   mount it as its own right pillar. Inline (mobile) it returns just the
   <section>; standalone (web) it wraps itself in a pillar with head + scroll.
   Same data, filters, search, look — unchanged from the original Home band.
   ============================================================ */
const StreamPanel = ({ nav, completed, onSeeStream, standalone, onRepin }) => {
  const [filter, setFilter] = useState("all");
  // web pillar: any stream item opens full-pillar, with everything the sheet has
  const [detail, setDetail] = useState(null); // { kind: "auto", id } | { kind: "txn", tx }
  const onItemDetail = standalone && window.__YOSHI_WEB ? (d) => setDetail(d) : null;
  const onAutoDetail = onItemDetail ? (id) => onItemDetail({ kind: "auto", id }) : null;
  const [searchOpen, setSearchOpen] = useState(false);
  const [query, setQuery] = useState("");

  // recent activity = freshly-approved agent moves prepended onto the ledger
  const txns = useMemo(() => {
    const fresh = (completed || []).filter((c) => String(c.id).startsWith("ex_")).map((c) => ({
      id: c.id, icon: "bolt", title: c.title, detail: `${c.detail} \u00b7 ${c.by}`, when: c.when, net: c.net
    }));
    return [...fresh, ...TRANSACTIONS];
  }, [completed]);

  // one feed: transactions with automation events woven in, each tagged with a
  // category so the filter chips can narrow the combined list.
  const feed = useMemo(() => {
    const txCat = { receipt: "spending", down: "income", swap: "transfers", trade: "investments", bolt: "automations" };
    const autos = AUTOMATIONS.map((a) => ({ kind: "auto", a, id: "f_" + a.id, cat: "automations" }));
    const tx = txns.map((t) => ({ kind: "txn", t, id: "f_" + t.id, cat: txCat[t.icon] || "spending" }));
    const out = [];
    let ai = 0;
    tx.forEach((item, i) => {
      out.push(item);
      if (i % 2 === 1 && ai < autos.length) out.push(autos[ai++]);
    });
    while (ai < autos.length) out.push(autos[ai++]);
    return out;
  }, [txns]);

  const shownFeed = useMemo(() => {
    const q = query.trim().toLowerCase();
    return feed.filter((f) => {
      if (filter !== "all" && f.cat !== filter) return false;
      if (!q) return true;
      const hay = f.kind === "auto" ? `${f.a.name} ${f.a.measure}` : `${f.t.title} ${f.t.detail}`;
      return hay.toLowerCase().includes(q);
    });
  }, [feed, filter, query]);

  const shownComing = useMemo(() => {
    const q = query.trim().toLowerCase();
    return COMING_UP.filter((c) => {
      if (filter !== "all" && c.cat !== filter) return false;
      if (!q) return true;
      return `${c.title} ${c.sub}`.toLowerCase().includes(q);
    });
  }, [filter, query]);

  const body = (
    <section style={{ padding: "8px 0 0", minHeight: "100%" }}>
      {/* sticky header: Stream title · filter pills · search */}
      <div style={{ position: "sticky", top: 0, zIndex: 6, background: "var(--bg)", paddingBottom: 10 }}>
        {!standalone &&
        <div style={{ padding: "2px 18px 0", display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{ fontFamily: "var(--f-display)", fontSize: onSeeStream ? 12.5 : 17, fontWeight: onSeeStream ? 500 : 600, letterSpacing: "-0.01em", color: onSeeStream ? "var(--ink-2)" : "var(--ink)" }}>{onSeeStream ? "Recent activity" : "Stream"}</span>
          {onSeeStream && <button className="press" onClick={onSeeStream} aria-label="Open Stream" style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 4, background: "none", border: "none", cursor: "pointer", fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600, color: "var(--ink-3)" }}><Icon name="back" size={14} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} /></button>}
          {onRepin && <button className="press" onClick={onRepin} aria-label="Pin Stream to the side" title="Pin to the side" style={{ marginLeft: "auto", flex: "none", width: 28, height: 28, borderRadius: 999, background: "none", border: "none", display: "grid", placeItems: "center", color: "var(--ink-2)", cursor: "pointer", padding: 0 }}><Icon name="external" size={14} /></button>}
        </div>}
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: standalone ? 4 : 9 }}>
          <div className="hcar" style={{ display: "flex", gap: 7, overflowX: "auto", padding: "0 0 0 18px", flex: 1, minWidth: 0 }}>
            {ACTIVITY_FILTERS.map(([k, l]) => {
              const on = filter === k;
              return <button key={k} className="press" onClick={() => setFilter(k)} style={{ flex: "none", padding: "6px 12px", borderRadius: 999, background: on ? "color-mix(in srgb, var(--ink) 15%, var(--bg-2))" : "color-mix(in srgb, var(--ink) 8%, var(--bg-2))", color: on ? "var(--ink)" : "var(--ink-2)", border: "none", fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600, whiteSpace: "nowrap", cursor: "pointer" }}>{l}</button>;
            })}
          </div>
          <button className="press" onClick={() => {setSearchOpen((o) => {if (o) setQuery("");return !o;});}}
          aria-label="Search activity"
          style={{ flex: "none", marginRight: 18, background: "none", border: "none", padding: 2, display: "flex", color: searchOpen ? "var(--ink)" : "var(--ink-3)", cursor: "pointer" }}>
            <Icon name={searchOpen ? "close" : "search"} size={18} stroke={1.6} />
          </button>
        </div>
        {searchOpen &&
        <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "9px 12px", margin: "10px 18px 0", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 10 }}>
            <Icon name="search" size={15} color="var(--ink-3)" stroke={1.5} />
            <input autoFocus value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search activity"
          style={{ flex: 1, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13.5 }} />
            {query && <button className="press" onClick={() => setQuery("")} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", padding: 0 }}><Icon name="close" size={14} /></button>}
          </div>
        }
      </div>

      {/* COMING UP */}
      {shownComing.length > 0 &&
      <div style={{ padding: "16px 18px 4px" }}>
          <SecTitle>Coming up</SecTitle>
          <div style={{ marginTop: 8 }}>
            {shownComing.map((c, i) => <ComingRow key={i} c={c} last={i === shownComing.length - 1} nav={nav} onDetail={onItemDetail} />)}
          </div>
        </div>
      }

      {/* RECENT ACTIVITY */}
      <div style={{ padding: "18px 18px 4px" }}>
        <SecTitle>Recent activity</SecTitle>
        <div style={{ marginTop: 8 }}>
          {shownFeed.length ? shownFeed.map((item, i) => item.kind === "auto" ?
          <FeedAutoRow key={item.id} a={item.a} last={i === shownFeed.length - 1} onOpen={() => onAutoDetail ? onAutoDetail(item.a.id) : nav.sheet({ type: "automation", id: item.a.id })} /> :
          <TxnRow key={item.id} t={item.t} last={i === shownFeed.length - 1} nav={nav} onDetail={onItemDetail} />) :
          <div style={{ padding: "26px 0", textAlign: "center", fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-3)" }}>{query.trim() ? `No activity matches "${query}".` : "Nothing in this filter yet."}</div>}
        </div>
      </div>
    </section>
  );

  if (!standalone) return body;
  const openAuto = detail && detail.kind === "auto" ? AUTOMATIONS.find((a) => a.id === detail.id) : null;
  const openTxn = detail && detail.kind === "txn" ? detail.tx : null;
  return (
    <aside className="stream-col">
      <div className="pillar-head">
        {detail ?
        <button className="press" onClick={() => setDetail(null)} style={{ display: "inline-flex", alignItems: "center", gap: 8, background: "none", border: "none", padding: 0, cursor: "pointer", color: "var(--ink)" }}>
          <Icon name="back" size={18} stroke={1.7} />
          <span className="pillar-title">Stream</span>
        </button> :
        <span className="pillar-title">Stream</span>}
      </div>
      {openAuto ? (
        <div style={{ flex: 1, minHeight: 0, position: "relative", display: "flex", flexDirection: "column" }} className="push-enter">
          <window.AutomationSheet key={openAuto.id} embedded automation={openAuto} onClose={() => setDetail(null)} nav={nav} flash={() => {}}
            onThread={nav.automationChat ? (t) => nav.automationChat(openAuto.id, t) : undefined} />
        </div>
      ) : openTxn ? (
        <div style={{ flex: 1, minHeight: 0, position: "relative", display: "flex", flexDirection: "column" }} className="push-enter">
          <TxnDetailSheet key={openTxn.id || openTxn.title} tx={openTxn} onClose={() => setDetail(null)} nav={nav} />
        </div>
      ) : (
        <div className="scroll" style={{ paddingBottom: 20 }}>{body}</div>
      )}
    </aside>
  );
};
window.StreamPanel = StreamPanel;

window.Home = Home;