// Bylt Capital CRM — the client portal, from the CRM's side.
//   PortalTab            a deal file's "Portal" tab: the client's account, the
//                        documents asked for (approve, ask for a re-upload,
//                        waive), messages with the client, invites
//   PortalSettingsCard   Settings: portal on/off, states not served, bank verification
//   PortalAccountsCard   Settings (Admins): every account; turn off, clear sign-in methods
const CHECK_STATUS = {
  needed: ["pending", "Needed"], received: ["review", "To review"], approved: ["approved", "Approved"],
  reupload: ["declined", "Re-upload asked"], waived: ["", "Not needed"],
};

function usePortal(dealId) {
  const [data, setData] = useState(null);
  const [error, setError] = useState("");
  const showing = useRef(dealId); showing.current = dealId;
  const load = useCallback(async () => {
    try { const d = await API.dealPortal(dealId); if (showing.current === dealId) { setData(d); setError(""); } }
    catch (err) { if (showing.current === dealId && err.status !== 401) setError(err.message); }
  }, [dealId]);
  useEffect(() => { setData(null); load(); }, [dealId]);
  return { data, setData, error, load };
}

function PortalTab({ deal, portal, canWrite, isAdmin, confirm, toast, onChanged }) {
  const { data, setData, error, load } = portal;
  const [busy, setBusy] = useState("");
  const [askFor, setAskFor] = useState(null);     // { key, note } while typing a re-upload reason
  const [products, setProducts] = useState(null);  // while choosing products
  const [adding, setAdding] = useState("");
  const [reply, setReply] = useState("");
  const [invite, setInvite] = useState(null);
  const thread = useRef(null);
  useEffect(() => { if (thread.current) thread.current.scrollTop = thread.current.scrollHeight; }, [data && data.messages && data.messages.length]);

  if (error) return <div className="banner" role="alert">{error} <button className="link-btn" onClick={load}>Try again</button></div>;
  if (!data) return <div className="empty"><p>Loading…</p></div>;
  const run = async (key, fn, ok) => {
    setBusy(key);
    try { const r = await fn(); if (r && r.checklist !== undefined) setData(r); else await load(); if (ok) toast(ok); if (onChanged) onChanged(); }
    catch (err) { toast(err.message, true); }
    setBusy("");
  };
  const setStatus = (key, status, note) => run(key + status, () => API.patchChecklist(deal.id, key, { status, note }), status === "reupload" ? "Re-upload asked for; the client is told" : status === "approved" ? "Approved" : "Saved");
  const productName = (k) => (PRODUCT_OPTIONS.find(p => p.value === k) || { label: k }).label;
  const list = data.checklist || [];
  const groups = [...new Set(list.map(i => i.group))];
  const notOnList = Object.keys(data.catalog.docs).filter(k => !list.some(i => i.key === k));
  const toReview = list.filter(i => i.status === "received").length;

  // Admin: let this account see the client record's history (other deals and funded advances).
  const linkClient = (a) => confirm({
    title: "Link to the client record?", body: `${a.email} will see every deal and funded advance on ${data.client.biz} that no other portal account owns. Only do this if you know it's the same business.`, confirmLabel: "Link", plain: true,
    onConfirm: () => run("link" + a.id, () => API.patchPortalAccount(a.id, { clientId: data.client.id }), "Linked to the client record"),
  });
  const sendInvite = () => run("invite", async () => { const r = await API.portalInvite(deal.id); setInvite(r); }, null);
  const copy = async (text) => { try { await navigator.clipboard.writeText(text); toast("Copied"); } catch (e) { toast("Select the link and copy it.", true); } };

  return (
    <>
      <div className="section-bar"><span className="label">Client portal</span>{data.bank ? <span className="muted" style={{ fontSize: 12.5 }}>Bank: {data.bank.status === "done" ? `${data.bank.statements} statements from ${data.bank.institution || "the bank"}` : data.bank.status}</span> : null}</div>
      <div className="card">
        {data.accounts.length ? data.accounts.map(a => (
          <div className="row" key={a.id}>
            <Avatar name={a.name || a.email} size="sm" />
            <div className="grow">
              <div className="title">{a.name || a.email}{a.status === "disabled" ? <Pill status="off" label="Turned off" /> : <Pill status="active" label="Has an account" />}</div>
              <div className="line">{a.email}{a.phone ? ` · ${a.phone}` : ""}</div>
              <div className="when">{[a.passkeys ? `${a.passkeys} passkey${a.passkeys === 1 ? "" : "s"}` : "", a.totp ? "authenticator app" : ""].filter(Boolean).join(" and ") || "No second factor yet"} · {a.lastLoginAt ? `last signed in ${fmt.when(a.lastLoginAt)}` : "hasn't signed in yet"}</div>
              {data.client && a.clientId !== data.client.id ? <div className="when">{a.clientId ? "Linked to a different client record" : `Not linked to ${data.client.biz}, so only this account's own applications show`}</div> : null}
            </div>
            {isAdmin && data.client && a.clientId !== data.client.id ? <div className="row-actions"><button className="btn btn-sm" disabled={busy === "link" + a.id} onClick={() => linkClient(a)}>Link to client</button></div> : null}
          </div>
        )) : (
          <div className="row">
            <div className="grow">
              <div className="title">No portal account yet</div>
              <div className="line">Invite the client to see this deal, upload documents, read your messages and accept offers in the portal. Applications they send from the portal land here automatically.</div>
            </div>
            {canWrite ? <div className="row-actions"><button className="btn btn-sm btn-primary" disabled={busy === "invite"} onClick={sendInvite}><Icon name="mail" size={14} />{busy === "invite" ? "Making…" : "Invite to the portal"}</button></div> : null}
          </div>
        )}
        {data.accounts.length && canWrite ? <div className="row"><div className="grow"><div className="line">Another owner or a new email? Send another invite.</div></div><div className="row-actions"><button className="btn btn-sm" disabled={busy === "invite"} onClick={sendInvite}><Icon name="mail" size={14} />Invite</button></div></div> : null}
        {invite ? (
          <div className="row" style={{ background: "var(--blue-tint)" }}>
            <div className="grow">
              <div className="title">{invite.sentTo ? `Emailed to ${invite.sentTo}` : "Invite link ready"}</div>
              <div className="line">Works once, for {invite.expiresInDays} days. Text it from your phone if email isn't set up.</div>
              <div className="line" style={{ fontFamily: "ui-monospace, Menlo, monospace", fontSize: 12 }}>{invite.link}</div>
            </div>
            <div className="row-actions"><button className="btn btn-sm" onClick={() => copy(invite.link)}><Icon name="copy" size={14} />Copy</button></div>
          </div>
        ) : null}
      </div>

      <div className="section-bar" style={{ marginTop: 18 }}>
        <span className="label">Documents asked for{toReview ? ` · ${toReview} to review` : ""}</span>
        {canWrite && products === null ? <button className="btn btn-sm" onClick={() => setProducts(data.products.slice())}>{list.length ? "Change products" : "Ask for documents"}</button> : null}
      </div>
      {products !== null ? (
        <div className="card">
          <div className="card-body">
            <div className="hint" style={{ marginBottom: 10 }}>The documents the client is asked for follow the products. Lines something already happened on stay.</div>
            <div className="check-row">
              {PRODUCT_OPTIONS.map(p => <label key={p.value}><input type="checkbox" checked={products.includes(p.value)} onChange={e => setProducts(e.target.checked ? products.concat([p.value]) : products.filter(x => x !== p.value))} /> {p.label}</label>)}
            </div>
            <div style={{ display: "flex", gap: 8, marginTop: 14 }}>
              <button className="btn btn-sm btn-primary" disabled={busy === "products"} onClick={() => run("products", () => API.setChecklistProducts(deal.id, products), "Document list saved").then(() => setProducts(null))}>Save</button>
              <button className="btn btn-sm" onClick={() => setProducts(null)}>Cancel</button>
            </div>
          </div>
        </div>
      ) : null}
      {list.length ? (
        <div className="card card-flush">
          {data.products.length ? <div className="row"><div className="grow"><div className="chips" style={{ marginTop: 0 }}>{data.products.map(k => <span className="chip" key={k}>{productName(k)}</span>)}</div></div></div> : null}
          {groups.map(g => (
            <React.Fragment key={g}>
              <div className="row" style={{ paddingTop: 10, paddingBottom: 6 }}><span className="label">{g}</span></div>
              {list.filter(i => i.group === g).map(i => (
                <div className="row" key={i.key}>
                  <div className="grow">
                    <div className="title">{i.name}<Pill status={CHECK_STATUS[i.status][0]} label={CHECK_STATUS[i.status][1]} />{!i.required ? <span className="chip" style={{ marginTop: 0 }}>If it applies</span> : null}</div>
                    {i.status === "reupload" && i.note ? <div className="line">Asked: {i.note}</div> : null}
                    {i.via ? <div className="line">{i.via}</div> : null}
                    {i.files.length ? <div className="line">{i.files.map((f, k) => <React.Fragment key={k}>{k ? " · " : ""}<a href={API.documentUrl(f.objectKey)} target="_blank" rel="noopener">{f.name}{f.side ? ` (${f.side})` : ""}</a>{f.by !== "you" ? ` from ${f.by === "your funding team" ? "the office" : f.by}` : ""}</React.Fragment>)}</div> : <div className="when">Nothing uploaded yet</div>}
                    {i.updatedAt ? <div className="when">Updated {fmt.when(i.updatedAt)}</div> : null}
                    {askFor && askFor.key === i.key ? (
                      <div style={{ display: "flex", gap: 8, marginTop: 8, flexWrap: "wrap" }}>
                        <input className="input" style={{ flex: "1 1 220px" }} autoFocus value={askFor.note} onChange={e => setAskFor({ ...askFor, note: e.target.value })} placeholder="What's wrong, e.g. June is missing pages 3 and 4" aria-label="What's wrong" />
                        <button className="btn btn-sm btn-primary" disabled={askFor.note.trim().length < 4} onClick={() => setStatus(i.key, "reupload", askFor.note.trim()).then(() => setAskFor(null))}>Ask the client</button>
                        <button className="btn btn-sm" onClick={() => setAskFor(null)}>Cancel</button>
                      </div>
                    ) : null}
                  </div>
                  {canWrite ? (
                    <div className="row-actions" style={{ flexWrap: "wrap", justifyContent: "flex-end" }}>
                      {i.status !== "approved" && i.files.length ? <button className="btn btn-sm btn-primary" disabled={!!busy} onClick={() => setStatus(i.key, "approved")}>Approve</button> : null}
                      {i.status !== "reupload" ? <button className="btn btn-sm" onClick={() => setAskFor({ key: i.key, note: "" })}>Re-upload</button> : null}
                      {i.status !== "waived" ? <button className="btn btn-sm btn-quiet" disabled={!!busy} onClick={() => setStatus(i.key, "waived")}>Not needed</button> : <button className="btn btn-sm btn-quiet" disabled={!!busy} onClick={() => setStatus(i.key, "needed")}>Ask again</button>}
                    </div>
                  ) : null}
                </div>
              ))}
            </React.Fragment>
          ))}
          {canWrite && notOnList.length ? (
            <div className="row">
              <div className="grow" style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
                <select className="select" style={{ flex: "1 1 240px" }} value={adding} onChange={e => setAdding(e.target.value)} aria-label="Ask for another document">
                  <option value="">Ask for another document…</option>
                  {notOnList.map(k => <option key={k} value={k}>{data.catalog.docs[k].name}</option>)}
                </select>
                <button className="btn btn-sm" disabled={!adding || busy === "add"} onClick={() => run("add", () => API.addChecklistItem(deal.id, adding), "Added; the client is told").then(() => setAdding(""))}>Ask</button>
              </div>
            </div>
          ) : null}
        </div>
      ) : products === null ? <div className="card"><Empty title="No documents asked for">Portal applications come with a list from their products. For a deal made here, choose its products to ask the client for documents in the portal.</Empty></div> : null}

      <div className="section-bar" style={{ marginTop: 18 }}><span className="label">Messages with the client</span></div>
      <div className="card">
        <div className="pmsgs" ref={thread}>
          {data.messages.length ? data.messages.map(m => (
            <div key={m.id} className={`pmsg${m.from === "staff" ? " ours" : ""}`}>
              <div className="who">{m.from === "staff" ? (m.by || "Office") : "Client"} · {fmt.when(m.at)}</div>
              {m.text}
            </div>
          )) : <div className="muted" style={{ padding: 16 }}>No messages yet.</div>}
        </div>
        {canWrite ? (
          <form className="pcompose" onSubmit={e => { e.preventDefault(); const t = reply.trim(); if (!t) return; run("send", () => API.portalMessage(deal.id, t), data.accounts.length ? "Sent; the client is emailed" : null).then(() => setReply("")); }}>
            <textarea className="input" rows={2} value={reply} onChange={e => setReply(e.target.value)} placeholder={data.accounts.length ? "Write to the client" : "Invite the client first to message them here"} disabled={!data.accounts.length} aria-label="Message to the client" maxLength={2000} />
            <button className="btn btn-primary" type="submit" disabled={!data.accounts.length || !reply.trim() || busy === "send"}><Icon name="mail" size={14} />Send</button>
          </form>
        ) : null}
      </div>
    </>
  );
}

