/* simple/tabs.jsx — the three surfaces.
   HOME: your money at Yoshi. 7 elements, no scrolling required.
   YOSHI: the interface. Research, trades, moves, automations, docs,
          support — all conversation, rendered as inline cards.
   ACTIVITY: the audit trail. Everything that touched money, one feed,
             tagged with who acted. */

/* ============================================================
   HOME
   ============================================================ */
const HOME_RANGES = ["1D", "1W", "1M", "6M", "YTD", "1Y", "ALL"];
const HOME_RETS = { "1D": DAY_PCT, "1W": 1.2, "1M": 3.1, "6M": 9.4, "YTD": 11.2, "1Y": 18.9, "ALL": 64.0 };
const homeSeries = (range) => series(101 + HOME_RANGES.indexOf(range) * 31, 36, NET_WORTH / (1 + HOME_RETS[range] / 100), NET_WORTH, range === "1D" ? 0.0016 : 0.006);

const AllocStrip = ({ nav }) => {
  const segs = [
    ["Cash", CASH_TOTAL, "var(--alloc-cash)"],
    ["Stocks", INVEST_TOTAL, "var(--alloc-invest)"],
    ["Crypto", CRYPTO_TOTAL, "var(--alloc-crypto)"],
  ];
  const total = CASH_TOTAL + INVEST_TOTAL + CRYPTO_TOTAL;
  // display-only: sits just above Move/Trade, so no tap target to avoid mis-taps
  return (
    <div>
      <div style={{ display: "flex", height: 6, gap: 2, marginBottom: 10, overflow: "hidden" }}>
        {segs.map(([l, v, c]) => <div key={l} style={{ background: c, width: `${(v / total) * 100}%` }} />)}
      </div>
      <div style={{ display: "flex", gap: 2 }}>
        {segs.map(([l, v, c]) => (
          <div key={l} style={{ width: `${(v / total) * 100}%`, display: "flex", flexDirection: "column", alignItems: "center" }}>
            <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: typeSize(11), color: "var(--ink-2)" }}>{((v / total) * 100).toFixed(2)}%</span>
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), fontWeight: 600, color: "var(--ink-2)" }}>{l}</span>
            </div>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(11), color: "var(--ink-3)", marginTop: 2 }}>{usd(v)}</span>
          </div>
        ))}
      </div>
    </div>
  );
};

/* holdings — what you hold, with live day moves. Below the pulse so the
   number up top is the summary and this is the detail beneath it. */
