/* ============================================================
   finance.jsx — Finance team user (primary persona)
   Focused, low-cognitive-load review — approve / reject.
   ============================================================ */

// Tax type derived from the parsed tax label (no longer hardcoded intra-state).
function gstType(doc) {
  const tl = doc.taxLabel || "";
  if (/CGST/i.test(tl)) return "Intra-state (CGST + SGST)" + (tl ? ` · ${tl}` : "");
  if (/IGST/i.test(tl)) return "Inter-state (IGST)" + (tl ? ` · ${tl}` : "");
  return tl || "—";
}

/* ---- at-a-glance pipeline stats for the Lekha tab ---- */
function LekhaStats({ docs }) {
  // Drive-ingested docs only (fetched === "From Drive") — not seeded/API demo docs.
  const drive = docs.filter((d) => d.fetched === "From Drive");
  const total = drive.length;
  // "written" = the credit note made it into Tally
  const written = drive.filter((d) => ["verified_in_tally", "posted"].includes(d.status)).length;
  // "in progress" = in the pipeline, not yet written and not rejected
  const inProgress = drive.filter((d) => ["draft", "pending_approval", "approved", "posting"].includes(d.status)).length;
  const tiles = [
    { label: "Total documents", val: total, foot: "Ingested from Drive", ic: <Ic.doc />, bg: "var(--blue-bg)", fg: "var(--blue-fg)" },
    { label: "Written to Tally", val: written, foot: "Posted as credit notes", ic: <Ic.tally />, bg: "var(--green-bg)", fg: "var(--green-fg)" },
    { label: "In progress", val: inProgress, foot: "Working through the pipeline", ic: <Ic.refresh />, bg: "var(--amber-bg)", fg: "var(--amber-fg)" },
  ];
  return (
    <div className="kpis mb12">
      {tiles.map((t, i) => (
        <div className="kpi" key={i}>
          <div className="k-top">
            <span className="k-label">{t.label}</span>
            <span className="k-ic" style={{ background: t.bg, color: t.fg }}>{t.ic}</span>
          </div>
          <div className="k-val">{t.val}</div>
          <div className="k-foot">{t.foot}</div>
        </div>
      ))}
    </div>
  );
}

function FinanceApp({ store }) {
  const { docs, bizById, financeApprove, financeReject } = store;
  const [tab, setTab] = React.useState("review");   // lekha-nakad: no Drive entry; review is the start
  const [openId, setOpenId] = React.useState(null);
  // top-bar AccountMenu jumps to a tab via a global event (it doesn't own tab state)
  React.useEffect(() => {
    const nav = (e) => { setTab(e.detail); setOpenId(null); };
    window.addEventListener("lekha-nav", nav);
    return () => window.removeEventListener("lekha-nav", nav);
  }, []);

  const [range, setRange] = React.useState("30d");
  const inRange = (d) => withinRange(d.createdAt, range);
  const reviewQ = docs.filter((d) => ["pending_approval"].includes(d.status) && inRange(d));
  const reviewed = docs.filter((d) => ["approved", "irn_pending", "irn_generated", "irn_failed", "posting", "posted", "post_failed", "verified_in_tally", "cancelled"].includes(d.status) && inRange(d));
  const open = openId && docs.find((d) => d.id === openId);

  const who = store.user || {};
  const myBridge = (store.bridges || []).find((b) => b.business_id === (who.business_id)) || {};
  const [excCount, setExcCount] = React.useState(0);
  React.useEffect(() => { store.financeExceptions && store.financeExceptions().then((d) => setExcCount(d.total)).catch(() => {}); }, [docs.length]);
  const availableDN = docs.filter((d) => d.status === "received").length;   // uploaded, ready to generate
  const tabs = [
    { id: "debitnotes", label: "Debit notes", count: availableDN || null },
    { id: "review", label: "To review", count: reviewQ.length },
    { id: "exceptions", label: "Exceptions", count: excCount || null },
    { id: "done", label: "Reviewed", count: reviewed.length },
    { id: "drive", label: "Drive" },
    { id: "team", label: "Team" },
    { id: "docs", label: "Docs" },
    { id: "notif", label: "Settings" },
  ];

  return (
    <div className="app">
      <AppHeader store={store} />
      <Tabs items={tabs} active={tab} onChange={(t) => { setTab(t); setOpenId(null); }} />
      <div className="main">
        {open ? (
          <ReviewScreen doc={open} biz={bizById(open.biz)} store={store} onBack={() => setOpenId(null)} />
        ) : (
          <div className="container">
            {tab === "debitnotes" && <FinanceDebitNotes store={store} who={who} />}
            {tab === "review" && <FinanceQueue docs={reviewQ} bizById={bizById} onOpen={setOpenId} range={range} setRange={setRange} />}
            {tab === "exceptions" && <FinanceExceptions store={store} />}
            {tab === "done" && <FinanceReviewed docs={reviewed} bizById={bizById} onOpen={setOpenId} range={range} setRange={setRange} store={store} />}
            {tab === "drive" && <DriveDocs who={who} />}
            {tab === "team" && <TeamTab store={store} who={who} />}
            {tab === "docs" && <DocsTab who={who} />}
            {tab === "notif" && <NotificationSettings />}
          </div>
        )}
      </div>
    </div>
  );
}

/* 3-hour TAT after 'Generate Credit Note': the ETA the credit note will be ready,
   anchored to when it was generated (cnCreatedAt). Formatted like "3:46pm". */
function etaBy(cnCreatedAt) {
  if (!cnCreatedAt) return null;
  const t = new Date(cnCreatedAt);
  if (isNaN(t.getTime())) return null;
  t.setHours(t.getHours() + 3);
  let h = t.getHours();
  const m = t.getMinutes();
  const ap = h >= 12 ? "pm" : "am";
  h = h % 12 || 12;
  return `${h}:${String(m).padStart(2, "0")}${ap}`;
}

/* Finance can also run Step 1 — upload a debit-note PDF and generate its credit note
   (same pipeline as Brand Ops: OCR → Lekha admin → finance review → Tally). Mirrors the
   Brand Ops "Debit notes" workbench, reusing the shared store methods. */
function FinanceDebitNotes({ store, who }) {
  const { docs, bizById, uploadDebitNote, generateCreditNote } = store;
  const [busyId, setBusyId] = React.useState(null);
  const [uploading, setUploading] = React.useState(false);
  const [err, setErr] = React.useState("");
  const [range, setRange] = React.useState("all");
  const [filter, setFilter] = React.useState("all");
  const fileRef = React.useRef(null);
  // Brand Ops resolves the brand as `businesses[0] || bizById(...)` — businesses[0] is the
  // scoped biz that carries the finance `email`. bizById NEVER returns falsy (it falls back
  // to {email:""}), so it must be the SECOND operand, else the empty-email fallback wins and
  // greys the upload button even though the business HAS an approver. (Verified: FMC 29 email
  // = accountsecom@fixmycurls.in, returned by /api/businesses.)
  const brand = (store.businesses || [])[0] || bizById(who.business_id || "");
  const noApprover = brand && !brand.email;

  let rows = docs.filter((d) => withinRange(d.createdAt, range));
  if (filter === "available") rows = rows.filter((d) => d.status === "received");
  if (filter === "done") rows = rows.filter((d) => d.status !== "received");
  rows = rows.slice().sort((a, b) =>
    (a.status === "received" ? 0 : 1) - (b.status === "received" ? 0 : 1) ||
    (b.createdAt || "").localeCompare(a.createdAt || ""));
  const availableCount = docs.filter((d) => d.status === "received").length;

  const doUpload = async (e) => {
    const file = e.target.files && e.target.files[0];
    if (e.target) e.target.value = "";
    if (!file) return;
    setErr(""); setUploading(true);
    const res = await uploadDebitNote(file);
    setUploading(false);
    if (res && !res.ok)
      setErr(/already been uploaded/i.test(res.error || "") ? "This exact PDF has already been uploaded."
        : res.error === "no_finance_approver" ? "No authorised finance email is configured for your business yet."
        : "Couldn't upload that PDF — please try again.");
  };
  const generate = async (id) => {
    setErr(""); setBusyId(id);
    const res = await generateCreditNote(id);
    setBusyId(null);
    if (res && !res.ok) setErr("Couldn't generate the credit note just now — please try again.");
  };

  return (
    <>
      <div className="page-head">
        <div><div className="ph-title">Debit notes</div>
          <div className="ph-sub">Upload a debit-note PDF, then generate its credit note — it runs the same pipeline: OCR → Lekha review → your approval → Tally.</div></div>
        <div className="spacer" /><TimeFilter value={range} onChange={setRange} />
      </div>
      {err ? (
        <div className="hint mb20" style={{ background: "var(--red-bg)", borderColor: "var(--red-fg)" }}>
          <Ic.x style={{ marginTop: 1, flex: "0 0 15px", color: "var(--red-fg)" }} /><div style={{ color: "var(--red-fg)" }}>{err}</div>
        </div>
      ) : (
        <div className="hint info mb20"><Ic.bolt style={{ marginTop: 1, flex: "0 0 15px" }} />
          <div><b>{availableCount}</b> debit note{availableCount === 1 ? "" : "s"} ready to generate. Generating moves a note into the pipeline until it returns to your review queue.</div></div>
      )}
      <div className="toolbar">
        <select className="select" value={filter} onChange={(e) => setFilter(e.target.value)}>
          <option value="all">All debit notes</option>
          <option value="available">Available to generate</option>
          <option value="done">Already generated</option>
        </select>
        <div className="spacer" />
        <input ref={fileRef} type="file" accept="application/pdf,.pdf" style={{ display: "none" }} onChange={doUpload} />
        <button className="btn btn-primary btn-sm" disabled={uploading || noApprover}
          onClick={() => fileRef.current && fileRef.current.click()} title="Upload a debit-note PDF from your computer">
          {uploading ? "Uploading…" : "Upload debit note (.pdf)"}
        </button>
      </div>
      <div className="card tbl-wrap">
        <table className="tbl">
          <thead><tr><th>Debit note</th><th>Invoice</th><th>Date</th><th>Amount</th><th style={{ width: 230 }}>Status / Action</th></tr></thead>
          <tbody>
            {rows.map((d) => {
              const available = d.status === "received";
              return (
                <tr key={d.id} style={{ cursor: "default" }}>
                  <td className="doc-no mono">{d.dnNo}</td>
                  <td className="muted mono" style={{ fontSize: 13 }}>{d.invoice || "—"}</td>
                  <td className="muted" style={{ fontSize: 13 }}>{d.docDate || d.fetched}</td>
                  <td className="amt">{fmtINR(d.amount)}</td>
                  <td>{available ? (
                    <button className="btn btn-primary btn-sm" disabled={busyId === d.id} onClick={() => generate(d.id)}>
                      <Ic.bolt />{busyId === d.id ? "Generating…" : "Generate Credit Note"}
                    </button>
                  ) : d.status === "verified_in_tally" ? (
                    <span className="badge green"><span className="b-dot" />Verified in Tally</span>
                  ) : (
                    <div style={{ display: "inline-flex", flexDirection: "column", alignItems: "flex-start", gap: 4 }}>
                      <span className="badge blue"><span className="b-dot" />Processing</span>
                      {etaBy(d.cnCreatedAt || d.createdAt) && (
                        <span className="tiny" style={{ color: "var(--text-3)" }}>
                          Credit note will be with you by {etaBy(d.cnCreatedAt || d.createdAt)}
                        </span>
                      )}
                    </div>
                  )}</td>
                  {/* 3 customer states only: Generate → Processing → Verified in Tally */}
                </tr>
              );
            })}
            {!rows.length && <tr><td colSpan="5"><div className="empty" style={{ padding: "36px 0" }}><div className="e-s">No debit notes yet — upload one to begin.</div></div></td></tr>}
          </tbody>
        </table>
      </div>
    </>
  );
}