// ── Settings ────────────────────────────────────────────────────────────────
const US_STATES = "AL AK AZ AR CA CO CT DE DC FL GA HI ID IL IN IA KS KY LA ME MD MA MI MN MS MO MT NE NV NH NJ NM NY NC ND OH OK OR PA RI SC SD TN TX UT VT VA WA WV WI WY".split(" ");
function PortalSettingsCard({ s, save }) {
  const p = s.portal || {}, b = s.bankVerify || { provider: "off", services: {} };
  const notServed = p.statesNotServed || [];
  const [editing, setEditing] = useState(false);
  const toggleState = (code) => save({ portal: { statesNotServed: notServed.includes(code) ? notServed.filter(x => x !== code) : notServed.concat([code]) } }, "States saved");
  const svc = b.services || {};
  return (
    <div className="card">
      <div className="card-head"><span className="label">Client portal</span><a className="link-btn" href="/portal/" target="_blank" rel="noopener">Open the portal</a></div>
      <div className="switch-row">
        <div><div className="t">Portal open</div><div className="d">Clients sign up and sign in at <span className="token">{location.origin}/portal/</span>. Off closes sign-up and sign-in; nothing is deleted.</div></div>
        <Switch checked={p.enabled !== false} onChange={on => save({ portal: { enabled: on } }, on ? "Portal opened" : "Portal closed")} label="Portal open" />
      </div>
      <div className="switch-row" style={{ alignItems: "flex-start" }}>
        <div style={{ minWidth: 0 }}>
          <div className="t">States not taken online</div>
          <div className="d">Businesses in these states can't apply in the portal (their state wants the funder's disclosure signed before an offer is accepted). They see a message to call instead.</div>
          <div className="chips">{notServed.length ? notServed.map(c => <span className="chip" key={c}>{c}</span>) : <span className="chip">None</span>}</div>
          {editing ? (
            <div className="check-row" style={{ marginTop: 10 }}>
              {US_STATES.map(c => <label key={c} style={{ minHeight: 32, padding: "0 10px" }}><input type="checkbox" checked={notServed.includes(c)} onChange={() => toggleState(c)} /> {c}</label>)}
            </div>
          ) : null}
        </div>
        <button className="btn btn-sm" onClick={() => setEditing(!editing)}>{editing ? "Done" : "Change"}</button>
      </div>
      <div className="form-section" style={{ borderTop: "1px solid var(--rule-soft)" }}>
        <span className="label">Bank verification</span>
        <div className="hint" style={{ marginBottom: 10 }}>Lets clients connect their bank instead of uploading statements; the statements land on the deal and are read like any upload. Uploading always stays available.</div>
        <div className="check-row" role="radiogroup" aria-label="Bank verification">
          {[["off", "Off"], ["plaid", "Plaid"], ["decisionlogic", "DecisionLogic"]].map(([id, label]) => (
            <label key={id}><input type="radio" name="bankVerify" checked={(b.provider || "off") === id} onChange={() => save({ bankVerify: { provider: id } }, id === "off" ? "Bank verification off" : `${label} switched on`)} /> {label}</label>
          ))}
        </div>
        {["plaid", "decisionlogic"].map(id => svc[id] ? (
          <div className="detail" key={id} style={{ marginTop: 10 }}>
            <b>{svc[id].label}: {svc[id].ready ? "ready to switch on" : "not ready yet"}</b>
            {svc[id].missing && svc[id].missing.length ? <div>Still needs {svc[id].missing.join("; ")}.</div> : null}
          </div>
        ) : null)}
      </div>
    </div>
  );
}

