/* screen-clients.jsx — Clients list + detail → window.QScreens.{Clients, ClientDetail} */
(function () {
  const { useState } = React;
  const Q = window.Q, LIB = window.LIB, Icons = window.Icons;
  const { Icon } = Icons;
  const { AppBar, Card, Money, Kicker, Button, StatusPill, Avatar, EmptyState, IconButton, Badge, Sheet, Field, Input, Textarea, Segmented } = Q;
  const t = window.t;

  /* Project status → existing pill palette (matches screen-projects.jsx). */
  const PILL = { active: "sent", wrap: "partial", done: "paid", onhold: "draft" };

  /* A project rate's unit: hourly when the whole team bills hourly, else day
     (matches screen-projects.jsx; the rate itself has no stored unit). */
  function rateUnit(p) {
    const team = (p && p.team) || [];
    return team.length && team.every(m => (m.unit || "day") === "hour") ? "hour" : "day";
  }
  function rateLabel(p, rate, currency) {
    const fmt = window.fmtMoney(rate, currency, { cents: false });
    return rateUnit(p) === "hour" ? fmt + "/" + t("unit.hour") : t("cl.proj.rate", { rate: fmt });
  }

  function lastInvoice(db, clientId) {
    const list = db.INVOICES.filter(i => i.clientId === clientId && i.kind !== "quote" && i.kind !== "credit").sort((a, b) => new Date(b.issued) - new Date(a.issued));
    return list[0] || null;
  }

  /* Days past due on this client's MOST overdue invoice, or 0 if nothing is
     overdue — turns the Clients list into a "who do I chase first" queue. */
  function maxDaysLate(db, clientId, today) {
    let max = 0;
    for (const inv of db.INVOICES) {
      if (inv.clientId !== clientId || inv.kind !== "invoice") continue;
      if (LIB.effectiveStatus(inv, today) !== "overdue") continue;
      const late = -LIB.daysBetween(today, inv.due);
      if (late > max) max = late;
    }
    return max;
  }

  /* ── List ────────────────────────────────────────────────────── */
  const SORTS = [
    { id: "amount", key: "sort.amount" },
    { id: "name", key: "sort.name" },
    { id: "last", key: "cl.sort.last" },
  ];
  function Clients({ api }) {
    const { db, today } = api;
    const [query, setQuery] = useState("");
    const [sort, setSort] = useState("amount");
    const [addOpen, setAddOpen] = useState(false);
    const cycleSort = () => setSort(s => SORTS[(SORTS.findIndex(x => x.id === s) + 1) % SORTS.length].id);
    const ClientPicker = window.QEditor && window.QEditor.ClientPicker;
    let list = db.CLIENTS.slice();
    if (query.trim()) {
      const q = query.toLowerCase();
      list = list.filter(c =>
        c.name.toLowerCase().includes(q) ||
        (c.email || "").toLowerCase().includes(q) ||
        (c.contact || "").toLowerCase().includes(q) ||
        (c.sector || "").toLowerCase().includes(q));
    }
    list = list.sort((a, b) => {
      if (sort === "name") return a.name.localeCompare(b.name);
      if (sort === "last") {
        const la = lastInvoice(db, a.id), lb = lastInvoice(db, b.id);
        return new Date((lb && lb.issued) || 0) - new Date((la && la.issued) || 0) || a.name.localeCompare(b.name);
      }
      return LIB.clientBalance(db, b.id, today) - LIB.clientBalance(db, a.id, today) || a.name.localeCompare(b.name);
    });
    /* Home-currency total only; foreign-currency balances are kept per currency
       (same pattern as LIB.summary) so we never add USD into a EUR figure. */
    const home = db.COMPANY.currency;
    let totalOut = 0; const fx = {};
    for (const x of db.CLIENTS) {
      const b = LIB.clientBalance(db, x.id, today);
      const cur = x.currency || home;
      if (cur === home) totalOut += b;
      else if (b !== 0) fx[cur] = (fx[cur] || 0) + b;
    }

    return (
      <div style={{ paddingBottom: 4 }}>
        <AppBar large title={t("cl.title")} kicker={t("cl.accounts", { count: db.CLIENTS.length })}
          right={<IconButton name="plus" label={t("cl.new")} onClick={() => setAddOpen(true)} />} />
        <div style={{ padding: "6px 16px 0", display: "flex", flexDirection: "column", gap: 12 }}>
          {/* The screen's one money moment: total outstanding across accounts. */}
          {db.CLIENTS.length > 0 && (
            <div style={{ display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" }}>
              <Money amount={totalOut} currency={home} size={16} cents={false} />
              {Object.entries(fx).map(([cur, amt]) => (
                <span key={cur} style={{ fontFamily: "var(--font-mono)", fontSize: 12, fontWeight: 600, color: "var(--color-text-3)", whiteSpace: "nowrap" }}>+ {window.fmtMoney(amt, cur, { cents: false })}</span>
              ))}
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 600, letterSpacing: "0.10em", textTransform: "uppercase", color: "var(--color-text-3)" }}>{t("cl.totalout", { count: db.CLIENTS.length })}</span>
            </div>
          )}
          {/* Search + sort row, same pattern as the Invoices list. */}
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <label style={{ flex: 1, minWidth: 0, display: "flex", alignItems: "center", gap: 8, height: 42, padding: "0 14px", background: "var(--color-surface-0)", border: "1.4px solid var(--color-border-strong)", borderRadius: 999, cursor: "text", transition: "border-color .15s" }}>
              <Icon name="search" size={17} color="var(--color-text-3)" />
              <input value={query} onChange={e => setQuery(e.target.value)} placeholder={t("cl.search")} aria-label={t("cl.search")}
                onFocus={e => e.target.parentElement.style.borderColor = "var(--color-primary)"}
                onBlur={e => e.target.parentElement.style.borderColor = "var(--color-border-strong)"}
                style={{ flex: 1, minWidth: 0, height: "100%", border: "none", background: "transparent", outline: "none", fontSize: 15, color: "var(--color-text-1)", fontFamily: "var(--font-sans)" }} />
              {query && <button onClick={() => setQuery("")} aria-label={t("g.close")} style={{ border: "none", background: "transparent", color: "var(--color-text-3)", cursor: "pointer", display: "flex", padding: 2 }}><Icon name="x" size={16} /></button>}
            </label>
            <button onClick={cycleSort} aria-label={t("sort.label")} className="q-tap" style={{ flexShrink: 0, height: 42, padding: "0 12px", borderRadius: 999, border: "1.4px solid var(--color-border-strong)", background: "var(--color-surface-0)", color: "var(--color-text-2)", fontSize: 12, fontWeight: 600, cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 4 }}>
              <Icon name="sliders" size={13} />{t(SORTS.find(s => s.id === sort).key)}
            </button>
          </div>

          {list.length === 0 ? (
            <EmptyState icon="clients" title={query ? t("g.nomatch") : t("cl.empty.title")} body={query ? t("g.trysearch") : t("cl.empty.body")}
              action={!query && <Button variant="primary" size="md" icon="plus" onClick={() => setAddOpen(true)}>{t("cl.new")}</Button>} />
          ) : (
            <Card pad={0} style={{ overflow: "hidden" }}>
              <div className="q-stagger">
              {list.map((c, i) => {
                const bal = LIB.clientBalance(db, c.id, today);
                const last = lastInvoice(db, c.id);
                const late = bal > 0 ? maxDaysLate(db, c.id, today) : 0;
                return (
                  <Q.SwipeRow key={c.id} onClick={() => api.go("client", { id: c.id })} last={i === list.length - 1}
                    actions={[{ icon: "plus", label: t("cl.newinvoice"), tone: "neutral", onPress: () => api.openManual({ clientId: c.id }) }]}>
                    <Avatar name={c.name} size={42} square />
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                        <div style={{ fontSize: 14, fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.name}</div>
                      </div>
                      {/* Money moment: an overdue account shouts "chase me" in
                         terracotta with how late it is; otherwise the neutral
                         last-invoice context line. */}
                      {late > 0
                        ? <div style={{ fontSize: 12, fontWeight: 600, color: "var(--color-accent)", marginTop: 1 }}>{t("due.overdue", { d: late })}</div>
                        : <div style={{ fontSize: 12, color: "var(--color-text-3)", marginTop: 1 }}>{last ? t("cl.lastinvoice", { date: window.fmtDate(last.issued, "short") }) : t("cl.never")}</div>}
                    </div>
                    {/* True signed balance: credits can push an account negative; the
                       row must agree with the header rollup and the detail screen
                       (sign from fmtMoney, e.g. -€420 — same convention as the
                       billing-history rows; non-positive muted like the detail).
                       An owed balance carries the screen's money signal: overdue →
                       terracotta + terracotta edge (chase now); merely outstanding
                       → gold edge; zero/credit muted. */}
                    <div style={{ textAlign: "right", display: "flex", alignItems: "center", gap: 9 }}>
                      {bal > 0
                        ? <Money amount={bal} currency={c.currency} size={14} cents={false} color={late > 0 ? "var(--color-accent)" : undefined} />
                        : <Money amount={bal} currency={c.currency} size={14} cents={false} color={bal < 0 ? "var(--color-text-2)" : "var(--color-text-3)"} />}
                      <span aria-hidden="true" style={{ width: 3, height: 22, borderRadius: 2, flexShrink: 0, background: late > 0 ? "var(--color-accent)" : bal > 0 ? "var(--color-gold)" : "transparent", opacity: bal > 0 ? (late > 0 ? 0.7 : 0.55) : 0 }} />
                    </div>
                  </Q.SwipeRow>
                );
              })}
              </div>
            </Card>
          )}
        </div>
        {ClientPicker && <ClientPicker api={api} open={addOpen} onClose={() => setAddOpen(false)} onPick={(id) => { setAddOpen(false); api.go("client", { id }); }} currency={db.COMPANY.currency} mode="manage" />}
      </div>
    );
  }

  /* ── Edit client sheet (same field set as the new-client form) ── */
  function EditClientSheet({ client, onClose, onSave }) {
    const [form, setForm] = useState({ name: client.name || "", contact: client.contact || "", email: client.email || "", phone: client.phone || "", address: client.address || "", vat: client.vat || "", reg: client.reg || "" });
    const setF = patch => setForm(f => ({ ...f, ...patch }));
    return (
      <Sheet open onClose={onClose} height="92%" title={t("cl.edit.title")}
        footer={<Button variant="primary" size="lg" full icon="check" disabled={!form.name.trim()} onClick={() => onSave({ ...form, name: form.name.trim() })}>{t("man.client.update")}</Button>}>
        <div style={{ padding: "2px 16px 16px", display: "flex", flexDirection: "column", gap: 12 }}>
          <Field label={t("cl.field.name")}><Input value={form.name} onChange={e => setF({ name: e.target.value })} placeholder={t("man.client.name.ph")} /></Field>
          <Field label={t("man.client.contact")}><Input value={form.contact} onChange={e => setF({ contact: e.target.value })} /></Field>
          <Field label={t("cl.email")}><Input value={form.email} onChange={e => setF({ email: e.target.value })} type="email" /></Field>
          <Field label={t("cl.phone")}><Input value={form.phone} onChange={e => setF({ phone: e.target.value })} mono /></Field>
          <Field label={t("cl.address")}><Textarea value={form.address} onChange={e => setF({ address: e.target.value })} rows={2} /></Field>
          <div style={{ display: "flex", gap: 12 }}>
            <Field label={t("cl.taxid")} style={{ flex: 1 }}><Input value={form.vat} onChange={e => setF({ vat: e.target.value })} mono /></Field>
            <Field label={t("cl.reg")} style={{ flex: 1 }}><Input value={form.reg} onChange={e => setF({ reg: e.target.value })} mono /></Field>
          </div>
        </div>
      </Sheet>
    );
  }

  /* ── Add project sheet (name + billing basis, same keys as editor) ── */
  function AddProjectSheet({ currency, onClose, onAdd }) {
    const [name, setName] = useState("");
    const [basis, setBasis] = useState("rate"); // rate | budget | none
    const [amount, setAmount] = useState("");
    const sym = (window.CURRENCIES[currency] || {}).symbol;
    const add = () => {
      const amt = Number(amount) || 0;
      onAdd({ id: LIB.newId("pr"), name: name.trim(), status: "active", rate: basis === "rate" ? amt : null, budget: basis === "budget" ? amt : null });
    };
    return (
      <Sheet open onClose={onClose} height="auto" title={t("man.proj.new")}
        footer={<Button variant="primary" size="lg" full icon="check" disabled={!name.trim()} onClick={add}>{t("man.proj.add")}</Button>}>
        <div style={{ padding: "2px 16px 16px", display: "flex", flexDirection: "column", gap: 12 }}>
          <Field label={t("man.proj.name")}><Input value={name} onChange={e => setName(e.target.value)} placeholder={t("man.proj.name.ph")} autoFocus /></Field>
          <div>
            <Kicker style={{ marginBottom: 8 }}>{t("man.proj.basis")}</Kicker>
            <Segmented value={basis} onChange={setBasis}
              options={[{ value: "rate", label: t("man.proj.rate") }, { value: "budget", label: t("man.proj.budget") }, { value: "none", label: t("man.proj.none") }]} />
          </div>
          {basis !== "none" && (
            <Field label={t("man.proj.amount")}>
              <div style={{ position: "relative" }}>
                <span style={{ position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)", fontFamily: "var(--font-mono)", fontSize: 15, color: "var(--color-text-3)" }}>{sym}</span>
                <Input value={amount} onChange={e => setAmount(e.target.value.replace(/[^\d.]/g, ""))} mono inputMode="decimal" style={{ paddingLeft: 34 }} />
              </div>
            </Field>
          )}
        </div>
      </Sheet>
    );
  }

  /* ── Detail ──────────────────────────────────────────────────── */
  function ClientDetail({ api, id }) {
    const { db, setDb, today } = api;
    const [editOpen, setEditOpen] = useState(false);
    const [addProj, setAddProj] = useState(false);
    const c = LIB.getClient(db, id);
    if (!c) return <div><AppBar title={t("cl.title")} onBack={() => api.back()} /><EmptyState icon="clients" title={t("g.notfound")} body={t("g.trysearch")} /></div>;
    const bal = LIB.clientBalance(db, id, today);
    const invoices = LIB.clientInvoices(db, id).sort((a, b) => new Date(b.issued) - new Date(a.issued));
    const projects = c.projects || [];

    /* Chase target: the client's MOST overdue invoice (longest past due), so a
       bal>0 detail can hand the owner a one-tap reminder. Falls back to any
       open (sent/partial) invoice when nothing is technically overdue yet but
       the account still carries a positive balance. */
    let mostOverdueInv = null, worstLate = -Infinity;
    let oldestOpenInv = null, oldestOpenDue = Infinity;
    for (const inv of invoices) {
      if (inv.kind !== "invoice") continue;
      const eff = LIB.effectiveStatus(inv, today);
      if (eff === "overdue") {
        const late = -LIB.daysBetween(today, inv.due); // days past due (positive)
        if (late > worstLate) { worstLate = late; mostOverdueInv = inv; }
      } else if (eff === "sent" || eff === "partial") {
        const dueT = +new Date(inv.due);
        if (dueT < oldestOpenDue) { oldestOpenDue = dueT; oldestOpenInv = inv; }
      }
    }
    const chaseInv = mostOverdueInv || oldestOpenInv;

    const saveClient = (patch) => {
      setDb(d => ({ ...d, CLIENTS: d.CLIENTS.map(x => x.id === id ? { ...x, ...patch } : x) }));
      setEditOpen(false);
      Q.toast(t("toast.clientUpdated", { name: patch.name }), "check");
    };
    const addProject = (p) => {
      setDb(d => ({ ...d, CLIENTS: d.CLIENTS.map(x => x.id === id ? { ...x, projects: [...(x.projects || []), p] } : x) }));
      setAddProj(false);
      Q.toast(t("toast.added", { name: p.name }), "check");
    };

    return (
      <div style={{ paddingBottom: 40 }}>
        <AppBar title={c.name} onBack={() => api.back()}
          right={<IconButton name="edit" label={t("cl.edit")} onClick={() => setEditOpen(true)} />} />
        {/* Detail assembles top-down: header → balance → contact → projects →
            history cascade in (q-stagger; reduced-motion-safe via global CSS). */}
        <div className="q-stagger" style={{ padding: "4px 16px 0", display: "flex", flexDirection: "column", gap: 16 }}>
          {/* header — name lives in the AppBar (no duplicate serif title here);
              this row carries identity context: avatar, sector, and how many
              invoices we've billed this account so the page isn't sparse. */}
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <Avatar name={c.name} size={56} square />
            <div style={{ flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 3 }}>
              {c.sector && <div style={{ fontSize: 14, fontWeight: 600, color: "var(--color-text-1)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.sector}</div>}
              <div style={{ fontSize: 12, color: "var(--color-text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                {(() => { const li = lastInvoice(db, id); return li ? t("cl.lastinvoice", { date: window.fmtDate(li.issued, "short") }) : t("cl.never"); })()}
              </div>
            </div>
          </div>

          {/* balance + CTA. The kicker tells the truth about the sign: a positive
              balance is genuinely outstanding (gold = money to collect), zero is
              "all settled", negative means we owe them (a credit). When money is
              outstanding the get-paid action — Send reminder on the most overdue
              invoice — leads; New invoice demotes to outline. */}
          <Card pad={18}>
            <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between" }}>
              <div>
                <Kicker>{bal > 0 ? t("cl.balance") : bal < 0 ? t("cl.balance.credit") : t("cl.balance.settled")}</Kicker>
                <div style={{ marginTop: 6 }}><Q.MoneyDisplay amount={bal} currency={c.currency} size={28} cents={false} color={bal > 0 ? "var(--color-gold)" : "var(--color-text-2)"} animKey={c.id} /></div>
              </div>
            </div>
            {bal > 0 && chaseInv ? (
              <div style={{ display: "flex", flexDirection: "column", gap: 10, marginTop: 16 }}>
                <Button variant="primary" size="lg" full icon="send" onClick={() => api.openSend(chaseInv.id, "remind")}>{t("det.remind")}</Button>
                <Button variant="outline" size="lg" full icon="plus" onClick={() => api.openManual({ clientId: c.id })}>{t("cl.newinvoice")}</Button>
              </div>
            ) : (
              <Button variant="primary" size="lg" full icon="plus" style={{ marginTop: 16 }} onClick={() => api.openManual({ clientId: c.id })}>{t("cl.newinvoice")}</Button>
            )}
          </Card>

          {/* contact. Empty fields are no longer bare "-" dead rows; they read
              as a muted "add" prompt that opens the edit sheet (sparse-dash fix).
              Email/phone are actionable: tap the row to mailto:/tel:, with a
              dedicated 44px copy button on the right. */}
          {(() => {
            const copy = (text, toastKey) => () => { try { navigator.clipboard.writeText(text); } catch (e) {} Q.toast(t(toastKey), "copy"); };
            const rows = [
              { icon: "user", value: c.contact, sub: t("man.client.contact") },
              { icon: "mail", value: c.email, sub: t("cl.email"), href: c.email ? "mailto:" + c.email : null, copy: c.email ? copy(c.email, "toast.emailCopied") : null },
              { icon: "phone", value: c.phone, sub: t("cl.phone"), href: c.phone ? "tel:" + c.phone.replace(/[^\d+]/g, "") : null, copy: c.phone ? copy(c.phone, "pay.copied") : null, mono: true },
              { icon: "mapPin", value: c.address, sub: t("cl.address"), multiline: true },
              { icon: "hash", value: c.vat, sub: t("cl.taxid"), mono: true },
            ];
            return (
              <div>
                <Kicker style={{ marginBottom: 10 }}>{t("cl.contact")}</Kicker>
                <Card pad={0}>
                  {rows.map((r, i) => (
                    <ContactRow key={r.icon} icon={r.icon} value={r.value} sub={r.sub} href={r.href} copy={r.copy}
                      mono={r.mono} multiline={r.multiline} last={i === rows.length - 1} onAdd={() => setEditOpen(true)} />
                  ))}
                </Card>
              </div>
            );
          })()}

          {/* projects */}
          <div>
            <div style={{ display: "flex", alignItems: "center", marginBottom: 10 }}>
              <div style={{ flex: 1, display: "flex", alignItems: "baseline", gap: 8 }}>
                <Kicker>{t("cl.projects")}</Kicker>
                {projects.length > 0 && <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--color-text-3)", fontVariantNumeric: "tabular-nums" }}>{projects.length}</span>}
              </div>
              <button onClick={() => setAddProj(true)} style={{ display: "flex", alignItems: "center", gap: 4, background: "none", border: "none", color: "var(--color-primary)", fontSize: 13, fontWeight: 600, cursor: "pointer", padding: 0 }}>
                <Icon name="plus" size={15} />{t("cl.addproject")}
              </button>
            </div>
            {projects.length === 0 ? (
              /* Inviting empty state: icon + inline add affordance rather than a
                 bare line, so the dashed card reads as "do this" not "nothing here". */
              <Card dashed pad={20} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 10 }}>
                <div style={{ width: 40, height: 40, borderRadius: 11, background: "var(--color-surface-1)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--color-text-3)" }}><Icon name="briefcase" size={19} /></div>
                <div style={{ fontSize: 13, color: "var(--color-text-3)", textAlign: "center" }}>{t("cl.projects.empty")}</div>
                <button onClick={() => setAddProj(true)} className="q-tap" style={{ display: "inline-flex", alignItems: "center", gap: 5, background: "none", border: "none", color: "var(--color-primary)", fontSize: 13, fontWeight: 600, cursor: "pointer", padding: "2px 4px" }}>
                  <Icon name="plus" size={15} />{t("cl.addproject")}
                </button>
              </Card>
            ) : (
              <Card pad={0}>
                {projects.map((p, i) => {
                  const rate = LIB.projectRate(p);
                  const status = p.status || "active";
                  return (
                    <Q.Row key={p.id} onClick={() => api.go("project", { id: p.id })} last={i === projects.length - 1}>
                      {/* Project rows navigate to a whole sub-screen — the tile gets a
                         1-tier shadow so it reads as a tappable surface, not flat. */}
                      <div style={{ width: 34, height: 34, borderRadius: 8, background: "var(--color-surface-1)", boxShadow: "var(--elev-1)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--color-text-3)", flexShrink: 0 }}><Icon name="briefcase" size={16} /></div>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ fontSize: 14, fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.name}</div>
                        <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--color-gold)", marginTop: 1 }}>{p.budget ? t("cl.proj.budget", { amount: window.fmtMoney(p.budget, c.currency, { cents: false }) }) : rate ? rateLabel(p, rate, c.currency) : "-"}</div>
                      </div>
                      <StatusPill status={PILL[status] || "draft"} label={t("proj.status." + status)} />
                    </Q.Row>
                  );
                })}
              </Card>
            )}
          </div>

          {/* billing history */}
          <div>
            <div style={{ display: "flex", alignItems: "baseline", gap: 7, marginBottom: 10 }}>
              <Kicker>{t("cl.history")}</Kicker>
              {invoices.length > 0 && <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--color-text-3)", fontVariantNumeric: "tabular-nums" }}>{invoices.length}</span>}
            </div>
            {invoices.length === 0 ? (
              <Card pad={20}><div style={{ textAlign: "center", color: "var(--color-text-3)", fontSize: 13 }}>{t("cl.never")}</div></Card>
            ) : (
              <Card pad={0}>
                {invoices.map((inv, i) => {
                  const tot = LIB.totals(inv);
                  const eff = LIB.effectiveStatus(inv, today);
                  return (
                    <Q.Row key={inv.id} onClick={() => api.go("invoice", { id: inv.id })} last={i === invoices.length - 1}>
                      <div style={{ flex: 1 }}>
                        <div style={{ fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 600 }}>{inv.id}</div>
                        <div style={{ fontSize: 12, color: "var(--color-text-3)", marginTop: 1 }}>{window.fmtDate(inv.issued, "short")}</div>
                      </div>
                      <div style={{ textAlign: "right", display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4 }}>
                        <Money amount={LIB.docSign(inv) * tot.total} currency={inv.currency} size={14} cents={false} />
                        <StatusPill status={eff} solid />
                      </div>
                    </Q.Row>
                  );
                })}
              </Card>
            )}
          </div>
        </div>
        {editOpen && <EditClientSheet client={c} onClose={() => setEditOpen(false)} onSave={saveClient} />}
        {addProj && <AddProjectSheet currency={c.currency || db.COMPANY.currency} onClose={() => setAddProj(false)} onAdd={addProject} />}
      </div>
    );
  }

  function ContactRow({ icon, value, sub, mono, multiline, last, href, copy, onAdd }) {
    /* Direct copy feedback: the trailing button flashes copy→check (sage) for
       ~1.1s so the tap reads as "done" on the row itself, not just via toast. */
    const [copied, setCopied] = useState(false);
    const has = value != null && String(value).trim() !== "";
    const doCopy = copy ? (e => { e.stopPropagation(); copy(e); setCopied(true); setTimeout(() => setCopied(false), 1100); }) : undefined;
    /* Empty field → a muted "add" prompt that opens the edit sheet, instead of a
       dead "-" placeholder. Tappable, ≥44px, keyboard-reachable. */
    if (!has) {
      return (
        <div onClick={onAdd} role="button" tabIndex={0} className="q-row"
          onKeyDown={e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onAdd && onAdd(); } }}
          style={{ display: "flex", gap: 12, alignItems: "center", minHeight: 52, padding: "11px 16px", borderBottom: last ? "none" : "1px solid var(--color-border)", cursor: "pointer" }}>
          <div style={{ width: 32, height: 32, borderRadius: 8, background: "var(--color-surface-1)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--color-text-3)", flexShrink: 0 }}><Icon name={icon} size={15} /></div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 600, letterSpacing: "0.10em", textTransform: "uppercase", color: "var(--color-text-3)", marginBottom: 2 }}>{sub}</div>
            <div style={{ fontSize: 14, fontWeight: 500, color: "var(--color-text-3)" }}>{t("g.add")}</div>
          </div>
          <Icon name="plus" size={16} color="var(--color-text-3)" />
        </div>
      );
    }
    /* The label/value area is the primary action when there's an href
       (mailto:/tel:) — a real anchor so it works on a phone; otherwise plain. */
    const Body = (
      <React.Fragment>
        <div style={{ width: 32, height: 32, borderRadius: 8, background: "var(--color-surface-1)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--color-text-3)", flexShrink: 0, marginTop: multiline ? 1 : 0 }}><Icon name={icon} size={15} /></div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 600, letterSpacing: "0.10em", textTransform: "uppercase", color: "var(--color-text-3)", marginBottom: 2 }}>{sub}</div>
          <div style={{ fontSize: 14, fontWeight: 500, color: href ? "var(--color-primary)" : "var(--color-text-1)", fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", whiteSpace: multiline ? "pre-line" : "normal", lineHeight: multiline ? 1.4 : 1.3, wordBreak: "break-word" }}>{value}</div>
        </div>
      </React.Fragment>
    );
    const rowStyle = { display: "flex", gap: 12, alignItems: multiline ? "flex-start" : "center", minHeight: 52, padding: "11px 16px", textDecoration: "none", color: "inherit" };
    return (
      <div style={{ display: "flex", alignItems: "stretch", borderBottom: last ? "none" : "1px solid var(--color-border)" }}>
        {href
          ? <a href={href} className="q-row" style={{ ...rowStyle, flex: 1, minWidth: 0, cursor: "pointer" }}>{Body}</a>
          : <div style={{ ...rowStyle, flex: 1, minWidth: 0 }}>{Body}</div>}
        {doCopy && (
          <button onClick={doCopy} aria-label={sub} title={sub} className="q-tap"
            style={{ flexShrink: 0, alignSelf: "stretch", width: 48, display: "flex", alignItems: "center", justifyContent: "center", background: "transparent", border: "none", cursor: "pointer", color: "var(--color-text-3)" }}>
            <span className={copied ? "q-pop" : ""} style={{ display: "flex" }}><Icon name={copied ? "check" : "copy"} size={16} color={copied ? "var(--color-primary)" : "var(--color-text-3)"} /></span>
          </button>
        )}
      </div>
    );
  }

  window.QScreens = Object.assign(window.QScreens || {}, { Clients, ClientDetail });
})();