const HoldingRow = ({ h, nav, first }) => (
  <button className="press" onClick={() => nav.security(h.id)} style={{ width: "100%", textAlign: "left", display: "grid", gridTemplateColumns: "38px 1fr auto", gap: 11, alignItems: "center", padding: "11px 14px", background: "none", border: "none", borderTop: first ? "none" : "1px solid var(--rule)", cursor: "pointer" }}>
    <span style={{ width: 38, height: 38, flex: "none", borderRadius: 9, background: "var(--bg-2)", display: "grid", placeItems: "center", fontFamily: "var(--f-mono)", fontSize: typeSize(11), fontWeight: 700, color: h.kind === "crypto" ? "var(--alloc-crypto)" : "var(--accent)" }}>{h.ticker.slice(0, 4)}</span>
    <div style={{ minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{h.name}</div>
      {/* value of the holding */}
      <div style={{ marginTop: 2 }}><Money value={h.value} size={11.5} color="var(--ink-3)" dim="var(--ink-3)" /></div>
    </div>
    {/* price of the stock today + change today */}
    <span style={{ textAlign: "right", whiteSpace: "nowrap" }}>
      <Money value={h.last} size={13.5} />
      <div style={{ marginTop: 2 }}><Delta abs={h.last * h.dch / 100} pct={h.dch} size={10.5} /></div>
    </span>
  </button>
);

const HoldingsList = ({ nav }) => {
  // GROUPS.*.items is snapshotted in data.jsx before accounts.jsx pushes its
  // EXTRA_HOLDINGS into the shared HOLDINGS array — so this stays the user's
  // actual held positions, not the wider tradeable universe.
  const rows = [...GROUPS.investments.items, ...GROUPS.crypto.items].sort((a, b) => b.value - a.value);
  return (
    <div>
      <button className="press" onClick={() => nav.sheet({ type: "trade", holdings: true })} style={{ width: "100%", display: "flex", alignItems: "center", gap: 6, background: "none", border: "none", padding: "10px 2px", cursor: "pointer" }}>
        <Eyebrow color="var(--accent)">Holdings</Eyebrow>
        <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 4, fontFamily: "var(--f-display)", fontSize: typeSize(11.5), fontWeight: 600, color: "var(--accent)" }}>
          View all <Icon name="back" size={13} color="var(--accent)" style={{ transform: "scaleX(-1)" }} />
        </span>
      </button>
      <div style={{ marginTop: 4, border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
        {rows.map((h, i) => <HoldingRow key={h.id} h={h} nav={nav} first={i === 0} />)}
      </div>
    </div>
  );
};

/* Manual fast paths — same centered icon + label as before, just more
   presence: a hairline border, a touch more height, and an accent-lit icon. */
const FastBtn = ({ icon, label, onClick }) => (
  <button className="press" onClick={onClick} style={{
    display: "flex", alignItems: "center", justifyContent: "center", gap: 8, width: "100%",
    padding: "15px 18px", background: "var(--bg-2)", border: "1px solid var(--accent)",
    borderRadius: 12, cursor: "pointer",
    fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 600, letterSpacing: "0.01em", color: "var(--accent)",
  }}>
    <Icon name={icon} size={17} color="var(--accent)" /> {label}
  </button>
);

const HomeTab = ({ nav, proposals, onReview, hideFastPaths, inflight = [] }) => {
  const [range, setRange] = useState("1D");
  const [scrub, setScrub] = useState(null);
  // money in motion — one-time instructions still in flight ("one balance"
  // means moving money is neither here nor there; say where it is)
  const moving = inflight.filter((x) => x.kind === "once");
  const movingSum = moving.reduce((s, x) => s + Math.abs(x.net || 0), 0);
  // pending transfers move real money in/out of Yoshi, so the balance reflects
  // them immediately (+ in / − out). A trade is cash ↔ security — net-zero on
  // total value until the position moves after it executes — so it doesn't shift
  // the balance here.
  // a queued one-time automation gated on incoming funds (fundedBy) hasn't run
  // yet, so it doesn't move the balance — only genuinely in-flight transfers do
  const balanceDelta = moving.filter((x) => x.category === "Transfer" && !x.fundedBy).reduce((s, x) => s + (x.net || 0), 0);
  const liveNetWorth = NET_WORTH + balanceDelta;
  const data = useMemo(() => homeSeries(range), [range]);
  const ret = HOME_RETS[range];
  const abs = NET_WORTH - NET_WORTH / (1 + ret / 100);
  const shown = proposals.slice(0, 1);
  const chartColor = ret >= 0 ? "var(--accent-pos)" : "var(--signal-neg)";
  // drag across the chart → read the value + change at that point in time
  const scrubbing = scrub != null && data[scrub] != null;
  // headline = live balance, which already reflects the inbound transfer via
  // balanceDelta (In motion item f2). Money in motion sits in the one balance;
  // orders that need it just wait in the In motion list until it lands.
  const dispVal = scrubbing ? data[scrub] : liveNetWorth;
  const dispAbs = scrubbing ? data[scrub] - data[0] : abs;
  const dispPct = scrubbing ? ((data[scrub] - data[0]) / data[0]) * 100 : ret;
  return (
    <div style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
      {/* header: hamburger (settings) · bell (briefs) — logo lives in the Yoshi tab */}
      <div style={{ flex: "none", display: "flex", alignItems: "center", padding: "6px 16px 2px", minHeight: 40 }}>
        <button className="press" onClick={() => nav.sheet({ type: "profile" })} aria-label="Menu" style={{ background: "none", border: "none", padding: 6, marginLeft: -6, color: "var(--ink)", display: "flex", cursor: "pointer" }}>
          <Icon name="menu" size={22} />
        </button>
        <span style={{ marginLeft: "auto" }}><BellButton nav={nav} /></span>
      </div>

      <div className="scroll" style={{ padding: "4px 18px 24px" }}>
        {/* 1+2 · the number and the day — tap to see the accounts behind it
             (isolated up here, away from Move/Trade, so no mis-taps) */}
        <button className="press" onClick={() => nav.sheet({ type: "accounts" })} style={{ display: "block", width: "100%", textAlign: "left", background: "none", border: "none", padding: 0, cursor: "pointer" }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
            <Eyebrow>Yoshi balance</Eyebrow>
            <Icon name="back" size={12} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
          </span>
          <div style={{ marginTop: 5, lineHeight: 1 }}><Money value={dispVal} size={38} weight={500} /></div>
          <div style={{ marginTop: 7, display: "flex", alignItems: "baseline", gap: 7 }}>
            <Delta abs={dispAbs} pct={dispPct} size={13} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-3)" }}>{scrubbing ? chartDateLabel(range, scrub, data.length) : (range === "1D" ? "Today" : range)}</span>
          </div>
        </button>

        {/* in-flight money annotates the balance — a clock pill that taps
            through to Activity's In motion section, which now covers inbound
            transfers too. One item shows its own signed amount (green when
            arriving); several show the total. */}
        {moving.length > 0 && (() => {
          const one = moving.length === 1 ? moving[0] : null;
          const amt = one ? one.net : movingSum;
          const label = one ? "in motion" : `in motion · ${moving.length} items`;
          return (
            <div style={{ marginTop: 10 }}>
              <button className="press" onClick={() => nav.tab("activity")} style={{
                display: "inline-flex", alignItems: "center", gap: 9,
                padding: "6px 10px 6px 8px", background: "none",
                border: "1px solid var(--rule-2)", borderRadius: 999, cursor: "pointer",
              }}>
                <span style={{ width: 22, height: 22, flex: "none", borderRadius: 999, background: "var(--bg-2)", display: "grid", placeItems: "center" }}>
                  <Icon name="clock" size={13} color="var(--accent)" stroke={1.6} />
                </span>
                <Money value={amt} size={13} sign={!!one} weight={600} color={one && one.net > 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
                <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)" }}>{label}</span>
                <Icon name="back" size={13} color="var(--ink-3)" style={{ transform: "scaleX(-1)", marginLeft: 1 }} />
              </button>
            </div>
          );
        })()}

        {/* 3 · sparkline — drag across to scrub the value over time */}
        <div style={{ margin: "8px 0 0" }}>
          <Chart data={data} height={104} accent={chartColor} fillColor={chartColor} padY={4} scrub onScrub={(idx) => setScrub(idx)} range={range} yLabelFormatter={(n) => "$" + Math.round(n / 1000) + "k"} />
        </div>
        <div style={{ display: "flex", gap: 3, marginTop: 4 }}>
          {HOME_RANGES.map((r) => (
            <button key={r} className="press" onClick={() => { setRange(r); setScrub(null); }} style={{ flex: 1, padding: "3px 0", background: r === range ? "var(--bg-2)" : "transparent", border: "none", borderRadius: 7, fontFamily: "var(--f-mono)", fontSize: typeSize(12), fontWeight: 500, letterSpacing: "0.02em", color: r === range ? "var(--ink)" : "var(--ink-3)", cursor: "pointer", fontVariantNumeric: "tabular-nums" }}>{r}</button>
          ))}
        </div>

        {/* 4 · allocation strip — one account, three rails */}
        <div style={{ marginTop: 12 }}><AllocStrip nav={nav} /></div>

        {/* 5 · manual fast paths — the two doors, above the fold.
             Original TransferFlow / TradeSheet, unchanged. On web these live
             in the top bar instead, so the shell hides them here. */}
        {!hideFastPaths && (
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginTop: 16 }}>
            <FastBtn icon="swap" label="Transfer" onClick={() => nav.sheet({ type: "transfer" })} />
            <FastBtn icon="trade" label="Trade" onClick={() => nav.sheet({ type: "trade" })} />
          </div>
        )}

        {/* 6 · needs you */}
        <div style={{ marginTop: 18 }}>
          <div style={{ display: "flex", alignItems: "baseline" }}>
            <Eyebrow color="var(--accent)">Needs you</Eyebrow>
            {proposals.length > 1 && (
              <button className="press" onClick={() => nav.sheet({ type: "briefs" })} style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 4, background: "none", border: "none", padding: "2px 0", cursor: "pointer", fontFamily: "var(--f-display)", fontSize: typeSize(11.5), fontWeight: 600, color: "var(--accent)" }}>
                +{proposals.length - 1} more <Icon name="back" size={13} color="var(--accent)" style={{ transform: "scaleX(-1)" }} />
              </button>
            )}
          </div>
          {proposals.length > 0 ? (
            <div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 8 }}>
              {shown.map((p) => <PCard key={p.id} p={p} compact onReview={() => onReview(p)} />)}
            </div>
          ) : (
            <div style={{ marginTop: 8, fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: 600, color: "var(--ink-2)", lineHeight: 1.45 }}>
              You're all caught up
            </div>
          )}
        </div>

        {/* 7 · holdings — what you hold, live day moves */}
        <div style={{ marginTop: 18 }}><HoldingsList nav={nav} /></div>
      </div>
    </div>
  );
};

