/* simple/core.jsx — the redesigned app's spine.
   ConfirmSheet is THE execution primitive: every money action — agent
   proposal or manual order — funnels into the same review → passkey →
   success flow. Two doors, one primitive.
   Also: the agent card library rendered inline in the Yoshi thread.
   Each card replaces a killed screen (Studio, Trade browse, Briefs). */

/* ---------- shared money rows ---------------------------------------------- */
const LegRow = ({ leg, last }) => {
  const [tag, label, detail, amount] = leg;
  const tagColor = tag === "SELL" ? "var(--signal-neg)" : tag === "BUY" ? "var(--accent-pos)" : "var(--ink-2)";
  // newer proposals put a security object in the label slot ({ ticker, price, change, name, valueLabel })
  const sec = label && typeof label === "object" ? label : null;
  return (
    <div style={{ display: "grid", gridTemplateColumns: "62px 1fr auto", gap: 8, alignItems: "start", padding: "9px 11px", borderBottom: last ? "none" : "1px dashed var(--rule)" }}>
      <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(9.5), fontWeight: 700, letterSpacing: "0.08em", color: tagColor, paddingTop: 2 }}>{tag}</span>
      <div style={{ minWidth: 0 }}>
        {sec ? (
          <>
            <div style={{ display: "flex", alignItems: "baseline", gap: 6, flexWrap: "wrap" }}>
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 600 }}>{sec.ticker}</span>
              {sec.price ? <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(10.5), color: "var(--ink-3)" }}>{sec.price}</span> : null}
              {sec.change ? <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(10), color: sec.changeTone === "neg" ? "var(--signal-neg)" : "var(--accent-pos)" }}>{sec.change}</span> : null}
            </div>
            {(sec.name || sec.valueLabel) ? <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)" }}>{[sec.name, sec.valueLabel].filter(Boolean).join(" · ")}</div> : null}
          </>
        ) : (
          <>
            <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 500 }}>{label}</div>
            {detail ? <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)" }}>{detail}</div> : null}
          </>
        )}
      </div>
      <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12), color: "var(--ink)", whiteSpace: "nowrap" }}>{amount}</span>
    </div>
  );
};

const StatCell = ({ label, value, color = "var(--ink)", border }) => (
  <div style={{ padding: "11px 13px", borderLeft: border ? "1px solid var(--rule)" : "none" }}>
    <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-3)" }}>{label}</div>
    <div style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(15), fontWeight: 500, color, marginTop: 3, fontVariantNumeric: "tabular-nums" }}>{value}</div>
  </div>
);

/* One receipt language for every instruction. Manual queued orders, transfers,
   and approved briefs all pass through this model before Activity takes over. */