function FinanceExceptions({ store }) {
  const [items, setItems] = React.useState(null);
  const load = React.useCallback(() => {
    store.financeExceptions().then((d) => setItems(d.items)).catch(() => setItems([]));
  }, []);
  React.useEffect(() => { load(); }, [load]);
  if (!items) return <div className="empty"><div className="e-s">Loading…</div></div>;
  return (
    <>
      <div className="page-head">
        <div><div className="ph-title">Your exceptions</div><div className="ph-sub">Credit notes Lekha AI couldn't fix from your first feedback. Give it more detail — you get 2 tries, then it goes to the Lekha team.</div></div>
      </div>
      <div className="hint info mb20"><Ic.shield style={{ marginTop: 1, flex: "0 0 15px" }} /><div>These are <b>only yours</b> — the ones you rejected (or Tally rejected after you approved) that need one more round of your guidance. A successful rework comes back to <b>To review</b>; after 2 tries it moves to the Lekha team, who send it back for your approval.</div></div>
      {!items.length && <EmptyState title="Nothing stuck with you" sub="Reworked credit notes come back to your review queue; there's nothing waiting on your feedback." icon={<Ic.check />} />}
      <div className="stack gap12">
        {items.map((e) => <FinanceExcCard key={e.id} exc={e} store={store} onDone={load} />)}
      </div>
    </>
  );
}

function FinanceExcCard({ exc, store, onDone }) {
  const [fb, setFb] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const triesLeft = Math.max(0, 2 - (exc.financeTry || 0));
  const c = exc.case || {};
  return (
    <div className="card card-pad" style={{ borderColor: "var(--amber-fg)" }}>
      <div className="row gap10" style={{ justifyContent: "space-between", flexWrap: "wrap" }}>
        <span className="row gap8">
          <span className="badge amber"><span className="b-dot" />{exc.kind === "TALLY_REJECTED" ? "Tally rejected" : exc.kind === "FLAG_VERIFY_MISMATCH" ? "Not found in Tally" : "Rework needed"}</span>
          <span className="tiny">{c.cnNo || exc.lekhaId || ""}{c.party ? " · " + c.party : ""}</span>
        </span>
        <span className="tiny muted">{triesLeft} of 2 {triesLeft === 1 ? "try" : "tries"} left</span>
      </div>
      <div style={{ fontWeight: 600, marginTop: 10 }}>{exc.reason}</div>
      {exc.explanation && <div className="tiny" style={{ marginTop: 6, lineHeight: 1.6 }}><b>Lekha AI:</b> {exc.explanation}</div>}
      <div className="card" style={{ padding: 12, marginTop: 12, background: "rgba(122,158,63,0.06)" }}>
        <div className="tiny" style={{ fontWeight: 600, marginBottom: 6 }}>Give Lekha AI more detail</div>
        <textarea className="input" rows={2} style={{ width: "100%", marginBottom: 8, resize: "vertical" }}
          placeholder="e.g. “post the sales return to Sales Account”, or “use invoice SL/25-26/02”."
          value={fb} onChange={(e) => setFb(e.target.value)} disabled={busy} />
        <button className="btn btn-primary btn-sm" disabled={busy || !fb.trim()}
          onClick={async () => { setBusy(true); await store.financeExceptionFeedback(exc.id, fb); setBusy(false); setFb(""); onDone(); }}>
          {busy ? "Lekha AI is reworking…" : "Send — Lekha AI reworks it"}
        </button>
      </div>
    </div>
  );
}