/* ============================================================
   YOSHI · the interface. The reply engine renders capability
   cards inline — each one a screen this redesign deleted.
   ============================================================ */
const YOSHI_QUICK = ["What's my full net worth?", "How am I doing vs the S&P?", "Move $2,000 to savings", "Show my automations"];

const yoshiReply = (t, ctx) => {
  const s = t.toLowerCase();
  if (/net worth|everything|total|full picture|external/.test(s)) return {
    t: `Everything together: ${usd(NET_WORTH + 8210.32 + 186400 + 12840 - LIAB_TOTAL)}. That's ${usd(NET_WORTH)} here at Yoshi, plus ${usd(8210.32 + 186400 + 12840)} across Chase, Schwab, and Coinbase, minus ${usd(LIAB_TOTAL)} of debt (the mortgage and two cards). Your biggest lever: the Schwab account is earning almost nothing in its cash sweep — want me to draft a consolidation?`,
  };
  if (/s&p|compare|vs |versus|how am i doing|performance/.test(s)) return {
    t: "You're ahead of the index this year — concentration in NVDA did most of that, which cuts both ways.",
    card: { type: "compare" },
  };
  if (/nvda|nvidia/.test(s)) return { t: securityTake("nvda").take, card: { type: "quote", id: "nvda" } };
  if (/bitcoin|btc/.test(s)) return { t: securityTake("btc").take, card: { type: "quote", id: "btc" } };
  if (/tsla|tesla/.test(s)) return { t: securityTake("tsla").take, card: { type: "quote", id: "tsla" } };
  if (/move|transfer|send/.test(s)) {
    const amt = parseFloat((s.match(/\$?([\d,]+(?:\.\d+)?)/) || [])[1]?.replace(/,/g, "")) || 2000;
    return {
      t: `Drafted. ${usd(amt)} from Cash to Reserve, instant, no fee. Approve when ready.`,
      proposal: {
        id: "pm_" + Date.now(), agent: "Yoshi agent", title: `Move ${usd(amt)} to Reserve`,
        why: `One-time instant transfer from Yoshi Cash ••8841 to Yoshi Reserve ••2207. Both earn 3.30% APY. This will not repeat.`,
        net: 0, settles: "In seconds", settlesVerb: "arrives", kind: "transfer",
        legs: [["FROM", "Yoshi Cash ••8841", `Balance ${usd(18420.55)}`, "−" + usd(amt)], ["TO", "Yoshi Reserve ••2207", `Balance ${usd(64000)}`, "+" + usd(amt)], ["TIMING", "Instant transfer", "Arrives in seconds", "$0.00 fee"]],
        activity: { icon: "swap", title: "Reserve", detail: `${usd(amt)} · Instant`, category: "Transfer", by: "Yoshi agent", net: 0, amount: amt },
      },
    };
  }
  if (/automat|rules|recurring/.test(s)) return { t: "Six rules running, all inside your limits. They've earned you $841.00 this quarter between interest, the VTI streak, and harvested losses.", card: { type: "autos" } };
  if (/spend|dining|budget/.test(s)) return { t: "Good news, actually:", card: { type: "insight", i: 0 } };
  if (/1099|tax|statement|document|doc/.test(s)) return { t: "Here you go — I can also send these straight to your accountant.", card: { type: "doc", name: "1099-B · 2025", sub: "Yoshi Securities · consolidated" } };
  if (/pay|sapphire|card|debt/.test(s)) return { t: "Your Sapphire statement is $2,340.18, due Friday at 21.99% APR. Paying in full from your Cash keeps you at zero interest — it's ready below.", proposal: PROPOSALS.find((p) => p.id === "p2") };
  if (/cash|idle|savings rate|yield/.test(s)) return { t: "Your cash is working: every Yoshi cash account earns 3.30% APY, and the buffer rule keeps your spendable Cash lean while the excess sits in your Reserve. One idea — $15,000.00 has sat past your buffer for 9 days; short-term Treasuries would add about $760.00 a year and stay liquid. Want the proposal?" };
  return { t: "On it. I can research anything you hold, move money, place trades, set up rules, pull documents, or just explain what's happening — all from right here. What do you want to do?" };
};