const outcomeTicker = (row) => {
  if (row.ticker) return row.ticker;
  const match = String(row.title || "").match(/\b(?:Buy|Sell)\s+([A-Z]{1,6})\b/i);
  return match ? match[1].toUpperCase() : null;
};
const outcomeIsTrade = (row) => ["BUY", "SELL"].includes(String(row.tag || "").toUpperCase());
const outcomeAmount = (row) => Number(String(row.amt || "").replace(/[^\d.]/g, "")) || 0;
const outcomeNet = (row) => {
  const raw = String(row.amt || "");
  const value = outcomeAmount(row);
  const tag = String(row.tag || "").toUpperCase();
  if (tag === "BUY" || tag === "PAY") return -value;
  if (tag === "SELL" || tag === "PULL") return value;
  if (/[-−]/.test(raw)) return -value;
  if (/\+/.test(raw)) return value;
  return 0;
};
const transactionRowsForBrief = (action) => {
  if (action.series) return action.series;
  if (action.kind === "trade" && action.legs) {
    const account = action.legs.find((leg) => leg[0] === "ACCOUNT" || leg[0] === "FROM");
    return action.legs.filter((leg) => ["BUY", "SELL"].includes(leg[0])).map((leg) => {
      const security = leg[1] && typeof leg[1] === "object" ? leg[1] : null;
      const ticker = security ? security.ticker : String(leg[1] || "");
      return { tag: leg[0], ticker, title: `${leg[0] === "BUY" ? "Buy" : "Sell"} ${ticker}`, sub: [security && security.name, security && security.valueLabel, account && account[1]].filter(Boolean).join(" · "), amt: leg[3] };
    });
  }
  return [{
    tag: action.kind === "trade" ? "BUY" : action.kind === "automation" ? "RULE" : "MOVE",
    title: action.title,
    sub: action.rail || action.settles || "Submitted to Yoshi",
    amt: action.amount || (action.net ? usd(Math.abs(action.net)) : ""),
  }];
};
const briefOutcomeItems = (action) => {
  const rows = transactionRowsForBrief(action);
  return rows.map((row) => {
    const trade = outcomeIsTrade(row) || (!action.series && action.kind === "trade");
    const scheduled = !action.series && action.kind === "automation";
    const queued = trade && !MARKET_OPEN;
    const submittedDetail = [row.amt, row.sub].filter(Boolean).join(" · ");
    const detail = trade
      ? [row.amt, queued ? "Queued for tomorrow at market open" : "Executing at market", row.sub].filter(Boolean).join(" · ")
      : [row.amt, row.sub].filter(Boolean).join(" · ");
    return { ...row, submittedDetail, detail, status: scheduled ? "Scheduled" : "In motion", trade };
  });
};
const transactionStatusForTracker = (item) => {
  const trade = item.category === "Trade";
  const queuedTrade = trade && item.state === "queued";
  const scheduled = item.kind === "recurring";
  const amountValue = Math.abs(item.amount || item.net || 0);
  const amount = amountValue ? usd(amountValue) : "";
  return {
    title: scheduled ? "Automation scheduled" : `${trade ? "Trade" : "Transfer"} submitted`,
    summary: scheduled
      ? "Your recurring transfer is scheduled. You can follow its next run in Activity."
      : trade
      ? queuedTrade
        ? "Your one-time order is queued for tomorrow at market open."
        : "Your order is in motion. You can follow it in Activity."
      : "Your transfer is processing. You can follow it in Activity.",
    items: [{ title: item.title, submittedDetail: [amount, item.rail || item.acct].filter(Boolean).join(" · "), detail: [amount, item.rail || item.acct, item.chip].filter(Boolean).join(" · "), status: scheduled ? "Scheduled" : "In motion" }],
  };
};
const transactionStatusForBrief = (action) => {
  const items = briefOutcomeItems(action);
  const inMotion = items.filter((item) => item.status === "In motion").length;
  const scheduled = items.filter((item) => item.status === "Scheduled").length;
  const summary = [
    inMotion ? `${inMotion} ${inMotion === 1 ? "is" : "are"} in motion` : "",
    scheduled ? `${scheduled} scheduled` : "",
  ].filter(Boolean).join(". ") + ".";
  return {
    title: items.length > 1 ? "Transactions submitted" : action.kind === "trade" ? "Trade submitted" : action.kind === "automation" ? "Automation scheduled" : "Transfer submitted",
    submittedSummary: items.length === 1 ? "Your transaction was submitted." : `${items.length} transactions were submitted.`,
    summary,
    items,
  };
};
const seriesInflightItem = (row) => {
  const trade = outcomeIsTrade(row);
  const queued = trade && !MARKET_OPEN;
  const net = outcomeNet(row);
  const ticker = outcomeTicker(row);
  const quote = ticker && MARKET.find((security) => security.ticker === ticker);
  return {
    icon: trade ? "trade" : "swap", title: row.title, category: trade ? "Trade" : "Transfer",
    kind: "once", state: trade ? (queued ? "queued" : "working") : "pending", agent: false,
    when: trade ? (queued ? "Tomorrow · market open" : "Executing now") : "Processing",
    chip: trade ? (queued ? "Queued · tomorrow 9:30 AM" : "In motion · executing") : "In motion · processing",
    net, amount: outcomeAmount(row), scope: "yoshi", rail: trade ? "Market order" : "Brief transaction", acct: row.sub,
    cancelNote: queued ? "Cancellation is best efforts before market open." : trade ? "Cancellation is best efforts." : undefined,
    steps: trade
      ? queued
        ? [["Submitted", "Just now"], ["Queued for market open", "You are here"], ["Executes", "Tomorrow · 9:30 AM"]]
        : [["Submitted", "Just now"], ["Order in motion", "You are here"], ["Executes", "Market open"]]
      : [["Submitted", "Just now"], ["Processing", "You are here"], ["Completes", "Soon"]],
    stepAt: 1, settleMs: trade ? (queued ? undefined : 6000) : 25000,
    settle: {
      icon: trade ? "trade" : "swap",
      title: trade ? String(row.title).replace(/^Buy\b/i, "Bought").replace(/^Sell\b/i, "Sold") : row.title,
      detail: trade ? [row.amt, quote ? `Executed at ${usd(quote.last)}` : "Market order executed", row.sub].filter(Boolean).join(" · ") : row.sub,
      category: trade ? "Investment" : "Transfer", accountScope: "yoshi", net, amount: outcomeAmount(row),
    },
  };
};
const automationInflightItem = (action) => ({
  icon: "clock", title: action.title, category: "Automation", kind: "recurring", state: "scheduled", agent: true,
  when: action.settles || "Next run", chip: `Scheduled · ${action.settles || "next run"}`,
  net: action.net || 0, amount: outcomeAmount({ amt: action.amount }), scope: "yoshi", acct: "Yoshi automation",
});
const TransactionSubmissionScreen = ({ status, onDone }) => {
  useEffect(() => {
    const doneTimer = setTimeout(onDone, 2200);
    return () => clearTimeout(doneTimer);
  }, [status]);
  const destinationCopy = status.destination === "automations" ? "Opening Automations…" : "Opening Activity…";
  return (
    <div className="push-enter" data-screen-label={status.title} style={{ flex: 1, minHeight: 0, width: "100%", background: "var(--bg)", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "28px 24px", boxSizing: "border-box" }}>
      <div style={{ position: "relative", width: 104, height: 104, flex: "none" }}>
        <div style={{ position: "absolute", inset: 0, borderRadius: "44% 56% 48% 52% / 53% 42% 58% 47%", background: "var(--accent)", opacity: 0.22, filter: "blur(12px)", animation: "yo-blob-morph 5s ease-in-out infinite, yo-blob-breathe 2.8s ease-in-out infinite" }} />
        <div style={{ position: "absolute", inset: 13, borderRadius: 999, border: "1.5px solid var(--accent)", background: "var(--bg-card)", display: "grid", placeItems: "center", animation: "yo-blob-breathe 1.4s ease-in-out infinite" }}>
          <span style={{ width: 14, height: 14, borderRadius: 999, background: "var(--accent)", boxShadow: "0 0 0 7px color-mix(in srgb, var(--accent) 18%, transparent)", animation: "yo-blob-breathe 1.1s ease-in-out infinite" }} />
        </div>
      </div>
      <Eyebrow color="var(--accent)" style={{ marginTop: 22 }}>Submitted</Eyebrow>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(25), fontWeight: 600, letterSpacing: "-0.028em", textAlign: "center", marginTop: 7 }}>{status.title}</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), color: "var(--ink-2)", lineHeight: 1.5, textAlign: "center", maxWidth: 420, marginTop: 7 }}>{status.submittedSummary || status.summary}</div>
      <div data-transaction-outcomes style={{ width: "100%", maxWidth: 480, border: "1px solid var(--rule-2)", borderRadius: 12, overflow: "hidden", marginTop: 20 }}>
        {status.items.map((item, index) => (
          <div key={`${item.title}-${index}`} style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) auto", gap: 12, alignItems: "center", padding: "11px 12px", borderTop: index ? "1px solid var(--rule)" : "none" }}>
            <span style={{ minWidth: 0 }}>
              <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 600 }}>{item.title}</span>
              <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)", lineHeight: 1.4, marginTop: 2 }}>{item.submittedDetail}</span>
            </span>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(9.5), fontWeight: 700, letterSpacing: "0.05em", textTransform: "uppercase", color: "var(--accent)", whiteSpace: "nowrap" }}>Submitted</span>
          </div>
        ))}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 7, fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-3)", marginTop: 16 }}><Icon name="clock" size={13} color="var(--ink-3)" /> {destinationCopy}</div>
    </div>
  );
};