function PortalAccountsCard({ toast, confirm }) {
  const [list, setList] = useState(null);
  const [q, setQ] = useState("");
  const [error, setError] = useState("");
  const [demoFor, setDemoFor] = useState(null);   // the account whose demo password is being typed
  const [demoPw, setDemoPw] = useState("");
  const load = useCallback(async () => {
    try { setList(await API.portalAccounts()); setError(""); } catch (err) { if (err.status !== 401) setError(err.message); }
  }, []);
  useEffect(() => { load(); }, []);
  const change = async (a, body, ok) => { try { await API.patchPortalAccount(a.id, body); await load(); toast(ok); } catch (err) { toast(err.message, true); } };
  const act = async (fn, ok) => { try { await fn(); await load(); toast(ok); return true; } catch (err) { toast(err.message, true); return false; } };
  const needle = q.trim().toLowerCase();
  const rows = (list || []).filter(a => !needle || [a.email, a.name, a.biz, ...(a.deals || []).map(d => d.biz)].some(v => String(v || "").toLowerCase().includes(needle)));
  return (
    <div className="card" style={{ gridColumn: "1 / -1" }}>
      <div className="card-head"><span className="label">Client portal accounts{list ? ` · ${list.length}` : ""}</span>
        <input className="input" style={{ maxWidth: 240, height: 34 }} value={q} onChange={e => setQ(e.target.value)} placeholder="Search" aria-label="Search portal accounts" /></div>
      {error ? <div className="banner" role="alert" style={{ margin: 12 }}>{error} <button className="link-btn" onClick={load}>Try again</button></div> : null}
      {list === null && !error ? <div className="empty"><p>Loading…</p></div> : rows.length ? (
        <div className="table-wrap">
          <table className="list">
            <thead><tr><th>Client</th><th>Deals</th><th>Sign-in</th><th>Last signed in</th><th /></tr></thead>
            <tbody>
              {rows.map(a => (
                <tr key={a.id}>
                  <td><div className="who"><Avatar name={a.name || a.email} size="sm" /><div><b>{a.name || a.email}</b><span>{a.email}</span>{a.biz ? <span>{a.biz}</span> : null}</div></div></td>
                  <td data-label="Deals">{a.deals.length ? a.deals.map(d => <a key={d.id} href={`#/deals/${encodeURIComponent(d.id)}`} style={{ display: "block" }}>{d.biz || d.id}</a>) : <span className="muted">None yet</span>}</td>
                  <td data-label="Sign-in">{a.status === "disabled" ? <Pill status="off" label="Turned off" /> : !a.verified ? <Pill status="pending" label="Email not confirmed" /> : a.demoAccount ? <Pill status="pending" label="Demo · password only" /> : <Pill status="active" label={[a.passkeys ? "Passkey" : "", a.totp ? "App" : ""].filter(Boolean).join(" + ") || "No 2nd factor"} />}</td>
                  <td data-label="Last signed in">{a.lastLoginAt ? fmt.when(a.lastLoginAt) : <span className="muted">Never</span>}</td>
                  <td className="acts">
                    {demoFor === a.id ? (
                      <form className="row" style={{ gap: 6 }} onSubmit={e => { e.preventDefault(); act(() => API.patchPortalAccount(a.id, { demoAccount: true, password: demoPw }), "Demo sign-in on").then(done => { if (done) { setDemoFor(null); setDemoPw(""); } }); }}>
                        <input className="input" style={{ height: 32, width: 160 }} type="text" autoComplete="off" value={demoPw} onChange={e => setDemoPw(e.target.value)} placeholder="Demo password" aria-label="Demo password" autoFocus />
                        <button className="btn btn-sm btn-primary" type="submit" disabled={demoPw.length < 8}>Save</button>
                        <button className="btn btn-sm" type="button" onClick={() => { setDemoFor(null); setDemoPw(""); }}>Cancel</button>
                      </form>
                    ) : a.demoAccount
                      ? <button className="btn btn-sm" onClick={() => confirm({ title: "Turn off demo sign-in?", body: `${a.email} will need its email code and a new passkey or authenticator app at its next sign-in.`, confirmLabel: "Turn off demo sign-in", plain: true, onConfirm: () => act(() => API.patchPortalAccount(a.id, { demoAccount: false }), "Demo sign-in off") })}>Demo off</button>
                      : a.verified ? <button className="btn btn-sm" title="Password only, no second step: for showing reps the portal" onClick={() => { setDemoFor(a.id); setDemoPw(""); }}>Demo sign-in</button> : null}
                    {a.status === "disabled"
                      ? <button className="btn btn-sm" onClick={() => change(a, { status: "active" }, "Account turned on")}>Turn on</button>
                      : <button className="btn btn-sm btn-danger" onClick={() => confirm({ title: "Turn this account off?", body: `${a.email} is signed out at once and can't sign in until it is turned back on.`, confirmLabel: "Turn off", onConfirm: () => change(a, { status: "disabled" }, "Account turned off") })}>Turn off</button>}
                    {a.hasDemo
                      ? <button className="btn btn-sm" onClick={() => confirm({ title: "Remove the demo data?", body: `The demo advances, the demo application and its offers are deleted from ${a.email} and from the CRM.`, confirmLabel: "Remove demo data", onConfirm: () => act(() => API.removePortalDemo(a.id), "Demo data removed") })}>Remove demo</button>
                      : <button className="btn btn-sm" onClick={() => confirm({ title: "Add demo data?", body: `Adds two funded demo advances and a demo application with two offers and a message to ${a.email}, so you can show how the portal looks to a client. Demo deals are named "(Demo)", carry no commission, and set off no repayment or renewal alerts. They also show in the CRM until you remove them.`, confirmLabel: "Add demo data", plain: true, onConfirm: () => act(() => API.addPortalDemo(a.id), "Demo data added") })}>Add demo</button>}
                    <button className="btn btn-sm btn-danger" onClick={() => confirm({ title: "Delete this account?", body: `${a.email} can no longer sign in, and its passkeys, messages and demo data are deleted. Its real applications stay in the CRM, unlinked; delete those from the deal file if they were tests. This can't be undone.`, confirmLabel: "Delete account", onConfirm: () => act(() => API.deletePortalAccount(a.id), "Account deleted") })}>Delete</button>
                    {a.passkeys || a.totp ? <button className="btn btn-sm" onClick={() => confirm({ title: "Clear their passkeys and app?", body: `For a client who lost their phone, once you've confirmed who they are. ${a.email} is signed out; next time they sign in with their password, confirm their email, and set up a new passkey or app.`, confirmLabel: "Clear sign-in methods", onConfirm: () => change(a, { resetFactors: true }, "Sign-in methods cleared") })}>Lost phone</button> : null}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      ) : <Empty title={needle ? "No match" : "No portal accounts yet"}>{needle ? "Try another name or email." : "Clients who sign up at /portal/ show here."}</Empty>}
    </div>
  );
}

Object.assign(window, { PortalTab, usePortal, PortalSettingsCard, PortalAccountsCard });