const YoshiCardFor = ({ card, nav, flash }) => {
  if (!card) return null;
  if (card.type === "quote") return <QuoteCard id={card.id} nav={nav} />;
  if (card.type === "compare") return <CompareCard />;
  if (card.type === "autos") return <AutosCard flash={flash} />;
  if (card.type === "insight") { const ins = BRIEF_INSIGHTS[card.i || 0]; return <InsightChip label={ins.label} stat={ins.stat} read={ins.read} />; }
  if (card.type === "doc") return <DocCard name={card.name} sub={card.sub} flash={flash} />;
  return null;
};

const YOSHI_SEED = [
  { from: "agent", t: "Morning. You're up " + perf(DAY_ABS, DAY_PCT) + " today — NVDA and BTC did most of it. One thing needs you: your stocks drifted past the mix you set, so I'd like to even things out.", time: "9:24" },
  { from: "user", t: "what would that do", time: "9:25" },
  { from: "agent", t: "Three trades: trim NVDA and BTC, add VOO. You'd come out about $1,153.00 ahead, done by tomorrow. Approve whenever you're ready.", time: "9:25", proposal: "p1" },
];

const YoshiTab = ({ nav, proposals, onReview, inject, onInjected, flash }) => {
  const [msgs, setMsgs] = useState(YOSHI_SEED);
  const [input, setInput] = useState("");
  const [typing, setTyping] = useState(false);
  const endRef = useRef(null);
  useEffect(() => { endRef.current && endRef.current.scrollIntoView({ behavior: "smooth" }); }, [msgs, typing]);
  useEffect(() => {
    if (!inject) return;
    setMsgs((m) => [...m, ...(inject.note ? [{ from: "user", t: inject.note, time: "now" }] : []), { from: "agent", t: inject.reply, time: "now" }]);
    onInjected();
  }, [inject]);

  const send = (text) => {
    const t = (text != null ? text : input).trim();
    if (!t) return;
    setInput("");
    setMsgs((m) => [...m, { from: "user", t, time: "now" }]);
    setTyping(true);
    setTimeout(() => {
      const r = yoshiReply(t);
      setTyping(false);
      setMsgs((m) => [...m, { from: "agent", t: r.t, time: "now", card: r.card, proposalObj: r.proposal }]);
    }, 700);
  };

  const findP = (idOrObj) => typeof idOrObj === "string" ? proposals.find((p) => p.id === idOrObj) || PROPOSALS.find((p) => p.id === idOrObj) : idOrObj;

  return (
    <div style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
      <div style={{ flex: "none", display: "flex", alignItems: "center", gap: 8, padding: "6px 20px 10px", borderBottom: "1px solid var(--rule)" }}>
        <Logo size={18} />
        <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 600, letterSpacing: "-0.02em" }}>Yoshi</span>
        <button className="press" onClick={() => nav.tab("home")} aria-label="Close" style={{ marginLeft: "auto", background: "none", border: "none", padding: 0, color: "var(--ink-3)", display: "flex", cursor: "pointer" }}>
          <Icon name="close" size={19} />
        </button>
      </div>

      <div className="scroll" style={{ padding: "14px 16px 8px" }}>
        {msgs.map((m, i) => {
          const p = m.proposal ? findP(m.proposal) : m.proposalObj;
          return (
            <div key={i} style={{ marginBottom: 12 }}>
              <div style={{ display: "flex", justifyContent: m.from === "user" ? "flex-end" : "flex-start" }}>
                <div style={{ maxWidth: "86%", padding: "10px 13px", borderRadius: m.from === "user" ? "14px 14px 4px 14px" : "14px 14px 14px 4px", background: m.from === "user" ? "var(--ink)" : "var(--bg-card)", color: m.from === "user" ? "var(--bg)" : "var(--ink)", border: m.from === "user" ? "none" : "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.5 }}>
                  {m.t}
                </div>
              </div>
              {p && <div style={{ marginTop: 8 }}><ChatProposal p={p} onReview={() => onReview(p)} /></div>}
              {m.card && <div style={{ marginTop: 8 }}><YoshiCardFor card={m.card} nav={nav} flash={flash} /></div>}
            </div>
          );
        })}
        {typing && (
          <div style={{ display: "inline-flex", gap: 4, padding: "12px 14px", background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: "14px 14px 14px 4px" }}>
            {[0, 1, 2].map((i) => <span key={i} className="yo-mk-pulse" style={{ width: 5, height: 5, borderRadius: 999, background: "var(--ink-3)", animationDelay: `${i * 0.2}s` }} />)}
          </div>
        )}
        <div ref={endRef} />
      </div>

      <div style={{ flex: "none", padding: "6px 14px 10px" }}>
        <div className="hcar" style={{ display: "flex", gap: 7, overflowX: "auto", paddingBottom: 8 }}>
          {YOSHI_QUICK.map((qr) => (
            <button key={qr} className="press" onClick={() => send(qr)} style={{ flex: "none", padding: "8px 13px", borderRadius: 999, background: "transparent", border: "1px solid var(--rule-2)", color: "var(--ink-2)", fontFamily: "var(--f-display)", fontSize: typeSize(12), fontWeight: 500, cursor: "pointer" }}>{qr}</button>
          ))}
        </div>
        <YoshiComposer value={input} onChange={setInput} onSend={() => send()} placeholder="Talk to Yoshi…" onAttach={(d) => send(`I attached ${d.name}`)} />
      </div>
    </div>
  );
};