/* ============================================================
   ConfirmSheet · the one execution primitive.
   `action` = { title, why, legs, net, settles, settlesVerb, kind, agent }
   agent falsy → a manual order (your own trade/move); truthy → a proposal.
   Both render identically: same review, same passkey, same success.
   ============================================================ */
const CONFIRM_DECLINES = ["Not now", "Too large", "I'll do it manually", "Don't trust the timing"];

/* multi-transaction series — the brief shows the legs for review only.
   The footer owns the single confirmation decision for the whole series. */
const seriesReviewTag = (item, index) => item.tag || String(index + 1);
const seriesReviewTone = (tag) => tag === "BUY" || tag === "PULL" || tag === "MOVE" ? "var(--accent-pos)" : tag === "PAY" ? "var(--signal-neg)" : "var(--ink-2)";
const SeriesReviewRows = ({ items }) => {
  return (
    <div style={{ marginTop: 2 }}>
      <div data-series-review="proposal-table" style={{ display: "inline-block", maxWidth: "100%", border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden" }}>
        {items.map((s, i) => (
          <div key={i} style={{ display: "grid", gridTemplateColumns: "64px minmax(0, 1fr) auto", gap: 10, alignItems: "start", padding: "10px 10px", borderBottom: i === items.length - 1 ? "none" : "1px dashed var(--rule)" }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(9.5), fontWeight: 700, letterSpacing: "0.08em", color: seriesReviewTone(seriesReviewTag(s, i)), paddingTop: 2 }}>{seriesReviewTag(s, i)}</span>
            <span style={{ minWidth: 0 }}>
              <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 600, lineHeight: 1.35 }}>{s.title}</span>
              <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)", marginTop: 2, lineHeight: 1.4 }}>{s.sub}</span>
            </span>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12), fontWeight: 500, whiteSpace: "nowrap", color: (s.amt || "").startsWith("+") ? "var(--accent-pos)" : "var(--ink)" }}>{s.amt}</span>
          </div>
        ))}
      </div>
    </div>
  );
};