function FinanceQueue({ docs, bizById, onOpen, range, setRange }) {
  return (
    <>
      <div className="page-head">
        <div>
          <div className="ph-title">Waiting for your review</div>
          <div className="ph-sub">Approve to post directly into Tally, or reject with a note back to Nakad.</div>
        </div>
        <div className="spacer" />
        <TimeFilter value={range} onChange={setRange} />
      </div>
      <div className="hint info mb20">
        <Ic.shield style={{ marginTop: 1, flex: "0 0 15px" }} />
        <div>Only your authorised email can approve these. Approving auto-posts the entry to Tally — no field-by-field typing. Anything left unactioned <b>auto-approves after 24 hours</b>.</div>
      </div>
      {!docs.length && <EmptyState title="You're all caught up" sub="No credit notes waiting for your review in this period." icon={<Ic.check />} />}
      <div className="stack gap12">
        {docs.map((d) => {
          const biz = bizById(d.biz);
          const pct = Math.round(d.confidence * 100);
          return (
            <button key={d.id} className="card" onClick={() => onOpen(d.id)}
              style={{ textAlign: "left", cursor: "pointer", padding: "18px 22px", display: "flex", alignItems: "center", gap: 18 }}>
              <BizAvatar biz={biz} size={42} />
              <div style={{ minWidth: 0, flex: "1 1 auto" }}>
                <div className="row gap10" style={{ marginBottom: 4 }}>
                  <span style={{ fontWeight: 700, fontSize: 15.5, whiteSpace: "nowrap" }}>{d.cnNo}</span>
                  {d.status === "pending_approval" && <StatusBadge status="pending_approval" customer />}
                </div>
                <div className="tiny" style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                  {biz.name} · against {d.invoice} · {d.lines.length} items · {pct}% confidence
                </div>
              </div>
              <div style={{ textAlign: "right", flex: "0 0 auto" }}>
                <div style={{ fontWeight: 700, fontSize: 18, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" }}>{fmtINR(d.amount)}</div>
              </div>
              <span className="btn btn-primary btn-sm" style={{ flex: "0 0 auto" }}>Review<Ic.arrow /></span>
            </button>
          );
        })}
      </div>
    </>
  );
}

function FinanceReviewed({ docs, bizById, onOpen, range, setRange, store }) {
  // "Written to Tally" summary — everything that actually reached the books.
  const written = docs.filter((d) => ["posted", "verified_in_tally"].includes(d.status));
  const confirmed = written.filter((d) => d.status === "verified_in_tally");
  const totalWritten = written.reduce((s, d) => s + (d.amount || 0), 0);
  const fmtDate = (iso) => {
    if (!iso) return "—";
    try { return new Date(iso).toLocaleDateString("en-IN", { day: "2-digit", month: "short", year: "2-digit" }); }
    catch { return iso.slice(0, 10); }
  };
  return (
    <>
      <div className="page-head"><div><div className="ph-title">Reviewed</div><div className="ph-sub">Your approval history — and a summary of everything written into Tally.</div></div><div className="spacer" /><TimeFilter value={range} onChange={setRange} /></div>

      {/* ---- Written-to-Tally summary ------------------------------------- */}
      <div className="card card-pad mb20">
        <div className="row" style={{ justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 12 }}>
          <div className="row gap10" style={{ alignItems: "center" }}>
            <span className="avatar" style={{ width: 34, height: 34, background: "var(--green-fg)", color: "#fff" }}><Ic.tally /></span>
            <div>
              <div style={{ fontWeight: 800, fontSize: 15 }}>Written to Tally</div>
              <div className="tiny">Credit notes posted into your books in this period</div>
            </div>
          </div>
          <div className="row gap16" style={{ alignItems: "center" }}>
            <div style={{ textAlign: "right" }}>
              <div style={{ fontWeight: 800, fontSize: 20, fontVariantNumeric: "tabular-nums" }}>{written.length}</div>
              <div className="tiny">vouchers</div>
            </div>
            <div style={{ textAlign: "right" }}>
              <div style={{ fontWeight: 800, fontSize: 20, fontVariantNumeric: "tabular-nums" }}>{fmtINR(totalWritten)}</div>
              <div className="tiny">total value</div>
            </div>
            <button className="btn btn-ghost btn-sm" title="Download the full register of every credit note posted through Lekha (Excel)"
              onClick={() => store && store.exportPostedRegister && store.exportPostedRegister()}>
              <Ic.download />Export to Excel
            </button>
          </div>
        </div>
        {written.length > 0 && (
          <div className="tbl-wrap" style={{ marginTop: 14, boxShadow: "none", border: "1px solid var(--border)", borderRadius: 10 }}>
            <table className="tbl">
              <thead><tr><th>Credit note</th><th>Customer / business</th><th>Posted</th><th>Amount</th><th>State</th></tr></thead>
              <tbody>
                {written.map((d) => {
                  const biz = bizById(d.biz);
                  return (
                    <tr key={d.id} onClick={() => onOpen(d.id)}>
                      <td className="doc-no">{d.cnNo}</td>
                      <td><span className="row gap8"><BizAvatar biz={biz} size={22} />{biz.name}</span></td>
                      <td className="muted" style={{ fontSize: 13 }}>{fmtDate(d.confirmedAt || d.postedAt)}</td>
                      <td className="amt">{fmtINR(d.amount)}</td>
                      <td onClick={(e) => e.stopPropagation()}>
                        <StatusBadge status={d.status} customer />
                        {d.deletedInTally ? (
                          <span title="This voucher was posted, then deleted in Tally — auto-detected on sync; its number is freed for reuse."
                            style={{ marginLeft: 6, display: "inline-block", padding: "2px 8px", borderRadius: 999, fontSize: 11, fontWeight: 700, background: "var(--red-bg, #fdecea)", color: "var(--red-fg, #b3261e)" }}>
                            Deleted in Tally
                          </span>
                        ) : null}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
        {written.length === 0 && <div className="tiny" style={{ marginTop: 12, opacity: .8 }}>Nothing posted to Tally yet in this period. Approved credit notes appear here the moment they're written into your books.</div>}
      </div>

      {/* ---- Full review history ------------------------------------------ */}
      <div className="section-title">All reviewed</div>
      {!docs.length && <EmptyState title="Nothing reviewed yet" sub="Approved and rejected credit notes will appear here." icon={<Ic.clock />} />}
      {docs.length > 0 && (
        <div className="card tbl-wrap">
          <table className="tbl">
            <thead><tr><th>Credit note</th><th>Business</th><th>Invoice</th><th>Amount</th><th>Status</th><th>Posted to Tally</th><th></th></tr></thead>
            <tbody>
              {docs.map((d) => {
                const biz = bizById(d.biz);
                const postedOn = ["verified_in_tally", "posted"].includes(d.status);
                return (
                  <tr key={d.id} onClick={() => onOpen(d.id)}>
                    <td className="doc-no">{d.cnNo}</td>
                    <td><span className="row gap8"><BizAvatar biz={biz} size={22} />{biz.name}</span></td>
                    <td className="muted mono" style={{ fontSize: 13 }}>{d.invoice}</td>
                    <td className="amt">{fmtINR(d.amount)}</td>
                    <td><StatusBadge status={d.status} customer /></td>
                    <td className="muted" style={{ fontSize: 13 }}>{postedOn ? fmtDate(d.confirmedAt || d.postedAt) : <span className="muted">—</span>}</td>
                    <td style={{ width: 28 }}><Ic.chevron className="row-arrow" /></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </>
  );
}

/* ---- Connect Tally — connector = automatic status, Tally = the one button ----
   First principle (PRD 2026-06-22): the connector⇄Lekha link is an automatic system
   state the user only needs to SEE; the one human decision is connecting Lekha to
   THIS company's Tally (granting permission to auto-post) and disconnecting it again.
   So: connector is a status light (auto-claimed, never clicked); "Connect Tally" is
   the single control and the posting-permission gate — nothing posts until clicked. */
function ConnGraphic({ connectorLinked, connectorPaired, tallyConnected, tallyUp, company }) {
  // Pure status visual — nothing here is clickable. The control is the button below.
  const wire = (lit) => ({
    flex: 1, position: "relative", height: 4, marginTop: 30, borderRadius: 2, overflow: "hidden",
    background: lit ? "var(--green-fg)"
      : "repeating-linear-gradient(90deg, var(--border) 0 6px, transparent 6px 13px)",
    transition: "background .3s",
  });
  const Node = ({ icon, label, sub, lit }) => (
    <div style={{ textAlign: "center", flex: "0 0 92px" }}>
      <div style={{
        width: 60, height: 60, margin: "0 auto", borderRadius: 16, display: "grid", placeItems: "center",
        background: lit ? "var(--lime)" : "var(--gray-bg, #f1f1ee)",
        border: "2px solid " + (lit ? "var(--lime-deep)" : "var(--border)"),
        color: lit ? "var(--lime-ink)" : "var(--text-3)", transition: "all .3s",
      }}>{icon}</div>
      <div style={{ fontWeight: 700, fontSize: 13, marginTop: 8 }}>{label}</div>
      <div className="tiny" style={{ minHeight: 14 }}>{sub}</div>
    </div>
  );
  return (
    <div className="card card-pad mb20" style={{ maxWidth: 760 }}>
      <style>{`@keyframes lnaflow{0%{left:-35%}100%{left:135%}}
        .lna-flow{position:absolute;top:0;height:4px;width:35%;border-radius:2px;
          background:linear-gradient(90deg,transparent,rgba(255,255,255,.95),transparent);
          animation:lnaflow 1.25s linear infinite;}`}</style>
      <div className="row" style={{ alignItems: "flex-start", justifyContent: "space-between" }}>
        <Node icon={<Ic.mail />} label="Lekha" sub="cloud" lit />
        <div style={wire(connectorLinked)}>{connectorLinked && <span className="lna-flow" />}</div>
        <Node icon={<Ic.shield />} label="Lekha App"
          sub={connectorLinked ? "linked to Lekha" : connectorPaired ? "offline" : "not running"}
          lit={connectorLinked} />
        <div style={wire(tallyConnected)}>{tallyConnected && <span className="lna-flow" />}</div>
        <Node icon={<Ic.tally />} label="Tally"
          sub={tallyConnected ? (company || "connected") : tallyUp ? "ready" : "not open"}
          lit={tallyConnected} />
      </div>
    </div>
  );
}

// Right-side panel: a simple, reassuring summary of the connector for the user
// (condensed from docs/connector-onboarding.md).
function ConnectorGuide() {
  const Item = ({ ok, children }) => (
    <li className="tiny" style={{ display: "flex", gap: 7, marginBottom: 7, listStyle: "none", lineHeight: 1.5 }}>
      <span style={{ flex: "0 0 14px", color: ok ? "var(--green-fg)" : "#b23b1e", marginTop: 1 }}>{ok ? <Ic.check /> : <Ic.x />}</span>
      <span>{children}</span>
    </li>
  );
  const Sec = ({ title, children }) => (
    <div style={{ marginTop: 16 }}>
      <div style={{ fontWeight: 800, fontSize: 11.5, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--text-3)", marginBottom: 8 }}>{title}</div>
      {children}
    </div>
  );
  // "what's inside" — name + plain-words description
  const Part = ({ name, children }) => (
    <li className="tiny" style={{ listStyle: "none", marginBottom: 8, lineHeight: 1.5 }}>
      <b>{name}</b> — {children}
    </li>
  );
  const Bullet = ({ children }) => (
    <li className="tiny" style={{ listStyle: "none", marginBottom: 6, lineHeight: 1.5, display: "flex", gap: 7 }}>
      <span style={{ flex: "0 0 5px", height: 5, width: 5, borderRadius: 5, background: "var(--text-3)", marginTop: 7 }} />
      <span>{children}</span>
    </li>
  );
  return (
    <div className="card card-pad" style={{ position: "sticky", top: 16 }}>
      <div className="row gap10" style={{ alignItems: "center" }}>
        <span className="k-ic" style={{ background: "var(--lime)", color: "var(--lime-ink)", flex: "0 0 30px" }}><Ic.shield /></span>
        <div>
          <div style={{ fontWeight: 800, fontSize: 15 }}>About the Lekha app</div>
          <div className="tiny" style={{ color: "var(--text-3)" }}>A tiny Windows app · ~5 min to set up</div>
        </div>
      </div>

      <Sec title="What it is">
        <div className="tiny" style={{ lineHeight: 1.6 }}>A small (~5 MB) Windows app that runs quietly beside TallyPrime — the secure bridge between your Tally and Lekha. Once you approve a credit note, it posts into Tally automatically, with no manual entry. Think of it as a trusted courier sitting next to Tally: it only acts on what you've approved, and you can send it home (disconnect) any time.</div>
        <div style={{ marginTop: 12, padding: "12px 14px", background: "var(--surface-2, #f6f7f5)", borderRadius: 10, border: "1px solid var(--border)" }}>
          <pre className="mono" style={{ margin: 0, fontSize: 10.5, lineHeight: 1.5, whiteSpace: "pre", overflowX: "auto", color: "var(--text-2)" }}>{
`  Your Tally PC                 Lekha AI (cloud)
┌──────────────────────┐       ┌────────────────┐
│ TallyPrime ⇄ Connector│──WSS──▶│   Lekha AI     │
│ (localhost, your data)│ outbnd │ review/approve │
└──────────────────────┘       └────────────────┘
   stays on your PC      encrypted, outbound-only`
          }</pre>
        </div>
      </Sec>

      <Sec title="What's inside it">
        <ul style={{ margin: 0, padding: 0 }}>
          <Part name="The agent">a tiny background program beside Tally; auto-starts when you sign in to Windows.</Part>
          <Part name="A secure outbound link">dials out to Lekha over an encrypted connection — never opens your PC to incoming traffic. No firewall changes, no ports to open.</Part>
          <Part name="A private identity key">a unique cryptographic ID created on first run — that's how Lekha knows it's really your device. No passwords or tokens to copy-paste.</Part>
          <Part name="A local link to Tally">talks to TallyPrime on the same PC; this connection stays entirely on your computer.</Part>
        </ul>
        <div className="tiny" style={{ marginTop: 4, color: "var(--text-3)", lineHeight: 1.5 }}>It also remembers what it has already done, so it <b>never posts the same credit note twice</b>.</div>
      </Sec>

      <Sec title="Before you start">
        <ul style={{ margin: 0, padding: 0 }}>
          <Bullet>A <b>Windows PC</b> with <b>TallyPrime</b> installed.</Bullet>
          <Bullet>Tally's <b>data gateway is on</b> (the standard setting that lets apps exchange data on the same PC — usually port <b>9000</b>).</Bullet>
          <Bullet>The <b>company you want to use is open</b> in TallyPrime.</Bullet>
          <Bullet>You're <b>signed in to Lekha</b> in your browser.</Bullet>
        </ul>
      </Sec>

      <Sec title="Good to know">
        <ul style={{ margin: 0, padding: 0 }}>
          <Bullet><b>First-time Windows warning.</b> Because the app is new, Windows may show <i>“Windows protected your PC.”</i> Click <b>More info → Run anyway</b> — this is the standard prompt for newly published apps.</Bullet>
          <Bullet><b>Everyday use.</b> Nothing to do — it runs in the background and starts with Windows. Just keep <b>TallyPrime open with your company</b> during the day so posting can happen.</Bullet>
          <Bullet><b>Pausing.</b> Click <b>Disconnect</b> any time — posting stops and approved notes simply <b>queue</b> until you reconnect. Nothing in Tally changes while paused.</Bullet>
        </ul>
      </Sec>

      <Sec title="How it keeps your data safe">
        <ul style={{ margin: 0, padding: 0 }}>
          <Item ok>Outbound-only, encrypted link (like online banking) — your PC is never exposed to the internet.</Item>
          <Item ok>A private identity key per machine — no passwords or tokens to copy-paste.</Item>
          <Item ok>Per-user install, no admin rights — safe on office PCs.</Item>
          <Item ok>Nothing posts until you approve; disconnect or uninstall any time.</Item>
        </ul>
      </Sec>

      <Sec title="It does — and doesn't">
        <ul style={{ margin: 0, padding: 0 }}>
          <Item ok>Reads your ledgers when needed &amp; posts approved credit notes.</Item>
          <Item ok>Shows a live status (connected / Tally open / posting).</Item>
          <Item>Never changes Tally until you approve.</Item>
          <Item>Never opens your PC to incoming connections.</Item>
          <Item>Never asks for a Tally password, token, or certificate.</Item>
        </ul>
      </Sec>

      <Sec title="If something's off">
        <ul style={{ margin: 0, padding: 0 }}>
          <li className="tiny" style={{ listStyle: "none", marginBottom: 6, lineHeight: 1.5 }}><b>Tally app offline</b> — open the Lekha app on your Tally PC; it reconnects in seconds.</li>
          <li className="tiny" style={{ listStyle: "none", marginBottom: 6, lineHeight: 1.5 }}><b>Tally not open</b> — open TallyPrime with your company loaded.</li>
          <li className="tiny" style={{ listStyle: "none", marginBottom: 6, lineHeight: 1.5 }}><b>Button greyed out</b> — all three lights must be green.</li>
          <li className="tiny" style={{ listStyle: "none", marginBottom: 6, lineHeight: 1.5 }}><b>Notes “queued”</b> — reconnect &amp; keep Tally open; the queue clears on its own.</li>
          <li className="tiny" style={{ listStyle: "none", marginBottom: 6, lineHeight: 1.5 }}><b>Restarted Tally</b> — notes pause &amp; queue while it's closed; reopen TallyPrime with your company and posting resumes on its own.</li>
          <li className="tiny" style={{ listStyle: "none", marginBottom: 6, lineHeight: 1.5 }}><b>“Unlink this device”</b> — fully unbinds it (for switching PCs), unlike <b>Disconnect</b> which only pauses; press <b>Connect Tally</b> on the PC you want to re-link, then queued notes post.</li>
          <li className="tiny" style={{ listStyle: "none", lineHeight: 1.5 }}><b>A brief reconnect now and then</b> — normal; the secure link refreshes itself.</li>
        </ul>
      </Sec>

      <Sec title="Uninstall">
        <div className="tiny" style={{ lineHeight: 1.6 }}>Windows <b>Settings → Apps → Lekha AI → Uninstall</b>. It removes the app and its files cleanly.</div>
      </Sec>
    </div>
  );
}

function ConnectTally({ store, who }) {
  const [status, setStatus] = React.useState(null);   // { paired, connected, tally_online, company_loaded, connector_on, ... }
  const [avail, setAvail] = React.useState([]);       // live connectors on the WSS hub
  const [busy, setBusy] = React.useState(false);
  const [confirmOpen, setConfirmOpen] = React.useState(false);   // disconnect-Tally confirm
  const [connectedPopup, setConnectedPopup] = React.useState(null); // "ready to post" popup (company)
  const [replica, setReplica] = React.useState(null);  // REQ-U1: sync progress + posting-ready

  const reload = React.useCallback(async () => {
    const [s, a] = await Promise.all([store.connectorStatus(), store.availableConnectors()]);
    setAvail(a || []);
    if (who && who.business_id) {
      api.get(`/api/tally/replica/${who.business_id}/state`).then(setReplica).catch(() => {});
    }
    // Connector = automatic status: silently claim the single detected connector so
    // the user never picks one (binding is not a decision — posting is). A >1 case
    // falls back to the picker below.
    if (s && !s.paired) {
      const free = (a || []).filter((c) => c.online && !c.mine);
      if (free.length === 1) {
        const r = await store.claimConnector(free[0].connector_id);
        if (r) { setStatus(r); return; }
      }
    }
    setStatus(s);
  }, [store, who]);

  // The connector dials in over WSS; poll so status + the live list stay fresh.
  React.useEffect(() => {
    let alive = true;
    const tick = () => { if (alive) reload(); };
    tick();
    const id = setInterval(tick, 8000);
    return () => { alive = false; clearInterval(id); };
  }, [reload]);

  const paired = !!(status && status.paired);
  const online = !!(status && status.connected);              // Lekha ↔ connector link is live
  const connectorLinked = paired && online;                   // connector status light = green
  const tallyUp = !!(status && status.tally_online);          // TallyPrime actually open
  const companyLoaded = !!(status && status.company_loaded);  // …with the bound company
  const permission = !!(status && status.connector_on);       // finance granted posting
  const expectedCompany = (status && status.expected_company) || "";
  const companies = (status && status.companies) || [];
  const company = expectedCompany || companies[0] || "your company";
  const queued = (status && status.queued) || 0;
  const claimable = avail.filter((c) => c.online && !c.mine);

  // green/posting state and whether the button can be clicked to connect
  const tallyConnected = connectorLinked && tallyUp && companyLoaded && permission;
  const canConnect = connectorLinked && tallyUp && companyLoaded && !permission;
  const blockReason = !connectorLinked
      ? (paired ? "Tally app offline — open the Lekha app on your Tally PC"
                : "Waiting for the Lekha app — open it on your Tally PC")
    : !tallyUp ? "Open TallyPrime on your Tally PC"
    : !companyLoaded ? ("Open " + (expectedCompany || "your company") + " in TallyPrime")
    : "";

  const connectTally = async () => {
    setBusy(true);
    try { await store.setConnector(true); await reload(); setConnectedPopup(company); }
    finally { setBusy(false); }
  };
  const disconnectTally = async () => {          // revoke permission; connector stays linked
    setBusy(true);
    try { await store.setConnector(false); await reload(); }
    finally { setBusy(false); }
  };
  const unlinkConnector = async () => {          // rare: switching PCs — fully unbind
    setBusy(true);
    try { await store.unclaimConnector(); await reload(); }
    finally { setBusy(false); }
  };

  const Step = ({ n, title, done, children }) => (
    <div className="card card-pad" style={{ display: "flex", gap: 16, alignItems: "flex-start" }}>
      <span style={{
        flex: "0 0 30px", width: 30, height: 30, borderRadius: "50%", display: "grid", placeItems: "center",
        background: done ? "var(--green-fg)" : "var(--lime)", color: done ? "#fff" : "var(--lime-ink)",
        border: "2px solid " + (done ? "var(--green-fg)" : "var(--lime-deep)"), fontWeight: 700, fontSize: 13,
      }}>{done ? <Ic.check /> : n}</span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontWeight: 700, fontSize: 15, marginBottom: 6 }}>{title}</div>
        {children}
      </div>
    </div>
  );

  const overlay = (title, body, actions) => (
    <div onClick={() => { setConfirmOpen(false); setConnectedPopup(null); }}
      style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.35)", display: "grid", placeItems: "center", zIndex: 60 }}>
      <div onClick={(e) => e.stopPropagation()} className="card card-pad" style={{ maxWidth: 420, width: "90%" }}>
        <div style={{ fontWeight: 800, fontSize: 16, marginBottom: 8 }}>{title}</div>
        <div className="tiny" style={{ marginBottom: 18, lineHeight: 1.6 }}>{body}</div>
        <div className="row gap10" style={{ justifyContent: "flex-end" }}>{actions}</div>
      </div>
    </div>
  );

  return (
    <>
      <div className="page-head"><div>
        <div className="ph-title">Connect your Tally</div>
        <div className="ph-sub">Install the Lekha desktop app on the PC where Tally runs and open it — it connects to Lekha on its own. Then open your company in TallyPrime and press <b>Connect Tally</b> to let Lekha post approved credit notes automatically. Disconnect any time to take control back.</div>
      </div></div>

      <div className="row gap16" style={{ alignItems: "flex-start", flexWrap: "wrap" }}>
       <div style={{ flex: "1 1 520px", minWidth: 320 }}>
      <ConnGraphic connectorLinked={connectorLinked} connectorPaired={paired}
        tallyConnected={tallyConnected} tallyUp={tallyUp}
        company={tallyConnected ? company : null} />

      {/* REQ-U1 — onboarding sync visibility: what Lekha has mirrored from this
          Tally and whether posting-critical data is ready. Interruptions are
          normal (progress is checkpointed) — say so instead of alarming. */}
      {connectorLinked && (() => {
        // REMASTER: replica state is per object_type (state.objects[]); readiness
        // is "every object type is ready", and voucher progress is on the 'voucher' row.
        const rows = (replica && replica.objects) || [];
        if (!rows.length) return null;
        const ready = rows.every((o) => o.replica_ready);
        const anyError = rows.some((o) => o.status === "ERROR");
        const vch = (rows.find((o) => o.object_type === "voucher") || {}).progress || {};
        const vDone = (vch.done || []).length, vTotal = vch.total || 0;
        const pct = ready ? 100 : (vTotal ? Math.round(100 * vDone / vTotal) : 8);
        return (
          <div className="card card-pad mb20">
            <div className="row" style={{ alignItems: "center", justifyContent: "space-between", gap: 12 }}>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontWeight: 800, fontSize: 15 }}>
                  {ready ? "Books synced — ready to post" : "Syncing your Tally books…"}
                </div>
                <div className="tiny" style={{ color: "var(--text-3)", marginTop: 3 }}>
                  {(replica.masters || 0).toLocaleString()} masters · {(replica.vouchers || 0).toLocaleString()} voucher headers mirrored
                  {!ready && vTotal > 0 && ` · ${vDone}/${vTotal} months`}
                  {anyError && " · connection hiccup — progress is saved, retrying"}
                </div>
              </div>
              <span className="tiny" style={{
                flex: "0 0 auto", fontWeight: 800, padding: "4px 10px", borderRadius: 999,
                background: ready ? "var(--green-bg)" : "var(--lime)",
                color: ready ? "var(--green-fg)" : "var(--lime-ink)",
              }}>{ready ? "READY" : (anyError ? "RETRYING" : "SYNCING")}</span>
            </div>
            {!ready && (
              <div style={{ marginTop: 12, height: 6, borderRadius: 3, background: "var(--border)", overflow: "hidden" }}>
                <div style={{ width: pct + "%", height: "100%", background: "var(--green-fg)", transition: "width .6s" }} />
              </div>
            )}
          </div>
        );
      })()}

      {/* THE one control: connect (grant posting) / disconnect (hand control back) */}
      <div className="card card-pad mb20">
        {!permission ? (
          <>
            <button className="btn btn-primary" disabled={!canConnect || busy} onClick={connectTally}
              style={{ fontSize: 15, padding: "12px 22px" }}>
              <Ic.tally />{busy ? "Connecting…" : "Connect Tally"}
            </button>
            <div className="tiny" style={{ marginTop: 8, opacity: .85 }}>
              {canConnect ? ("Lekha will start posting approved credit notes to " + company + ".") : blockReason}
            </div>
          </>
        ) : (
          <div className="row gap10" style={{ alignItems: "center", flexWrap: "wrap" }}>
            <span className={"badge " + (tallyConnected ? "green" : "gray")}>
              <span className="b-dot" />
              {tallyConnected ? ("Tally connected — posting to " + company)
                : !connectorLinked ? "Connector offline — posting resumes automatically"
                : "Tally not open — posting resumes automatically"}
            </span>
            {queued > 0 && <span className="tiny">{queued} approved credit note{queued > 1 ? "s" : ""} queued</span>}
            <button className="btn btn-ghost btn-sm" disabled={busy} onClick={() => setConfirmOpen(true)}>
              Disconnect Tally
            </button>
          </div>
        )}
      </div>

      {connectedPopup && overlay(
        "Connected ✓",
        <>Lekha AI is now connected to <b>{connectedPopup}</b> and will post approved credit notes into Tally automatically. You can disconnect any time to take control back.</>,
        <button className="btn btn-primary btn-sm" onClick={() => setConnectedPopup(null)}>Got it</button>
      )}

      {confirmOpen && overlay(
        "Disconnect Tally?",
        <>Lekha will stop posting and hand control back to you. Approved credit notes will <b>queue</b> until you reconnect — nothing inside Tally changes, and the app stays linked.</>,
        <>
          <button className="btn btn-sm" onClick={() => setConfirmOpen(false)}>Cancel</button>
          <button className="btn btn-danger btn-sm" disabled={busy}
            onClick={async () => { await disconnectTally(); setConfirmOpen(false); }}>
            {busy ? "Disconnecting…" : "Disconnect"}
          </button>
        </>
      )}

      <div className="stack gap12">
        <Step n={1} title="Download the connector" done={paired}>
          <div className="tiny mb8">A small Windows agent (~5 MB) that runs beside Tally. The same installer works for every business.</div>
          <button className="btn btn-primary btn-sm" onClick={store.downloadConnector}>
            <Ic.tally />Download the Lekha app
          </button>
          <div className="tiny" style={{ marginTop: 8, color: "var(--muted)" }}>
            New to Lekha? See <b>“What's inside the Lekha app”</b> in the <b>Docs</b> tab for what it is, how it works, and is it safe.
          </div>
        </Step>

        <Step n={2} title="Open it on your Tally PC" done={connectorLinked}>
          <div className="tiny mb8">Launch the Lekha app — it connects to Lekha automatically over a secure outbound link (no token, no certificate) and links itself to your business within a few seconds.</div>
          <div className="hint" style={{ background: connectorLinked ? "rgba(122,158,63,.1)" : "var(--amber-bg)" }}>
            {connectorLinked ? <Ic.check style={{ marginTop: 1, flex: "0 0 14px" }} /> : <Ic.shield style={{ marginTop: 1, flex: "0 0 14px" }} />}
            <div>
              <b>{connectorLinked ? "Lekha app connected." : paired ? "Tally app offline." : "Waiting for the Lekha app to connect…"}</b>
              {connectorLinked && companies.length > 0 && <>
                {" "}Tally companies on this connector:
                <ul style={{ margin: "6px 0 0", paddingLeft: 18 }}>
                  {companies.map((c, i) => <li key={i} className="mono" style={{ fontSize: 12.5 }}>{c}</li>)}
                </ul>
              </>}
              {paired && !online && " Open the Lekha app on your Tally PC and it'll reconnect."}
              {paired && <div style={{ marginTop: 6 }}>
                <button className="btn btn-ghost btn-sm" disabled={busy} onClick={unlinkConnector}>Unlink this device</button>
                <span className="tiny" style={{ marginLeft: 6, opacity: .6 }}>(only when switching PCs)</span>
              </div>}
            </div>
          </div>
        </Step>

        {!paired && claimable.length > 1 && (
          <Step n={3} title="Pick your connector">
            <div className="tiny mb8">More than one device is online — pick the one running on your Tally machine.</div>
            <div className="stack gap8">
              {claimable.map((c) => (
                <div key={c.connector_id} className="row" style={{ justifyContent: "space-between", alignItems: "center",
                  border: "1px solid var(--border)", borderRadius: 10, padding: "10px 12px" }}>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontWeight: 600, fontSize: 13 }}>
                      {c.companies && c.companies.length ? c.companies.join(", ") : "Tally (desktop app)"}
                      {c.claimed_by && <span className="tiny" style={{ marginLeft: 6, opacity: .7 }}>(claimed by {c.claimed_by})</span>}
                    </div>
                    <div className="tiny mono" style={{ opacity: .6 }}>{String(c.connector_id).slice(0, 8)}… · {c.tally_online ? "Tally online" : "Tally not detected"}</div>
                  </div>
                  <button className="btn btn-primary btn-sm" disabled={busy}
                    onClick={async () => { setBusy(true); try { const r = await store.claimConnector(c.connector_id); if (r) setStatus(r); await reload(); } finally { setBusy(false); } }}>
                    Use this
                  </button>
                </div>
              ))}
            </div>
          </Step>
        )}
      </div>
       </div>

       <div style={{ flex: "0 1 350px", minWidth: 300 }}>
         <ConnectorGuide />
       </div>
      </div>
    </>
  );
}

/* ---- accounting impact (double entry + downstream) — review & posted ---- */
function AccountingImpact({ acct }) {
  if (!acct) return null;
  if (acct.preview && (!acct.legs || !acct.legs.length)) {
    return (
      <div className="card card-pad" style={{ borderColor: "var(--amber-fg)", background: "var(--amber-bg)" }}>
        <div className="section-title" style={{ margin: 0 }}>Accounting impact</div>
        <div className="row gap8" style={{ marginTop: 8 }}>
          <Ic.shield style={{ flex: "0 0 15px", color: "var(--amber-fg)", marginTop: 1 }} />
          <div className="tiny"><b>Not posted yet — {acct.outcome}.</b> {acct.detail || ""} The exact Tally ledgers are set once Tally is connected; the impact below is from the document.</div>
        </div>
        {acct.downstream && acct.downstream.length > 0 && (
          <>
            <div className="section-title" style={{ marginTop: 14 }}>Downstream impact</div>
            <div className="stack gap8">
              {acct.downstream.map((d, i) => (
                <div key={i} className="row gap8" style={{ alignItems: "flex-start" }}>
                  <span className="bizdot" style={{ background: d.dir === "down" ? "var(--red-fg)" : "var(--text-3)", marginTop: 6, flex: "0 0 8px" }} />
                  <div className="tiny"><b>{d.label}</b> — {d.detail}{d.amount ? " (" + fmtINR(d.amount) + ")" : ""}</div>
                </div>
              ))}
            </div>
          </>
        )}
      </div>
    );
  }
  const legs = acct.legs || [];
  const sum = legs.reduce((s, l) => s + (l.side === "Dr" ? l.amount : -l.amount), 0);
  return (
    <div className="card card-pad">
      <div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
        <div className="section-title" style={{ margin: 0 }}>Accounting impact — double entry</div>
        <PostBadge acct={acct} />
      </div>
      {acct.skeleton && (
        <div className="tiny" style={{ marginTop: 8, opacity: .8 }}>
          <Ic.shield style={{ flex: "0 0 13px", marginTop: 1, marginRight: 4, verticalAlign: "text-top" }} />
          Indicative ledgers — the exact Tally ledger names are assigned once Tally is connected and the binding is confirmed.
        </div>
      )}
      <table className="li-tbl" style={{ marginTop: 10 }}>
        <thead><tr><th>Ledger</th><th className="num">Dr</th><th className="num">Cr</th></tr></thead>
        <tbody>
          {legs.map((l, i) => (
            <tr key={i}>
              <td>{l.ledger}</td>
              <td className="num">{l.side === "Dr" ? fmtINR(l.amount) : ""}</td>
              <td className="num">{l.side === "Cr" ? fmtINR(l.amount) : ""}</td>
            </tr>
          ))}
        </tbody>
        <tfoot><tr><td style={{ fontWeight: 700 }}>Balance</td><td className="num" colSpan="2" style={{ textAlign: "right", fontWeight: 700 }}>{Math.abs(sum) < 0.01 ? "✓ balanced" : fmtINR(sum)}</td></tr></tfoot>
      </table>
      {acct.downstream && acct.downstream.length > 0 && (
        <>
          <div className="section-title" style={{ marginTop: 14 }}>Downstream impact</div>
          <div className="stack gap8">
            {acct.downstream.map((d, i) => (
              <div key={i} className="row gap8" style={{ alignItems: "flex-start" }}>
                <span className="bizdot" style={{ background: d.dir === "down" ? "var(--red-fg)" : "var(--text-3)", marginTop: 6, flex: "0 0 8px" }} />
                <div className="tiny"><b>{d.label}</b> — {d.detail}{d.amount ? " (" + fmtINR(d.amount) + ")" : ""}</div>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

/* Audit log for FINANCE: only the milestones they care about — debit note uploaded,
   credit note generated, ready for review, approved, posting, posted, verified, rejected.
   The Lekha-side gate churn (draft edits, admin approvals, exception retries, in-progress
   working states) is hidden. Short but complete. Input is oldest-first (as the API sends). */
const _FIN_MILESTONE = {
  pending_approval: "Ready for your review",
  approved: "Approved — posting to Tally",
  posting: "Posting to Tally",
  posted: "Posted to Tally",
  verified_in_tally: "Verified in Tally",
  cancelled: "Rejected",
};
function financeAuditEvents(events) {
  if (!events) return events;
  const out = [];
  let seenGen = false;
  const seenTo = {};
  for (const e of events) {
    if (e.kind === "debit_note" && e.action === "dn_received") { out.push({ ...e, label: "Debit note uploaded" }); continue; }
    if (e.kind === "credit_note" && e.action === "cn_drafted") {
      if (!seenGen) { seenGen = true; out.push({ ...e, label: "Credit note generated" }); }
      continue;
    }
    if (e.to && _FIN_MILESTONE[e.to] && !seenTo[e.to]) {
      seenTo[e.to] = true;
      out.push({ ...e, label: _FIN_MILESTONE[e.to] });
    }
    // else: Lekha-gate edits/approvals, draft churn, in_exception, validation → hidden
  }
  return out;
}

/* ---- focused review screen ---------------------------------------------- */
function ReviewScreen({ doc, biz, store, onBack }) {
  const { financeApprove, financeReject } = store;
  const [rejecting, setRejecting] = React.useState(false);
  const [reason, setReason] = React.useState("");
  const [working, setWorking] = React.useState(false);
  const [posting, setPosting] = React.useState(false);
  const [acct, setAcct] = React.useState(null);
  const [detail, setDetail] = React.useState(null);
  const [showAudit, setShowAudit] = React.useState(false);
  const reloadDetail = React.useCallback(() => {
    api.get(`/api/credit-notes/${doc.id}`).then((d) => { setAcct(d.accounting); setDetail(d); }).catch(() => {});
  }, [doc.id]);
  React.useEffect(() => {
    let live = true;
    api.get(`/api/credit-notes/${doc.id}`).then((d) => { if (live) { setAcct(d.accounting); setDetail(d); } }).catch(() => {});
    return () => { live = false; };
  }, [doc.id, doc.status]);
  const acting = ["approved", "irn_pending", "irn_generated", "posting", "posted"].includes(doc.status);
  const finished = doc.status === "verified_in_tally" || doc.status === "cancelled";
  const cnTotal = (acct && acct.total != null) ? acct.total : doc.amount;
  const amountsMatch = Math.abs((cnTotal || 0) - (doc.amount || 0)) < 0.01;

  // success celebration — only when the note JUST verified (not when opening a done one)
  const [celebrate, setCelebrate] = React.useState(false);
  const prevStatus = React.useRef(doc.status);
  React.useEffect(() => {
    if (doc.status === "verified_in_tally" && prevStatus.current !== "verified_in_tally") {
      setCelebrate(true);
      const t = setTimeout(() => setCelebrate(false), 3400);
      prevStatus.current = doc.status;
      return () => clearTimeout(t);
    }
    prevStatus.current = doc.status;
  }, [doc.status]);

  // keyboard-first review: A approve · R reject · Esc / ← back (ignored while typing)
  React.useEffect(() => {
    const onKey = (e) => {
      const tag = ((e.target && e.target.tagName) || "").toLowerCase();
      if (tag === "input" || tag === "textarea" || tag === "select" || e.metaKey || e.ctrlKey || e.altKey) return;
      if (e.key === "Escape") { if (rejecting) setRejecting(false); else onBack(); return; }
      if (e.key === "ArrowLeft") { onBack(); return; }
      if (finished || acting || rejecting || posting) return;
      const k = e.key.toLowerCase();
      if (k === "a") { e.preventDefault(); setPosting(true); Promise.resolve(store.approveAndPost(doc.id)).finally(() => setPosting(false)); }
      else if (k === "r") { e.preventDefault(); setRejecting(true); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [finished, acting, rejecting, posting, doc.id, onBack]);

  return (
    <div className="container review-wide">
      <div className="row mb16" style={{ justifyContent: "space-between", alignItems: "center" }}>
        <button className="btn btn-ghost btn-sm" onClick={onBack}>
          <Ic.chevron style={{ transform: "rotate(180deg)" }} />Back to queue
        </button>
        <div className="row gap12" style={{ alignItems: "center" }}>
          <span className="tiny" style={{ fontWeight: 600, display: "inline-flex", alignItems: "center" }}>
            <BizAvatar biz={biz} size={22} /><span style={{ marginLeft: 8 }}>{biz.name}</span>
          </span>
          {detail && detail.history && detail.history.length ? (
            <button className="btn btn-ghost btn-sm" onClick={() => setShowAudit(true)}
              title="Key milestones for this credit note">
              <Ic.inbox />View detailed Audit log
            </button>
          ) : null}
        </div>
      </div>

      {/* timeline — full width, continuous bar */}
      <div className="card card-pad mb20">
        <Pipeline status={doc.status} doc={doc} biz={biz} />
      </div>

      {/* symmetric 2x2 grid: debit note | credit note  /  downstream | reconciliation */}
      <div className="review-grid mb20">
        <div className="card card-pad review-cell">
          <div className="row mb16 review-head" style={{ justifyContent: "space-between", alignItems: "center" }}>
            <div className="section-title" style={{ margin: 0 }}>Debit note — source</div>
            {doc.pdfUrl && (
              <a className="btn btn-ghost btn-sm" href={api.mediaUrl(doc.pdfUrl)} target="_blank" rel="noopener noreferrer"
                title="Open the source debit note in a new tab">
                <Ic.arrow />Open PDF
              </a>
            )}
          </div>
          {doc.pdfUrl ? (
            <div style={{ flex: 1, minHeight: 0 }}>
              <iframe src={api.mediaUrl(doc.pdfUrl)} title="Source debit note"
                style={{ width: "100%", height: "100%", border: "1px solid var(--line)", borderRadius: 10, background: "#fff", display: "block" }} />
            </div>
          ) : (
            <div className="review-scroll"><LineItems doc={doc} /></div>
          )}
        </div>

        <div className="card card-pad review-cell">
          <div className="row mb16 review-head" style={{ justifyContent: "space-between", alignItems: "center" }}>
            <div className="section-title" style={{ margin: 0 }}>Credit note — Tally voucher</div>
            {doc.confidence != null && <ConfidenceRing value={doc.confidence} />}
          </div>
          <div className="review-scroll">
            <CNPreview doc={doc} acct={acct} editable docId={doc.id}
              onSaved={(d) => { setAcct(d.accounting); reloadDetail(); }} />
          </div>
        </div>

        <div className="card card-pad">
          <div className="row mb16" style={{ justifyContent: "space-between", alignItems: "center" }}>
            <div className="section-title" style={{ margin: 0 }}>Source → Credit note reconciliation</div>
            <span className="match-pill"><Ic.check />Reconciled</span>
          </div>
          <div className="compare">
            <div className="panel" style={{ padding: 16 }}>
              <div className="tiny mb8" style={{ fontWeight: 700, textTransform: "uppercase", letterSpacing: ".05em" }}>Debit note (source)</div>
              <dl className="kv">
                <dt>Number</dt><dd className="mono" style={{ fontSize: 12.5 }}>{doc.dnNo}</dd>
                <dt>Invoice</dt><dd className="mono" style={{ fontSize: 12.5 }}>{doc.invoice}</dd>
                <dt>Amount</dt><dd>{fmtINR(doc.amount)}</dd>
              </dl>
            </div>
            <div className="arrow-mid"><Ic.arrow width="22" height="22" /></div>
            <div className="panel" style={{ padding: 16, borderColor: "var(--lime-deep)" }}>
              <div className="tiny mb8" style={{ fontWeight: 700, textTransform: "uppercase", letterSpacing: ".05em", color: "var(--lime-ink)" }}>Credit note (generated)</div>
              <dl className="kv">
                <dt>Number</dt><dd className="mono" style={{ fontSize: 12.5 }}>{doc.cnNo}</dd>
                <dt>Invoice</dt><dd className="mono" style={{ fontSize: 12.5 }}>{doc.invoice}</dd>
                <dt>Amount</dt><dd>{fmtINR(doc.amount)}</dd>
              </dl>
            </div>
          </div>
          {amountsMatch && (
            <div className="amt-match"><Ic.check />Amounts match — the debit note and credit note reconcile to the rupee</div>
          )}
        </div>

        <div className="card card-pad">
          <div className="section-title">Downstream impact</div>
          {acct && acct.downstream && acct.downstream.length > 0 ? (
            <div className="stack gap8" style={{ marginTop: 10 }}>
              {acct.downstream.map((d, i) => (
                <div key={i} className="row gap8" style={{ alignItems: "flex-start" }}>
                  <span className="bizdot" style={{ background: d.dir === "down" ? "var(--red-fg)" : "var(--text-3)", marginTop: 6, flex: "0 0 8px" }} />
                  <div className="tiny"><b>{d.label}</b> — {d.detail}{d.amount ? " (" + fmtINR(d.amount) + ")" : ""}</div>
                </div>
              ))}
            </div>
          ) : (
            <div className="tiny" style={{ color: "var(--text-3)", marginTop: 10 }}>The accounting impact appears once the credit note is composed.</div>
          )}
        </div>
      </div>

      {/* the decision / final status — sticky, centered below the grid, the primary action */}
      <div className="review-decision" style={{ maxWidth: 760, margin: "0 auto" }}>
        {finished ? (
          <div className="card card-pad" style={{ textAlign: "center" }}>
            <div className="kpi" style={{ border: 0, boxShadow: "none", padding: 0 }}>
              <span className="k-ic" style={{ margin: "0 auto 12px", width: 44, height: 44, background: doc.status === "verified_in_tally" ? "var(--green-bg)" : "var(--red-bg)", color: doc.status === "verified_in_tally" ? "var(--green-fg)" : "var(--red-fg)" }}>
                {doc.status === "verified_in_tally" ? <Ic.check /> : <Ic.x />}
              </span>
              <div style={{ fontWeight: 700, fontSize: 16 }}>{doc.status === "verified_in_tally" ? "Posted to Tally" : "Rejected"}</div>
              <div className="tiny mt8">{doc.status === "verified_in_tally" ? "Entry verified — pipeline complete." : (doc.rejectReason || "Sent back to Nakad.")}</div>
            </div>
            <button className="btn btn-sm mt16" onClick={onBack}>Back to queue</button>
          </div>
        ) : rejecting ? (
          <div className="card card-pad">
            <div className="section-title">Reason for rejection</div>
            <textarea className="input" style={{ width: "100%", minHeight: 80, resize: "vertical" }} placeholder="e.g. Post the sales return to 'Sales Account', not promotions." value={reason} onChange={(e) => setReason(e.target.value)} disabled={working} />
            <div className="tiny muted mt8">Your reason goes straight to Lekha AI — it reworks the credit note and either brings you an updated preview to approve, or (after 2 tries) hands it to the Lekha team.</div>
            <div className="row gap10 mt16">
              <button className="btn btn-sm grow" onClick={() => setRejecting(false)} disabled={working}>Cancel</button>
              <button className="btn btn-danger btn-sm grow" disabled={working || !reason.trim()}
                onClick={async () => {
                  setWorking(true);
                  await financeReject(doc.id, reason);
                  setWorking(false);
                  setRejecting(false);
                  onBack();
                }}>
                {working ? "Lekha AI is reworking…" : <><Ic.x />Confirm reject</>}
              </button>
            </div>
          </div>
        ) : (
          <div className="card card-pad">
            <div className="section-title">Your decision</div>
            {acting ? (
              <div className="stack gap10">
                <div className="row gap10" style={{ color: "var(--teal-fg)", fontWeight: 600, fontSize: 14 }}>
                  <span className="badge teal" style={{ padding: 6 }}><span className="b-dot" /></span>
                  {doc.status === "posting" ? "Posting to Tally…" : "Approved — queued for Tally"}
                </div>
                <div className="tiny">This posts to {biz.name}'s Tally automatically the moment the Lekha app is online (app running + Tally open) — nothing more to click. It stays queued until a voucher id comes back.</div>
              </div>
            ) : (
              <>
                <div className="tiny mb16">One click approves <b>and</b> posts to {biz.name}'s Tally — if the Lekha app is online it posts right away, otherwise it queues and posts automatically when the app connects. If not actioned, it auto-approves in 24 hours.</div>
                <div className="row gap10">
                  <button className="btn btn-danger grow" disabled={posting} onClick={() => setRejecting(true)}><Ic.x />Reject</button>
                  <button className="btn btn-primary btn-lg grow" disabled={posting}
                    onClick={async () => { setPosting(true); try { await store.approveAndPost(doc.id); } finally { setPosting(false); } }}>
                    {posting ? "Approving & posting…" : <><Ic.check />Approve &amp; post</>}
                  </button>
                </div>
                <div className="kbd-hint">
                  <span><kbd>A</kbd>approve</span><span><kbd>R</kbd>reject</span><span><kbd>←</kbd>back</span>
                </div>
              </>
            )}
          </div>
        )}
      </div>

      {celebrate && <Confetti />}

      {showAudit && detail && detail.history && (
        <Drawer center title="Audit log"
          sub={`${doc.cnNo || "Credit note"} · ${biz.name} — key milestones`}
          onClose={() => setShowAudit(false)}
          footer={<><span className="grow" /><button className="btn btn-primary btn-sm" onClick={() => setShowAudit(false)}>Done</button></>}>
          <EventHistory events={financeAuditEvents(detail.history)} title="" />
        </Drawer>
      )}
    </div>
  );
}

function EmptyState({ title, sub, icon }) {
  return (
    <div className="empty">
      <div className="e-ic" style={{ color: "var(--text-3)" }}>{icon}</div>
      <div className="e-t">{title}</div>
      <div className="e-s">{sub}</div>
    </div>
  );
}

/* ---- Guide / Docs tab — self-serve, non-technical onboarding ------------- */
const GUIDE_ARTICLES = [
  {
    title: "What Lekha does for you",
    summary: "Turn the debit notes your buyers send into credit notes in your Tally — automatically.",
    paras: [
      "When a marketplace or buyer (Swiggy Instamart, Zepto, Blinkit, BigBasket, and others) raises a debit note against you, you normally have to read it, work out the GST, and type a matching credit note into Tally by hand.",
      "Lekha does that for you. It reads the debit note, prepares the exact credit note with the right ledgers and taxes, and — once you approve it — records it straight into your Tally. No re-typing, no missed entries.",
      "Your job is simply to review and approve. Everything else is handled.",
    ],
  },
  {
    title: "Quick start — connect your Tally (one time)",
    summary: "Three steps, about five minutes. You only do this once per computer.",
    steps: [
      "Install the Lekha desktop app on the PC where Tally runs, and open it — it connects to Lekha on its own.",
      "Install and open it on that PC. It links to Lekha on its own — nothing to type, no codes, no certificates.",
      "Open your company in TallyPrime and keep it open — Lekha detects it automatically. Nothing else to click.",
    ],
  },
  {
    title: "The journey of a credit note",
    summary: "From the buyer's debit note to a confirmed entry in your books.",
    steps: [
      "Arrives — Lekha picks up the debit note and prepares the credit note.",
      "Review — it appears in your To review list for you to check.",
      "Approve — one click; or reject with a note if something's off.",
      "Posts to Tally — Lekha writes the voucher into your books automatically.",
      "Confirmed — it shows up under Reviewed with its Tally voucher reference.",
    ],
  },
  {
    title: "Reviewing and approving",
    summary: "What you'll see, and how to approve or reject.",
    paras: [
      "Open any item in To review. You'll see the amount, the customer, the line items, and the exact accounting impact — which ledgers are debited and credited, and the effect on the customer's balance.",
      "If it looks right, click Approve & post — Lekha records it in Tally. If something's wrong, click Reject and add a short reason; it goes back without touching your books.",
    ],
  },
  {
    title: "What happens after you approve",
    summary: "It posts to Tally on its own — and waits safely if anything's offline.",
    paras: [
      "The moment you approve, Lekha writes the credit note into your Tally and then reads it back to confirm it really landed.",
      "If your Tally or the Lekha app happens to be closed, the credit note simply waits in a queue — nothing is lost — and posts automatically the next time they're open.",
      "You can see every posted entry, with its Tally voucher reference, under Reviewed → Written to Tally.",
    ],
  },
  {
    title: "If you do nothing — auto-approve",
    summary: "Nothing gets stuck. Items auto-approve after 24 hours.",
    paras: [
      "So a credit note never sits forgotten, it auto-approves 24 hours after it arrives. You can always review and act sooner.",
      "If you'd rather review everything manually, your Lekha contact can adjust this for your business.",
    ],
  },
  {
    title: "Keeping the connection healthy",
    summary: "What the three tiles mean, and how to fix an amber one.",
    paras: [
      "The Lekha app shows its status at the bottom: whether it is connected to Tally and your company is open.",
      "All green = ready to post. If Tally shows offline, open TallyPrime with your company loaded. If the App shows offline, open the Lekha app on your Tally PC.",
      "To reconnect, open the Lekha app and TallyPrime with your company loaded — it reconnects automatically; your credit notes queue until then and post on their own.",
    ],
  },
  {
    title: "Seeing everything written to Tally",
    summary: "A running summary of every voucher Lekha has posted for you.",
    paras: [
      "The Reviewed tab opens with a Written to Tally summary: how many vouchers were posted, their total value, and a list with each credit note's Tally voucher reference and the date it was posted.",
      "It's your at-a-glance record of what Lekha has done in your books — handy at month-end or for a quick reconciliation against Tally.",
    ],
  },
  {
    title: "Emails and notifications",
    summary: "Choose what lands in your inbox.",
    paras: [
      "Under Settings you control which emails you receive — a new credit note to review, a confirmation when one posts, or a rejection notice.",
      "Review emails also let you approve or reject with a single click, without opening the app.",
    ],
  },
  {
    title: "Common questions",
    summary: "Quick answers to the things people ask most.",
    qa: [
      ["Tally shows “offline” — what do I do?", "Open TallyPrime with your company loaded, and make sure the Lekha app is running on that PC — it reconnects automatically."],
      ["A credit note didn't post — is it lost?", "No. The most common reason is that the customer isn't in your Tally yet, so Lekha flags it instead of guessing. Add that customer in Tally and it will retry — nothing is ever silently dropped."],
      ["Do I need to keep my computer on?", "Only when you want entries to post. If the Lekha app and Tally are closed, credit notes wait safely in a queue and post the next time you're online."],
      ["Can I undo a voucher Lekha posted?", "Yes — it's an ordinary voucher in your Tally now, so you manage it there like any other entry."],
      ["Will Lekha change anything I didn't approve?", "Never. Lekha only posts what you (or the 24-hour auto-approve) approve, and it always reads the entry back to confirm it's correct."],
    ],
  },
];

function GuideArticle({ a, open, onToggle }) {
  return (
    <div className="card" style={{ overflow: "hidden" }}>
      <button onClick={onToggle} style={{
        width: "100%", textAlign: "left", background: "transparent", border: "none", cursor: "pointer",
        padding: "16px 20px", display: "flex", alignItems: "center", gap: 14,
      }}>
        <span className="avatar" style={{ width: 30, height: 30, flex: "0 0 30px", background: open ? "var(--lime)" : "var(--gray-bg,#f1f1ee)", color: open ? "var(--lime-ink)" : "var(--text-3)" }}>
          {open ? "−" : "+"}
        </span>
        <span style={{ flex: 1, minWidth: 0 }}>
          <span style={{ display: "block", fontWeight: 700, fontSize: 15 }}>{a.title}</span>
          <span className="tiny" style={{ display: "block" }}>{a.summary}</span>
        </span>
      </button>
      {open && (
        <div style={{ padding: "0 20px 20px 64px" }}>
          {(a.paras || []).map((p, i) => (
            <p key={i} className="tiny" style={{ lineHeight: 1.7, margin: "0 0 10px", fontSize: 13.5 }}>{p}</p>
          ))}
          {a.steps && (
            <ol style={{ margin: "4px 0 0", paddingLeft: 18 }}>
              {a.steps.map((s, i) => <li key={i} className="tiny" style={{ lineHeight: 1.7, fontSize: 13.5, marginBottom: 6 }}>{s}</li>)}
            </ol>
          )}
          {a.qa && (
            <div className="stack gap12" style={{ marginTop: 4 }}>
              {a.qa.map(([q, ans], i) => (
                <div key={i}>
                  <div style={{ fontWeight: 700, fontSize: 13.5 }}>{q}</div>
                  <div className="tiny" style={{ lineHeight: 1.7, fontSize: 13.5 }}>{ans}</div>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function DocsTab({ who }) {
  const [open, setOpen] = React.useState(0);   // first article open by default
  return (
    <>
      <div className="page-head"><div>
        <div className="ph-title">Docs</div>
        <div className="ph-sub">Everything you need to set up and run Lekha yourself — in plain language. Start at the top; each section opens with a tap.</div>
      </div></div>

      <div className="card card-pad mb20" style={{ background: "var(--lime)", border: "1.5px solid var(--lime-deep)" }}>
        <div style={{ fontWeight: 800, fontSize: 15, color: "var(--lime-ink)", marginBottom: 6 }}>New here? Three steps to go live</div>
        <ol style={{ margin: 0, paddingLeft: 18, color: "var(--lime-ink)" }}>
          <li className="tiny" style={{ fontSize: 13.5, lineHeight: 1.8 }}><b>Connect Tally</b> — install the Lekha desktop app on your Tally PC and open your company in TallyPrime; Lekha connects automatically.</li>
          <li className="tiny" style={{ fontSize: 13.5, lineHeight: 1.8 }}><b>Review</b> — open a credit note in <i>To review</i> and check it.</li>
          <li className="tiny" style={{ fontSize: 13.5, lineHeight: 1.8 }}><b>Approve</b> — it posts into Tally automatically and appears under <i>Reviewed</i>.</li>
        </ol>
      </div>

      <div className="stack gap12" style={{ maxWidth: 820 }}>
        {GUIDE_ARTICLES.map((a, i) => (
          <GuideArticle key={i} a={a} open={open === i} onToggle={() => setOpen(open === i ? -1 : i)} />
        ))}
      </div>
    </>
  );
}

/* ---- Team — the owner directly provisions teammates (no invites) --------
   Finance owner adds brand_ops / finance accounts with a password they set,
   and resets a teammate's password directly. Mail-free by design. --------- */
const _ROLE_LABEL = { finance: "Finance", brand_ops: "Brand Ops" };

function TeamTab({ store, who }) {
  const [team, setTeam] = React.useState(null);
  const [form, setForm] = React.useState({ name: "", email: "", role: "brand_ops", password: "" });
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState("");
  const [resetFor, setResetFor] = React.useState(null);   // user id
  const [resetPw, setResetPw] = React.useState("");

  const load = () => api.get("/api/team/users").then((r) => setTeam(r.items)).catch(() => setTeam([]));
  React.useEffect(() => { load(); }, []);
  const set = (k) => (e) => setForm({ ...form, [k]: e.target.value });

  const add = async (e) => {
    if (e) e.preventDefault();
    setErr(""); setBusy(true);
    try {
      await api.post("/api/team/users", {
        name: form.name.trim(), email: form.email.trim(),
        role: form.role, password: form.password,
      });
      store.toast("Teammate added");
      setForm({ name: "", email: "", role: "brand_ops", password: "" });
      load();
    } catch (ex) {
      setErr((ex && ex.message) || "Could not add teammate.");
    } finally { setBusy(false); }
  };

  const doReset = async (uid) => {
    setBusy(true); setErr("");
    try {
      await api.post(`/api/team/users/${uid}/reset-password`, { password: resetPw });
      store.toast("Password reset");
      setResetFor(null); setResetPw("");
    } catch (ex) {
      setErr((ex && ex.message) || "Could not reset password.");
    } finally { setBusy(false); }
  };

  return (
    <div className="row gap16" style={{ alignItems: "flex-start", flexWrap: "wrap" }}>
      {/* team list */}
      <div className="card card-pad" style={{ flex: "1 1 380px", minWidth: 300 }}>
        <div className="section-title">Your team</div>
        <div className="tiny" style={{ marginBottom: 10 }}>People who can access {who.name ? "your" : "this"} organisation.</div>
        {team === null ? <div className="tiny">Loading…</div> : (
          <table className="tbl">
            <thead><tr><th>Name</th><th>Email</th><th>Role</th><th></th></tr></thead>
            <tbody>
              {team.map((u) => (
                <tr key={u.id}>
                  <td style={{ fontWeight: 600 }}>{u.name}{u.email === who.email && <span className="tiny"> · you</span>}</td>
                  <td className="mono tiny">{u.email}</td>
                  <td><span className="pill">{_ROLE_LABEL[u.role] || u.role}</span></td>
                  <td style={{ textAlign: "right" }}>
                    {u.email !== who.email && (
                      resetFor === u.id ? (
                        <span className="row gap8" style={{ justifyContent: "flex-end" }}>
                          <input className="input" type="password" placeholder="new password" value={resetPw}
                            onChange={(e) => setResetPw(e.target.value)} style={{ width: 150 }} />
                          <button className="btn btn-sm btn-primary" disabled={busy} onClick={() => doReset(u.id)}>Set</button>
                          <button className="btn btn-sm btn-ghost" onClick={() => { setResetFor(null); setResetPw(""); }}>×</button>
                        </span>
                      ) : (
                        <button className="btn btn-sm btn-ghost" onClick={() => { setResetFor(u.id); setErr(""); }}>Reset password</button>
                      )
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </div>

      {/* add teammate */}
      <div className="card card-pad" style={{ flex: "1 1 320px", minWidth: 280 }}>
        <div className="section-title">Add a teammate</div>
        <div className="tiny" style={{ marginBottom: 6 }}>You set their password and hand it over — no email is sent. They change it after signing in.</div>
        <form onSubmit={add}>
          <div className="field-label" style={{ marginTop: 10 }}>Name</div>
          <input className="input" style={{ width: "100%" }} value={form.name} onChange={set("name")} placeholder="Teammate name" />
          <div className="field-label" style={{ marginTop: 10 }}>Email</div>
          <input className="input" style={{ width: "100%" }} type="email" value={form.email} onChange={set("email")} placeholder="teammate@company.in" />
          <div className="field-label" style={{ marginTop: 10 }}>Role</div>
          <select className="input" style={{ width: "100%" }} value={form.role} onChange={set("role")}>
            <option value="brand_ops">Brand Ops — fetch &amp; generate credit notes</option>
            <option value="finance">Finance — review, approve, connect Tally</option>
          </select>
          <div className="field-label" style={{ marginTop: 10 }}>Temporary password</div>
          <input className="input" style={{ width: "100%" }} type="password" value={form.password} onChange={set("password")} placeholder="At least 10 characters" />
          {err && <div className="hint" style={{ marginTop: 12 }}><Ic.x style={{ marginTop: 1, flex: "0 0 14px" }} /><div>{err}</div></div>}
          <button className="btn btn-primary" type="submit" disabled={busy} style={{ width: "100%", justifyContent: "center", marginTop: 14 }}>
            {busy ? "Adding…" : "Add teammate"}
          </button>
        </form>
      </div>
    </div>
  );
}

Object.assign(window, { FinanceApp, EmptyState });