/* ============================================================
   ACTIVITY · the audit trail. One feed, everything, tagged with
   who acted. The trust contract for an agent with authority.
   ============================================================ */
const ACT_FILTERS = ["Yoshi", "External", "Card", "All"];
// a credit-card transaction — detail carries the card, e.g. "Card ··4417"
const cardOf = (tx) => { const m = (tx.detail || "").match(/Card ··(\d{4})/); return m ? "··" + m[1] : null; };

/* Coming up — the live queue (one-time in flight · recurring next runs ·
   predicted events). Data + lifecycle model live in data.jsx (COMING_UP_SEED);
   the stateful list is owned by SimpleApp and arrives via the `coming` prop.
   Chip color says how alive a row is: in-flight → accent, the rest stay calm. */
const inflightChipColor = (item) =>
  item.state === "working" || item.state === "queued" ? "var(--accent)"
  : item.state === "pending" ? "var(--ink-2)"
  : "var(--ink-3)";

const actorOf = (tx) => {
  if (tx.by) return tx.by;
  if (tx.autoId) { const a = AUTOMATIONS.find((x) => x.id === tx.autoId); return a ? a.name : "Yoshi"; }
  if (/Card ··/.test(tx.detail || "")) return "Card";
  if (tx.category === "Income") return "Deposit";
  return "You";
};
const isAgentActed = (tx) => !!tx.autoId || (tx.by && tx.by !== "You" && tx.by !== "Card");