const ConfirmSheet = ({ action, onClose, onApproved, onDeclined, onAsk, onSource }) => {
  const [phase, setPhase] = useState("review");
  const [msg, setMsg] = useState("");
  const [graduated, setGraduated] = useState(false); // "switch to automatic" accepted in-brief
  const a = action;
  const manual = !a.agent;
  // markets closed → an approved trade queues as a one-time order, and every
  // phase of this sheet says so instead of pretending it executed
  const queues = a.kind === "trade" && !MARKET_OPEN;

  return (
    <div className="push-enter" data-screen-label="Confirm" style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex", flexDirection: "column" }}>
      {window.StatusBar && <window.StatusBar />}
      {/* the shared brief nav bar (back chevron + "Briefs" title + rule) so a
          needs-you review reads exactly like the alert/insight brief detail. */}
      {phase !== "success" && <NavBar title="Briefs" onBack={onClose} />}

      {/* who initiated — the same header for both doors builds one habit.
          Rule-fired briefs show provenance instead: which rule asked, tappable
          through to the automation it came from. */}
      {phase !== "success" && (
        <div style={{ padding: "8px 20px 0", flex: "none" }}>
          {a.source ? (
            /* rule-fired — the glyph stays Yoshi: your rules run inside Yoshi */
            <button className="press" onClick={onSource ? () => onSource(a.source.auto) : undefined} style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 999, padding: "5px 10px 5px 8px", cursor: onSource ? "pointer" : "default" }}>
              <Logo size={11} />
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--accent)" }}>{a.source.name}</span>
              {onSource && <Icon name="back" size={11} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />}
            </button>
          ) : (
            <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              {a.agentIcon && typeof AGENT_ICON !== "undefined" && AGENT_ICON[a.agentIcon]
                ? <img src={AGENT_ICON[a.agentIcon]} alt="" style={{ width: 13, height: 13, objectFit: "contain", filter: "brightness(0) invert(1)", opacity: 0.85, flex: "none" }} />
                : <span style={{ width: 7, height: 7, borderRadius: 999, flex: "none", background: manual ? "transparent" : "var(--accent)", border: manual ? "1.5px solid var(--ink-3)" : "none" }} />}
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: manual ? "var(--ink-3)" : "var(--accent)" }}>{manual ? "Your order" : a.agent}</span>
            </div>
          )}
        </div>
      )}

      {phase === "review" && (
        <>
          <div className="scroll" style={{ padding: "0 20px" }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(22), fontWeight: 600, letterSpacing: "-0.025em", marginTop: 8 }}>{a.title}</div>
            {a.why && <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), color: "var(--ink-2)", marginTop: 7, lineHeight: 1.55 }}>{a.why}</div>}

            <Eyebrow style={{ margin: "16px 0 7px" }}>{a.series ? "Series" : manual ? "Order" : "Proposal"}</Eyebrow>
            {a.series ? (
              <SeriesReviewRows items={a.series} />
            ) : (
            <div style={{ border: "1px solid var(--rule-2)", borderRadius: 12, overflow: "hidden" }}>
              {a.legs.map((leg, i) => <LegRow key={i} leg={leg} last={i === a.legs.length - 1} />)}
            </div>
            )}

            {/* first-run preview — the rule resolved against today's numbers:
                what would move right now, or why it wouldn't fire yet */}
            {a.preview && (
              <div style={{ display: "flex", gap: 9, alignItems: "flex-start", marginTop: 10, padding: "11px 12px", borderRadius: 12, border: "1px solid color-mix(in srgb, var(--accent) 45%, transparent)", background: "color-mix(in srgb, var(--accent) 8%, transparent)" }}>
                <span style={{ marginTop: 1.5, flex: "none" }}><Icon name="clock" size={14} color="var(--accent)" stroke={1.7} /></span>
                <span style={{ minWidth: 0 }}>
                  <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 600 }}>{a.preview.t}</span>
                  <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-2)", marginTop: 3, lineHeight: 1.5 }}>{a.preview.s}</span>
                </span>
              </div>
            )}

            {/* amount summary — plain label + value text (no boxes), matching
                the reference app's brief detail */}
            <ProposalAmountSummary p={a} />

            <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 12, marginBottom: 8, padding: "10px 12px", background: "var(--bg-2)" }}>
              <Icon name="shield" size={16} color="var(--ink-3)" stroke={1.5} />
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-3)", lineHeight: 1.45 }}>
                {a.kind === "automation" ? "Turns on only after you approve. Future runs follow these parameters and limits."
                  : queues ? "Markets are closed, so this queues as a one-time order for tomorrow's market open. Cancel free anytime before then."
                  : manual ? "Executes immediately after your passkey. It lands in Activity like everything else."
                  : "Yoshi makes the move, only after you confirm."}
              </span>
            </div>

            {/* the trust ladder — after a streak of approvals on an ask-each-time
                rule, offer the switch to automatic right in the moment of trust */}
            {a.source && a.source.approvals >= 3 && (
              <div style={{ border: "1px solid var(--rule-2)", borderRadius: 12, padding: "12px 13px", marginBottom: 8 }}>
                {graduated ? (
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <Icon name="check" size={15} color="var(--accent-pos)" stroke={1.8} />
                    <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-2)", lineHeight: 1.5 }}>Done — future runs execute automatically within the rule's limits. This one still needs your approval below.</span>
                  </div>
                ) : (
                  <>
                    <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
                      <Icon name="bulb" size={15} color="var(--accent)" stroke={1.6} />
                      <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 600 }}>You've approved this rule {a.source.approvals} runs in a row</span>
                    </div>
                    <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-3)", marginTop: 5, lineHeight: 1.5 }}>Let it run automatically within its limits — {a.source.limits}. You can switch back anytime.</div>
                    <Btn kind="ghost" size="sm" onClick={() => setGraduated(true)} style={{ marginTop: 9 }}>Switch to automatic</Btn>
                  </>
                )}
              </div>
            )}
          </div>

          <div style={{ flex: "none", padding: "0 20px" }}><Seam style={{ margin: "12px 0 0", background: "var(--rule-2)", opacity: 1 }} /></div>
          <div style={{ flex: "none", padding: "12px 20px 20px" }}>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
              {manual
                ? <Btn kind="ghost" onClick={onClose}>Cancel</Btn>
                : <Btn kind="danger" onClick={() => setPhase("decline")}>Decline</Btn>}
              {/* one button → native passkey handles auth; then the action
                  runs and a toast confirms it (no full-screen success page). */}
              <Btn onClick={() => onApproved(a)}>Confirm with passkey</Btn>
            </div>
            {!manual && onAsk && (
              <div style={{ marginTop: 10 }}>
                <YoshiComposer value={msg} onChange={setMsg} onSend={() => { const t = msg.trim(); if (t) { setMsg(""); onAsk(a, t); } }} placeholder="Talk to Yoshi…" />
              </div>
            )}
          </div>
        </>
      )}

      {phase === "decline" && (
        <DeclineBody a={a} onBack={() => setPhase("review")} onDeclined={onDeclined} />
      )}
    </div>
  );
};

const DeclineBody = ({ a, onBack, onDeclined }) => {
  const [r, setR] = useState(null);
  const [note, setNote] = useState("");
  return (
    <>
      <div style={{ padding: "8px 20px 0", flex: 1 }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(18), fontWeight: 600, letterSpacing: "-0.018em" }}>Why decline?</div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-2)", marginTop: 5, lineHeight: 1.5 }}>Yoshi learns from this. It won't run.</div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 16 }}>
          {CONFIRM_DECLINES.map((reason) => (
            <button key={reason} className="press" onClick={() => setR(reason)} style={{ padding: "9px 13px", borderRadius: 999, background: r === reason ? "var(--ink)" : "transparent", color: r === reason ? "var(--bg)" : "var(--ink)", border: "1px solid var(--rule-2)", fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 500, cursor: "pointer" }}>{reason}</button>
          ))}
        </div>
        <textarea value={note} onChange={(e) => setNote(e.target.value)} placeholder="Or tell Yoshi why, in your own words…" rows={3}
          style={{ width: "100%", boxSizing: "border-box", marginTop: 14, padding: "12px 13px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 12, color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.5, outline: "none", resize: "none" }} />
      </div>
      <div style={{ flex: "none", padding: "16px 20px 20px" }}>
        <Btn kind="danger" full onClick={() => onDeclined(a, note.trim() || r || "Not now")}>Decline proposal</Btn>
        <Btn kind="ghost" full onClick={onBack} style={{ marginTop: 8 }}>Back</Btn>
      </div>
    </>
  );
};