// scope chip → account-registry group; the account filter offers the accounts
// that belong to the active scope (or every account when scope is "All").
const SCOPE_GROUP = { Yoshi: "yoshi", External: "external", Card: "card" };

const ActivityTab = ({ nav, extra, coming = [], filter = "All", onFilter }) => {
  const [q, setQ] = useState("");
  // a specific account within the current scope (null → the whole scope)
  const [acct, setAcct] = useState(null);
  const [fOpen, setFOpen] = useState(false);
  // switching scope changes which accounts exist, so drop any account filter
  useEffect(() => { setAcct(null); setFOpen(false); }, [filter]);
  const acctOptions = filter === "All" ? LEDGER_ACCOUNTS : LEDGER_ACCOUNTS.filter((a) => a.group === SCOPE_GROUP[filter]);
  const acctObj = acct ? LEDGER_ACCOUNTS.find((a) => a.id === acct) : null;
  const rows = [...extra, ...TRANSACTIONS];
  const shown = rows.filter((tx) => {
    if (filter === "Yoshi" && tx.accountScope !== "yoshi") return false;
    if (filter === "External" && tx.accountScope !== "external") return false;
    if (filter === "Card" && !cardOf(tx)) return false;
    if (acct && !accountIdsOf(tx).includes(acct)) return false; // narrow to one account
    const s = q.trim().toLowerCase();
    return !s || (tx.title + " " + (tx.detail || "") + " " + (tx.category || "")).toLowerCase().includes(s);
  });
  // All three sections follow the scope filter (and the account filter, when
  // set). A transfer in/out of a Yoshi account is a Yoshi transaction (scope
  // "yoshi"). In motion is newest-first (the array is prepended on placement).
  const scopeOk = (item) => (filter === "All" || item.scope === filter.toLowerCase()) && (!acct || accountIdsOf(item).includes(acct));
  const inMotion = coming.filter((item) => item.kind === "once" && scopeOk(item));
  const comingShown = coming.filter((item) => item.kind !== "once" && scopeOk(item));

  // one row shape for both sections — predicted events stay passive (plain
  // txn sheet); instructions open the in-flight detail with stepper + actions
  const queueRow = (item) => {
    // the core app labels transfers directionally (Transfer in / Transfer out)
    const iCat = directionalCategory(item.category, item.net);
    // automation rows: one glyph, one word — the rule and route live in the detail
    const isAuto = iCat === "Automation";
    const cSub = isAuto ? "Automation" : (item.acct ? `${iCat} · ${item.acct}` : iCat);
    const cPrefix = isAuto ? "↻ " : `${item.agent ? "● " : ""}${item.kind === "recurring" ? "↻ " : ""}`;
    const passive = item.kind === "predicted";
    const live = item.kind === "once"; // in flight right now — breathes
    // In-motion items open the SAME transaction detail as any other txn, just
    // flagged pending (+ a cancel button when they can still be pulled).
    // Predicted → the same detail as scheduled. Recurring → the rule sheet.
    const canCancel = live && (item.state === "queued" || item.state === "working" || (item.state === "pending" && item.cancelable));
    const open = passive
      ? () => nav.sheet({ type: "txn", tx: { ...item, net: null, amount: Math.abs(item.net), tone: item.net < 0 ? "out" : "in", detail: item.acct } })
      : live
        ? () => nav.sheet({ type: "txn", tx: { ...item, detail: item.acct, pending: true }, item, cancelable: canCancel })
        : () => nav.sheet({ type: "txn", tx: { ...item, net: null, amount: Math.abs(item.net), tone: item.net < 0 ? "out" : "in", detail: [item.rail, item.acct].filter(Boolean).join(" · "), trigger: item.chip, cancelNote: item.skipNote, cancelLabel: "Skip this run" }, item, cancelable: true });
    return (
      <button key={item.id} className="press" onClick={open} style={{ width: "100%", textAlign: "left", display: "grid", gridTemplateColumns: "30px 1fr auto", gap: 12, alignItems: "center", padding: "11px 20px", background: item.fresh ? "color-mix(in srgb, var(--accent) 8%, transparent)" : "none", transition: "background 900ms ease", border: "none", borderBottom: "1px solid var(--rule)", cursor: "pointer", animation: item.fresh ? "count-up 420ms ease both" : "none" }}>
        <span style={{ width: 30, height: 30, borderRadius: 9, background: "var(--bg-2)", display: "grid", placeItems: "center", color: item.agent ? "var(--accent)" : "var(--ink-2)" }}>
          <Icon name={item.icon} size={15} stroke={1.5} />
        </span>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{item.title}</div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: item.agent ? "var(--accent)" : "var(--ink-3)", marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
            {cPrefix}{cSub}
          </div>
        </div>
        <span style={{ textAlign: "right", whiteSpace: "nowrap" }}>
          {item.net !== 0
            ? <Money value={item.net} size={13} sign color={item.net > 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
            : <Money value={item.amount || 0} size={13} color="var(--ink-2)" dim="var(--ink-3)" />}
          {/* In motion rows read a uniform "Pending" with a breathing dot —
              the state/timing lives in the detail sheet. Coming up keeps its
              informative chip (Runs Fri · market open, Monday Jun 15, …). */}
          <div style={{ display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 5, fontFamily: "var(--f-display)", fontSize: typeSize(10.5), fontWeight: passive ? 400 : 600, color: live ? "var(--ink-2)" : inflightChipColor(item), marginTop: 2 }}>
            {live && <span className="yo-mk-pulse" style={{ width: 5, height: 5, borderRadius: 999, background: "var(--accent)", flex: "none" }} />}
            {live ? "Pending" : (item.chip || item.when)}
          </div>
        </span>
      </button>
    );
  };
  return (
    <div style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
      <div style={{ flex: "none", padding: "6px 20px 0" }}>
        <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(22), fontWeight: 700, letterSpacing: "-0.025em" }}>Activity</span>
        {/* search + account filter (the filter offers the accounts in the
            active scope — Yoshi accounts on Yoshi, external on External, …) */}
        <div style={{ position: "relative", marginTop: 12 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "9px 12px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 10 }}>
            <Icon name="search" size={15} color="var(--ink-3)" />
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search activity" style={{ flex: 1, minWidth: 0, background: "transparent", border: "none", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5) }} />
            {q && <button className="press" onClick={() => setQ("")} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", padding: 0, cursor: "pointer" }}><Icon name="close" size={15} /></button>}
            <span style={{ width: 1, height: 18, background: "var(--rule-2)", flex: "none" }} />
            <button className="press" onClick={() => setFOpen((o) => !o)} aria-label="Filter by account" style={{ position: "relative", background: "none", border: "none", display: "flex", padding: 1, color: acct || fOpen ? "var(--accent)" : "var(--ink-3)", cursor: "pointer" }}>
              <Icon name="filter" size={17} stroke={1.6} />
              {acct && <span style={{ position: "absolute", top: -2, right: -2, width: 6, height: 6, borderRadius: 999, background: "var(--accent)", border: "1.5px solid var(--bg-2)" }} />}
            </button>
          </div>
          {/* active account chip — tap to clear */}
          {acctObj && (
            <div style={{ display: "flex", marginTop: 8 }}>
              <button className="press" onClick={() => setAcct(null)} style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "4px 10px", borderRadius: 999, background: "color-mix(in srgb, var(--accent) 12%, var(--bg-2))", border: "1px solid color-mix(in srgb, var(--accent) 40%, var(--rule-2))", fontFamily: "var(--f-display)", fontSize: typeSize(11.5), fontWeight: 600, color: "var(--ink)", cursor: "pointer" }}>
                {acctObj.name}<Icon name="close" size={12} color="var(--ink-3)" />
              </button>
            </div>
          )}
          {/* account dropdown — scoped to the active chip */}
          {fOpen && (
            <>
              <div onClick={() => setFOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 30 }} />
              <div style={{ position: "absolute", right: 0, top: "calc(100% + 4px)", zIndex: 31, minWidth: 214, maxHeight: 320, overflowY: "auto", background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 10, boxShadow: "0 12px 28px -14px rgba(0,0,0,0.35)" }}>
                <div style={{ padding: "9px 13px 7px", borderBottom: "1px solid var(--rule)" }}><Eyebrow style={{ fontSize: typeSize(9.5) }}>Filter by account</Eyebrow></div>
                <button className="press" onClick={() => { setAcct(null); setFOpen(false); }} style={{ width: "100%", display: "flex", alignItems: "center", gap: 8, padding: "9px 13px", background: !acct ? "var(--bg-2)" : "none", border: "none", cursor: "pointer", textAlign: "left" }}>
                  <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: 600, color: "var(--ink)" }}>All accounts</span>
                  {!acct && <Icon name="check" size={14} stroke={2} color="var(--accent)" style={{ marginLeft: "auto" }} />}
                </button>
                {acctOptions.map((a) => (
                  <button key={a.id} className="press" onClick={() => { setAcct(a.id); setFOpen(false); }} style={{ width: "100%", display: "flex", alignItems: "center", gap: 8, padding: "9px 13px", background: acct === a.id ? "var(--bg-2)" : "none", border: "none", borderTop: "1px solid var(--rule)", cursor: "pointer", textAlign: "left" }}>
                    <span style={{ minWidth: 0 }}>
                      <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: 600, color: "var(--ink)" }}>{a.name}</span>
                      <span style={{ display: "block", fontFamily: "var(--f-mono)", fontSize: typeSize(10.5), color: "var(--ink-3)", marginTop: 1 }}>{a.label}</span>
                    </span>
                    {acct === a.id && <Icon name="check" size={14} stroke={2} color="var(--accent)" style={{ marginLeft: "auto" }} />}
                  </button>
                ))}
              </div>
            </>
          )}
        </div>
      </div>
      <div className="scroll">
        {/* In motion — trades and transfers in flight right now. Tap for the
            stepper + cancel; on settlement the row drops into Recent. */}
        {!q && inMotion.length > 0 && (
          <>
            <div style={{ display: "flex", alignItems: "center", gap: 6, padding: "12px 20px 6px" }}>
              <span className="yo-mk-pulse" style={{ width: 5, height: 5, borderRadius: 999, background: "var(--accent)" }} />
              <Eyebrow color="var(--accent)">In motion</Eyebrow>
            </div>
            {inMotion.map(queueRow)}
          </>
        )}
        {/* Coming up — scheduled automations, bills, payday (full view only) */}
        {!q && comingShown.length > 0 && (
          <>
            <div style={{ padding: "12px 20px 6px", borderTop: inMotion.length > 0 && !q ? "1px solid var(--rule)" : "none" }}><Eyebrow>Coming up</Eyebrow></div>
            {comingShown.map(queueRow)}
          </>
        )}
        {!q && (inMotion.length > 0 || comingShown.length > 0) && (
          <div style={{ padding: "12px 20px 6px", borderTop: "1px solid var(--rule)" }}><Eyebrow>Recent</Eyebrow></div>
        )}
        {shown.map((tx, i) => {
          const agent = isAgentActed(tx);
          // when it's today, show the time; otherwise the date
          const stamp = tx.when === "Today" ? (tx.time || "Today") : tx.when;
          // credit-card transactions call out the card; other external
          // transactions call out the account they touched, the same way
          const card = cardOf(tx);
          const acct = card ? `Card ${card}` : (tx.accountScope === "external" ? tx.acct : null);
          const tCat = directionalCategory(tx.category, tx.net);
          const sub = acct ? `${tCat} · ${acct}` : tCat;
          return (
            <button key={tx.id || "x" + i} className="press" onClick={() => nav.sheet({ type: "txn", tx })} style={{ width: "100%", textAlign: "left", display: "grid", gridTemplateColumns: "30px 1fr auto", gap: 12, alignItems: "center", padding: "11px 20px", background: tx.fresh ? "color-mix(in srgb, var(--accent) 8%, transparent)" : "none", transition: "background 900ms ease", animation: tx.fresh ? "count-up 420ms ease both" : "none", border: "none", borderBottom: "1px solid var(--rule)", cursor: "pointer" }}>
              <span style={{ width: 30, height: 30, borderRadius: 9, background: "var(--bg-2)", display: "grid", placeItems: "center", color: agent ? "var(--accent)" : "var(--ink-2)" }}>
                <Icon name={tx.icon || "receipt"} size={15} stroke={1.5} />
              </span>
              <div style={{ minWidth: 0 }}>
                {/* title = the transaction or the account; wraps to 2 lines so
                    'Transferred from X to Y' isn't cut off */}
                <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), fontWeight: 500, lineHeight: 1.3, display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{tx.title}</div>
                {/* category under the title; automation-powered rows stay highlighted */}
                <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: agent ? "var(--accent)" : "var(--ink-3)", marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                  {agent ? "● " : ""}{sub}
                </div>
              </div>
              {/* value on top, date/time beneath */}
              <span style={{ textAlign: "right", whiteSpace: "nowrap" }}>
                {tx.net !== 0
                  ? <Money value={tx.net} size={13} sign color={tx.net > 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
                  : tx.amount ? <Money value={tx.amount} size={13} color="var(--ink-2)" dim="var(--ink-3)" /> : <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), fontWeight: 500, color: "var(--ink-2)" }}>Done</span>}
                <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)", marginTop: 2 }}>{stamp}</div>
              </span>
            </button>
          );
        })}
        {/* an account filter with nothing to show — a quiet note, not a blank */}
        {acctObj && shown.length === 0 && inMotion.length === 0 && comingShown.length === 0 && (
          <div style={{ padding: "28px 20px", textAlign: "center", fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-3)" }}>No activity for {acctObj.name} yet.</div>
        )}
        <div style={{ height: 20 }} />
      </div>
    </div>
  );
};

Object.assign(window, { HomeTab, YoshiTab, ActivityTab, AllocStrip, actorOf });