/* ============================================================
   Agent cards · rendered inline in the Yoshi thread (and on Home
   for pending proposals). Terminal or actionable — a card answers
   the question or carries ≤2 buttons that lead to ConfirmSheet.
   ============================================================ */

/* from→to route + amount pulled from a proposal's legs, so the card can state
   exactly what will happen instead of a vague "Net". */
const propRoute = (p) => {
  const find = (t) => p.legs && p.legs.find((l) => l[0] === t);
  if (p.kind === "automation") {
    const when = find("WHEN"), action = find("PAY") || find("DO");
    const scope = action && (String(action[2] || "").split(" · ")[0] || action[1]);
    return [when && when[1], scope].filter(Boolean).join(" · ");
  }
  const from = find("FROM"), to = find("TO") || find("PAY");
  const parts = [];
  if (from) parts.push("From " + from[1]);
  if (to) parts.push((from ? "to " : "To ") + to[1]);
  return parts.join(" ");
};
/* transfer direction — the card's sign means "impact on your Yoshi balance":
   external → Yoshi shows +, Yoshi → external shows −, and Yoshi → Yoshi shows
   unsigned (nothing enters or leaves; the money just changes rooms).
   Yoshi-side detection: the label says Yoshi, or carries a Yoshi account mask
   (legs write the same account as "Yoshi Cash ••8841" or just "Cash ••8841"). */
const YOSHI_MASKS = ["8841", "2207", "7731", "4410"];
const isYoshiAcct = (label) => typeof label === "string" && (/yoshi/i.test(label) || YOSHI_MASKS.some((m) => label.includes("••" + m)));
const transferDirection = (p) => {
  if (p.kind !== "transfer" || !p.legs) return null;
  const from = p.legs.find((x) => x[0] === "FROM");
  const to = p.legs.find((x) => x[0] === "TO") || p.legs.find((x) => x[0] === "PAY");
  if (!from || !to) return null;
  const f = isYoshiAcct(from[1]), t = isYoshiAcct(to[1]);
  return f && t ? "internal" : t ? "in" : f ? "out" : null;
};
const propAmount = (p) => {
  const dir = transferDirection(p);
  if (dir) {
    const from = p.legs.find((x) => x[0] === "FROM");
    const mag = p.amount || (from && from[3] ? from[3].replace(/^[-−+]\s*/, "") : null);
    if (mag) return dir === "in" ? "+" + mag : dir === "out" ? "−" + mag : mag;
  }
  const l = p.legs && (p.legs.find((x) => x[0] === "FROM") || p.legs.find((x) => x[0] === "PAY") || p.legs.find((x) => x[0] === "TO"));
  const cell = l && l[3];
  return (cell && /\d/.test(cell)) ? cell : (p.net ? usd(Math.abs(p.net)) : null);
};

/* proposal card — the only thing allowed to "pool", and it pools on Home */
const PCard = ({ p, onReview, compact }) => {
  const route = propRoute(p);
  const amount = propAmount(p);
  const dir = transferDirection(p);
  const theme = useTheme();
  const inkBrand = theme === "bone" ? "brightness(0)" : "brightness(0) invert(1)";
  return (
    <div style={{ border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
      <div style={{ padding: "13px 14px 14px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
          {/* identity stays with the sender: Yoshi's accent dot for Yoshi work
              (incl. rule-fired), the agent's own brand mark for external agents */}
          {p.agentIcon && typeof AGENT_ICON !== "undefined" && AGENT_ICON[p.agentIcon] ? (
            <img src={AGENT_ICON[p.agentIcon]} alt="" style={{ width: 12, height: 12, objectFit: "contain", filter: inkBrand, opacity: 0.8, flex: "none" }} />
          ) : (
            <span style={{ width: 6, height: 6, borderRadius: 999, flex: "none", background: "var(--accent)" }} />
          )}
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), fontWeight: 600, color: "var(--ink-3)", letterSpacing: "0.04em" }}>{p.source ? `${p.source.name} · asked first` : p.agent}</span>
        </div>
        {/* explicit action + amount */}
        <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginTop: 7 }}>
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 600, letterSpacing: "-0.02em", minWidth: 0 }}>{p.title}</span>
          {amount && <span style={{ marginLeft: "auto", fontFamily: "var(--f-mono)", fontSize: typeSize(15), fontWeight: 600, color: dir === "in" ? "var(--accent-pos)" : "var(--ink)", whiteSpace: "nowrap" }}>{amount}</span>}
        </div>
        {/* details: from where to where */}
        {route && <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-2)", marginTop: 5, lineHeight: 1.45 }}>{route}</div>}
        {!compact && p.why && <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-3)", marginTop: 6, lineHeight: 1.5 }}>{p.why}</div>}
        <Btn full onClick={onReview} style={{ marginTop: 12 }}>Review <Icon name="arrow" size={16} color="var(--accent-ink)" /></Btn>
      </div>
    </div>
  );
};

/* chat proposal card — compact, matches the web app (and main's InlineProposal):
   a "Proposal" eyebrow (accent), the title, the net + a settle/starts label, and
   a small Review button. No description paragraph — that lives in the review. */
const ChatProposal = ({ p, onReview, done }) => (
  <div style={{ width: "min(184px, 100%)", border: "1px solid " + (done ? "var(--rule)" : "var(--accent)"), background: "var(--bg-card)", borderRadius: 12, boxSizing: "border-box" }}>
    <div style={{ padding: "8px 10px 10px", display: "flex", flexDirection: "column" }}>
      <Eyebrow color={done ? "var(--ink-3)" : "var(--accent)"}>{done ? "● Executed" : "Proposal"}</Eyebrow>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 600, letterSpacing: "-0.012em", marginTop: 4, lineHeight: 1.2 }}>{p.title}</div>
      <div style={{ display: "flex", alignItems: "baseline", gap: 5, marginTop: 5 }}>
        <Money value={p.net} size={11.5} sign color={p.net >= 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
        <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(8.5), color: "var(--ink-3)", marginLeft: "auto" }}>{p.settlesVerb || "settles"} {p.settles}</span>
      </div>
      {!done && <Btn full size="sm" onClick={onReview} style={{ marginTop: 9 }}>Review</Btn>}
    </div>
  </div>
);

/* quote card — replaces the Trade browse surface. Ask about a ticker, get
   the price and the two buttons that matter. */
const QuoteCard = ({ id, nav }) => {
  const m = MARKET.find((x) => x.id === id || x.ticker === String(id).toUpperCase());
  if (!m) return null;
  const h = securityHolding(m.id);
  const data = series(m.id.length * 7 + 13, 34, m.last * (1 - m.dch / 100), m.last, 0.012);
  return (
    <div style={{ border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
      <button className="press" onClick={() => nav.security(m.id)} style={{ width: "100%", textAlign: "left", background: "none", border: "none", padding: "13px 14px 4px", cursor: "pointer" }}>
        <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(13), fontWeight: 700, color: "var(--accent)" }}>{m.ticker}</span>
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{m.name}</span>
        </div>
        <div style={{ display: "flex", alignItems: "baseline", gap: 9, marginTop: 6 }}>
          <Money value={m.last} size={20} weight={600} />
          <Delta pct={m.dch} size={12} showAbs={false} />
          {h && <span style={{ marginLeft: "auto", fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)" }}>You hold {compactUsd(h.value)}</span>}
        </div>
      </button>
      <div style={{ padding: "2px 8px 0" }}>
        <Chart data={data} height={56} showAxisLabels={false} baseline={false} accent={m.dch >= 0 ? "var(--accent-pos)" : "var(--signal-neg)"} />
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, padding: "10px 12px 12px" }}>
        <Btn onClick={() => nav.sheet({ type: "trade", id: m.id, side: "BUY" })} style={{ padding: "10px" }}>Buy</Btn>
        <Btn kind="ghost" onClick={() => nav.sheet({ type: "trade", id: m.id, side: "SELL" })} style={{ padding: "10px" }}>Sell</Btn>
      </div>
    </div>
  );
};

/* comparison card — replaces Studio's analyze workspace. Rendered on request. */
const CompareCard = ({ aLabel = "Your portfolio", bLabel = "S&P 500", aRet = 18.9, bRet = 14.2 }) => {
  const A = series(7, 40, 100, 100 + aRet, 0.02);
  const B = series(8, 40, 100, 100 + bRet, 0.014);
  const all = [...A, ...B];
  const min = Math.min(...all), max = Math.max(...all), R = max - min || 1;
  const W = 320, H = 96;
  const path = (d) => d.map((v, i) => `${i === 0 ? "M" : "L"}${((i / (d.length - 1)) * W).toFixed(1)},${(8 + (1 - (v - min) / R) * (H - 16)).toFixed(1)}`).join(" ");
  return (
    <div style={{ border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)", padding: "13px 14px" }}>
      <Eyebrow>Past year · indexed</Eyebrow>
      <svg width="100%" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" style={{ display: "block", marginTop: 10 }}>
        <line x1="0" y1={H - 8} x2={W} y2={H - 8} stroke="var(--rule)" strokeWidth="1" strokeDasharray="2 3" vectorEffect="non-scaling-stroke" />
        <path d={path(B)} fill="none" stroke="var(--ink-3)" strokeWidth="1.4" vectorEffect="non-scaling-stroke" />
        <path d={path(A)} fill="none" stroke="var(--accent)" strokeWidth="1.6" vectorEffect="non-scaling-stroke" />
      </svg>
      <div style={{ display: "flex", gap: 16, marginTop: 10 }}>
        {[[aLabel, aRet, "var(--accent)"], [bLabel, bRet, "var(--ink-3)"]].map(([l, r, c]) => (
          <span key={l} style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
            <span style={{ width: 14, height: 2, background: c, flex: "none" }} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-2)" }}>{l}</span>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(11), fontWeight: 600, color: c === "var(--accent)" ? "var(--accent-pos)" : "var(--ink-2)" }}>{pct(r)}</span>
          </span>
        ))}
      </div>
    </div>
  );
};

/* insight card — replaces the Briefs "insights" tab. Pushed, never pooled. */
const InsightChip = ({ label, stat, read }) => (
  <div style={{ border: "1px solid var(--rule)", borderRadius: 12, background: "var(--bg-card)", padding: "13px 14px" }}>
    <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
      <Eyebrow>{label}</Eyebrow>
      <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(16), fontWeight: 600, color: "var(--accent-pos)" }}>{stat}</span>
    </div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-2)", marginTop: 6, lineHeight: 1.5 }}>{read}</div>
  </div>
);

/* document card — replaces the Docs hub. Ask, receive, done. */
const DocCard = ({ name, sub, flash }) => (
  <div style={{ border: "1px solid var(--rule)", borderRadius: 12, background: "var(--bg-card)", padding: "13px 14px", display: "flex", alignItems: "center", gap: 12 }}>
    <span style={{ width: 34, height: 34, flex: "none", border: "1px solid var(--rule-2)", borderRadius: 9, display: "grid", placeItems: "center", color: "var(--ink-2)" }}><Icon name="doc" size={17} /></span>
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), fontWeight: 600 }}>{name}</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-3)", marginTop: 2 }}>{sub}</div>
    </div>
    <Btn kind="quiet" onClick={() => flash && flash("Sent to your email")} style={{ padding: "9px 14px" }}>Share</Btn>
  </div>
);

/* automations card — replaces the automations tab. The agent manages the
   rules; this is the glanceable list, conversational management. */
const AutosCard = ({ flash }) => (
  <div style={{ border: "1px solid var(--rule)", borderRadius: 12, background: "var(--bg-card)", overflow: "hidden" }}>
    {AUTOMATIONS.slice(0, 5).map((au, i) => (
      <div key={au.id} style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 10, alignItems: "center", padding: "11px 14px", borderTop: i ? "1px solid var(--rule)" : "none" }}>
        <div style={{ minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
            <span style={{ width: 5, height: 5, borderRadius: 999, flex: "none", background: au.status === "active" ? "var(--accent)" : "var(--ink-3)" }} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{au.name}</span>
          </div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)", marginTop: 2 }}>{au.cadence}</div>
        </div>
        <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12), color: au.measure.startsWith("+") ? "var(--accent-pos)" : "var(--ink-2)" }}>{au.measure}</span>
      </div>
    ))}
    <div style={{ padding: "10px 14px", borderTop: "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-3)", lineHeight: 1.45 }}>
      Tell me to pause, change, or add a rule — in plain words.
    </div>
  </div>
);

Object.assign(window, { LegRow, StatCell, ConfirmSheet, PCard, QuoteCard, CompareCard, InsightChip, DocCard, AutosCard, TransactionSubmissionScreen, transactionStatusForTracker, transactionStatusForBrief, briefOutcomeItems, transactionRowsForBrief, seriesInflightItem, automationInflightItem });
