/* screen-expenses.jsx — Expenses list + detail + add/scan sheet (mock OCR)
 * → window.QScreens.{Expenses, ExpenseDetail, ExpenseSheet}
 */
(function () {
  const { useState, useEffect, useRef } = React;
  const Q = window.Q, LIB = window.LIB, Icons = window.Icons;
  const { Icon } = Icons;
  const { AppBar, Card, Money, Kicker, Button, Badge, EmptyState, IconButton, Sheet, Field, Input, Select, Segmented, Toggle, EntityLink, ReceiptThumb, ReceiptViewer, Sparkle } = Q;
  const t = window.t;

  /* Expense categories now flow from db.config.expenseCats (single source of
     truth, shared with ai-backend.js via window.NITA_CATS). expenseCats(db)
     reads the live config; the module-scope helpers fall back to the seed
     defaults so callers without a db in scope (list rows) still resolve. */
  const DEFAULT_CATS = (window.DATA && window.DATA.CONFIG && window.DATA.CONFIG.expenseCats) ||
    [{ id: "meals", label: "exp.cat.meals", icon: "utensils" }, { id: "travel", label: "exp.cat.travel", icon: "plane" },
     { id: "transport", label: "exp.cat.transport", icon: "car" }, { id: "supplies", label: "exp.cat.supplies", icon: "paperclip" },
     { id: "equipment", label: "exp.cat.equipment", icon: "laptop" }, { id: "software", label: "exp.cat.software", icon: "globe" },
     { id: "other", label: "exp.cat.other", icon: "tag" }];
  const expenseCats = db => (db && db.config && db.config.expenseCats) || window.NITA_CAT_DEFS || DEFAULT_CATS;
  const PAY_METHODS = ["card", "cash", "transfer"];
  /* Tax rates flow from db.config.taxes (same source as the invoice editor /
     Settings tax manager) so a user who edits their rates sees them here too.
     Always offer 0% (no-VAT expense), de-dupe, sort ascending. Falls back to
     the classic FR set when config is unavailable (module-scope callers). */
  const FALLBACK_RATES = [0, 5.5, 10, 20];
  const taxRates = db => {
    const cfg = (db && db.config && Array.isArray(db.config.taxes)) ? db.config.taxes : null;
    if (!cfg || !cfg.length) return FALLBACK_RATES;
    const set = new Set([0]);
    cfg.forEach(tx => { const r = Number(tx.rate); if (isFinite(r) && r >= 0) set.add(r); });
    return Array.from(set).sort((a, b) => a - b);
  };
  /* Deductible VAT on a TTC expense — mirrors lib.taxReport's input formula
     (amount - amount/(1+rate/100)). Money you can reclaim, not money spent. */
  const deductibleVat = (amount, rate) => {
    const a = Number(amount) || 0, r = Number(rate) || 0;
    return r > 0 ? a - a / (1 + r / 100) : 0;
  };
  const catLabel = c => { const m = expenseCats().find(x => x.id === c); return t(m ? m.label : "exp.cat.other"); };
  /* receipt-photo-labeled-pdf: every stored receipt.src is a rasterised image
     (SVG sample or JPEG camera capture) — never a real PDF — so a ".pdf"/".doc"
     name misrepresents the artifact. Strip the misleading document extension;
     keep the meaningful merchant-derived stem. Real image names (.jpg/.png) and
     extensionless names pass through unchanged. */
  const receiptLabel = (receipt) => {
    const raw = (receipt && receipt.name || "").trim();
    if (!raw) return t("receipt.title");
    return raw.replace(/\.(pdf|docx?|xlsx?)$/i, "");
  };
  const catIcon = c => { const m = expenseCats().find(x => x.id === c); return (m && m.icon) || "tag"; };
  const STATUS_TONE = { unbilled: "accent", billed: "success", personal: "neutral" };
  const ExpBadge = ({ status }) => <Badge tone={STATUS_TONE[status] || "neutral"}>{t("exp.status." + (status || "personal"))}</Badge>;

  /* The ONLY sage cue on these screens: Nita's OCR confidence (G6). */
  function OcrCue({ confidence, style }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 6, ...style }}>
        <Sparkle size={12} />
        <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, fontWeight: 600, color: "var(--color-primary)" }}>
          {t("exp.scanned", { confidence: Math.round((confidence || 0) * 100) })}
        </span>
      </div>
    );
  }

  /* ── List ────────────────────────────────────────────────────── */
  function Expenses({ api }) {
    const { db, setDb } = api;
    /* Publish the live categories so ai-backend.js (plain JS, no db access)
       validates receipts against the user's actual categories, and so the
       module-scope catLabel/catIcon (called without db) resolve custom rows. */
    try { const live = expenseCats(db); window.NITA_CAT_DEFS = live; window.NITA_CATS = live.map(c => c.id); } catch (e) {}
    const [query, setQuery] = useState("");
    const [tab, setTab] = useState("all");
    const cur = db.COMPANY.currency;
    const sum = LIB.expenseSummary(db);

    let list = db.EXPENSES.slice().sort((a, b) => new Date(b.date) - new Date(a.date));
    if (tab === "tobill") list = list.filter(e => e.status === "unbilled" && e.billable);
    else if (tab === "billed") list = list.filter(e => e.status === "billed");
    else if (tab === "personal") list = list.filter(e => e.status === "personal" || !e.billable);
    if (query.trim()) {
      const q = query.toLowerCase();
      list = list.filter(e => (e.merchant || "").toLowerCase().includes(q) || (e.note || "").toLowerCase().includes(q) || catLabel(e.category).toLowerCase().includes(q));
    }
    const isEmpty = db.EXPENSES.length === 0;

    return (
      <div style={{ paddingBottom: 4 }}>
        <AppBar large title={t("exp.title")} kicker={t("exp.sub", { count: db.EXPENSES.length })}
          right={<IconButton name="plus" label={t("exp.new")} onClick={() => api.openExpenseSheet()} />} />
        <div style={{ padding: "6px 16px 0", display: "flex", flexDirection: "column", gap: 12 }}>

          {/* to-bill summary (terracotta = needs your attention) */}
          {sum.toBillCount > 0 && (
            <div className="q-fade-up">
              <Card dashed pad={14} onClick={() => setTab("tobill")} style={{ cursor: "pointer" }}>
                <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 12 }}>
                  <div>
                    <Kicker>{t("exp.tab.tobill")}</Kicker>
                    <div style={{ fontSize: 11.5, fontFamily: "var(--font-mono)", fontWeight: 600, color: "var(--color-accent)", marginTop: 8 }}>
                      {t("home.expenses.tobill", { count: sum.toBillCount })}
                    </div>
                  </div>
                  <Q.AnimatedNumber amount={sum.toBillTotal} currency={cur} size={19} />
                </div>
              </Card>
            </div>
          )}

          {!isEmpty && (
            <label style={{ display: "flex", alignItems: "center", gap: 9, 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("exp.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, height: "100%", border: "none", background: "transparent", outline: "none", fontSize: 14.5, 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>
          )}

          {!isEmpty && (
            <Segmented value={tab} onChange={setTab} options={[
              { value: "all", label: t("exp.tab.all") },
              { value: "tobill", label: t("exp.tab.tobill"), count: sum.toBillCount, danger: sum.toBillCount > 0 },
              { value: "billed", label: t("exp.tab.billed") },
              { value: "personal", label: t("exp.tab.personal") },
            ]} />
          )}

          {isEmpty ? (
            <EmptyState icon="receipt" title={t("exp.empty.title")} body={t("exp.empty.body")}
              action={<Button variant="primary" size="md" icon="scan" onClick={() => api.openExpenseSheet({ mode: "scan" })}>{t("exp.empty.cta")}</Button>} />
          ) : list.length === 0 ? (
            <EmptyState icon="receipt" title={t("g.nomatch")} body={t("g.trysearch")} />
          ) : (
            <Card pad={0} style={{ overflow: "hidden" }}>
              {list.map((e, i) => {
                const actions = [];
                if (e.status === "unbilled" && e.billable && e.clientId) actions.push({
                  icon: "invoice", label: t("exp.bill"), tone: "accent",
                  onPress: () => api.openManual({ clientId: e.clientId, projectId: e.projectId || null, items: [LIB.expenseToLine(e)] }),
                });
                actions.push({
                  icon: "trash", label: t("g.delete"), tone: "error",
                  onPress: () => { setDb(d => ({ ...d, EXPENSES: d.EXPENSES.filter(x => x.id !== e.id) })); Q.toast(t("toast.expenseDeleted"), "trash"); },
                });
                return (
                <Q.SwipeRow key={e.id} actions={actions} onClick={() => api.go("expense", { id: e.id })} last={i === list.length - 1}>
                  {e.receipt
                    ? <ReceiptThumb receipt={e.receipt} size={34} onClick={ev => { if (ev && ev.stopPropagation) ev.stopPropagation(); api.go("expense", { id: e.id }); }} />
                    : <div style={{ width: 38, height: 38, borderRadius: 11, background: "var(--color-surface-1)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--color-text-3)", flexShrink: 0 }}>
                        <Icon name={catIcon(e.category)} size={17} />
                      </div>}
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 14.5, fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{e.merchant}</div>
                    <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--color-text-3)", marginTop: 2 }}>{window.fmtDate(e.date, "short")} · {catLabel(e.category)}</div>
                    {e.status === "unbilled" && e.billable && !e.clientId && (
                      <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 3, fontSize: 11, fontWeight: 600, color: "var(--color-accent)" }}>
                        <Icon name="alert" size={12} style={{ flexShrink: 0 }} />
                        <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{t("exp.billable.unassigned")}</span>
                      </div>
                    )}
                  </div>
                  <div style={{ textAlign: "right", display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 5, flexShrink: 0 }}>
                    <Money amount={e.amount} currency={e.currency || cur} size={14} />
                    <ExpBadge status={e.status} />
                  </div>
                </Q.SwipeRow>
                );
              })}
            </Card>
          )}
        </div>
      </div>
    );
  }

  /* ── Detail ──────────────────────────────────────────────────── */
  function MetaCell({ label, value }) {
    return (
      <div style={{ flex: 1 }}>
        <Kicker style={{ fontSize: 9.5 }}>{label}</Kicker>
        <div style={{ fontSize: 13.5, fontWeight: 500, marginTop: 3 }}>{value}</div>
      </div>
    );
  }
  function BillRow({ label, children, last }) {
    return (
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, padding: "13px 16px", borderBottom: last ? "none" : "1px solid var(--color-border)" }}>
        <span style={{ fontSize: 13, color: "var(--color-text-2)", flexShrink: 0 }}>{label}</span>
        {children}
      </div>
    );
  }

  function ExpenseDetail({ api, id }) {
    const { db, setDb } = api;
    const [viewer, setViewer] = useState(false);
    const e = db.EXPENSES.find(x => x.id === id);
    if (!e) return <div><AppBar title={t("exp.detail.title")} onBack={() => api.back()} /><EmptyState icon="receipt" title={t("g.notfound")} body={t("g.trysearch")} /></div>;
    const client = e.clientId ? LIB.getClient(db, e.clientId) : null;
    const proj = e.projectId ? LIB.getProject(db, e.projectId) : null;
    const cur = e.currency || db.COMPANY.currency;

    const del = () => {
      api.back();
      setDb(d => ({ ...d, EXPENSES: d.EXPENSES.filter(x => x.id !== id) }));
      Q.toast(t("toast.expenseDeleted"), "trash");
    };
    const billNow = () => api.openManual({ clientId: e.clientId, projectId: e.projectId || null, items: [LIB.expenseToLine(e)] });

    return (
      <div style={{ paddingBottom: 40 }}>
        <AppBar title={t("exp.detail.title")} onBack={() => api.back()}
          right={<>
            <IconButton name="edit" label={t("g.edit")} onClick={() => api.openExpenseSheet({ edit: e })} />
            <IconButton name="trash" label={t("exp.delete")} onClick={del} />
          </>} />
        <div style={{ padding: "4px 16px 0", display: "flex", flexDirection: "column", gap: 16 }}>

          {/* hero */}
          <Card pad={20}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
              <ExpBadge status={e.status} />
              <span style={{ flex: 1 }} />
              <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontFamily: "var(--font-mono)", fontSize: 10.5, color: "var(--color-text-3)" }}>
                <Icon name={catIcon(e.category)} size={13} />{catLabel(e.category)}
              </span>
            </div>
            <div style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 23, letterSpacing: "-0.01em", lineHeight: 1.15 }}>{e.merchant}</div>
            <div style={{ fontSize: 12.5, color: "var(--color-text-3)", marginTop: 3, fontFamily: "var(--font-mono)" }}>{window.fmtDate(e.date)}</div>
            {e.note && <div style={{ fontSize: 13.5, color: "var(--color-text-2)", marginTop: 8, lineHeight: 1.45 }}>{e.note}</div>}
            <div style={{ marginTop: 14 }}><Money amount={e.amount} currency={cur} size={28} /></div>
            <div style={{ display: "flex", gap: 14, marginTop: 16, paddingTop: 14, borderTop: "1px solid var(--color-border)" }}>
              <MetaCell label={t("exp.field.payment")} value={t("exp.pay." + (e.paymentMethod || "card"))} />
              <MetaCell label={t("exp.field.tax")} value={(e.taxRate != null ? e.taxRate : 0) + "%"} />
            </div>
            {/* Deductible-VAT framing: the VAT inside a business expense is money
               you can reclaim, not money spent. Personal expenses don't qualify
               (mirrors lib.taxReport's exclusion), so only frame business ones. */}
            {e.status !== "personal" && deductibleVat(e.amount, e.taxRate) > 0 && (
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 12, padding: "9px 12px", borderRadius: 10, background: "color-mix(in oklab, var(--color-gold) 9%, transparent)", border: "1px solid color-mix(in oklab, var(--color-gold) 24%, transparent)" }}>
                <Icon name="receipt" size={14} color="var(--color-gold)" style={{ flexShrink: 0 }} />
                <span style={{ flex: 1, fontSize: 12, color: "var(--color-text-2)", lineHeight: 1.35 }}>{t("tax.input")}</span>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 700, color: "var(--color-gold)", whiteSpace: "nowrap" }}>
                  {window.fmtMoney(deductibleVat(e.amount, e.taxRate), cur)}
                </span>
              </div>
            )}
            {e.ocr && <OcrCue confidence={e.ocr.confidence} style={{ marginTop: 12 }} />}
          </Card>

          {/* receipt */}
          <div>
            <Kicker style={{ marginBottom: 10 }}>{t("exp.receipt")}</Kicker>
            {e.receipt ? (
              <Card pad={14} onClick={() => setViewer(true)} style={{ cursor: "pointer" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 13 }}>
                  <ReceiptThumb receipt={e.receipt} size={46} onClick={() => setViewer(true)} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{receiptLabel(e.receipt)}</div>
                    <div style={{ fontSize: 12, color: "var(--color-text-3)", marginTop: 2 }}>{t("exp.attached")}</div>
                  </div>
                  <Icon name="eye" size={17} color="var(--color-text-3)" />
                </div>
              </Card>
            ) : (
              <Card dashed pad={18}>
                <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 8, color: "var(--color-text-3)", fontSize: 13.5 }}>
                  <Icon name="receipt" size={16} />{t("exp.noreceipt")}
                </div>
              </Card>
            )}
          </div>

          {/* billing */}
          {e.billable && (() => {
            const hasProj = !!proj;
            const hasMarkup = (Number(e.markupPct) || 0) > 0;
            const isBilled = e.status === "billed" && !!e.invoiceId;
            return (
            <div>
              <Kicker style={{ marginBottom: 10 }}>{t("exp.bill")}</Kicker>
              <Card pad={0}>
                {client ? (
                  <BillRow label={t("exp.field.client")} last={!hasProj && !hasMarkup && !isBilled}>
                    <EntityLink kind="client" name={client.name} onClick={() => api.go("client", { id: client.id })} />
                  </BillRow>
                ) : (
                  <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "13px 16px", borderBottom: (!hasProj && !hasMarkup && !isBilled) ? "none" : "1px solid var(--color-border)" }}>
                    <Icon name="alert" size={15} color="var(--color-accent)" />
                    <span style={{ flex: 1, fontSize: 13, color: "var(--color-accent)", fontWeight: 500, lineHeight: 1.4 }}>{t("exp.billable.unassigned")}</span>
                    <Button variant="secondary" size="sm" onClick={() => api.openExpenseSheet({ edit: e })}>{t("g.edit")}</Button>
                  </div>
                )}
                {hasProj && (
                  <BillRow label={t("exp.field.project")} last={!hasMarkup && !isBilled}>
                    <EntityLink kind="project" name={proj.project.name} onClick={() => api.go("project", { id: e.projectId })} />
                  </BillRow>
                )}
                {hasMarkup && (
                  <BillRow label={t("exp.field.markup")} last={!isBilled}>
                    <span style={{ fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 600 }}>+{e.markupPct}%</span>
                  </BillRow>
                )}
                {isBilled && (
                  <BillRow label={t("exp.status.billed")} last>
                    <EntityLink kind="invoice" name={e.invoiceId} mono onClick={() => api.goTab("invoices", "invoice", { id: e.invoiceId })} />
                  </BillRow>
                )}
              </Card>
              {e.status === "unbilled" && client && (
                <Button variant="primary" size="lg" full icon="invoice" style={{ marginTop: 12 }} onClick={billNow}>{t("exp.bill.cta")}</Button>
              )}
              {e.status === "billed" && e.invoiceId && (
                <Button variant="secondary" size="md" full icon="eye" style={{ marginTop: 12 }} onClick={() => api.goTab("invoices", "invoice", { id: e.invoiceId })}>{t("exp.viewinvoice")}</Button>
              )}
            </div>
            );
          })()}
        </div>

        <ReceiptViewer open={viewer} receipt={e.receipt} onClose={() => setViewer(false)}
          title={e.merchant} meta={window.fmtDate(e.date) + " · " + window.fmtMoney(e.amount, cur)} />
      </div>
    );
  }

  /* ── Add / scan / edit sheet (app-level overlay) ─────────────── */
  function ExpenseSheet({ api, seed, onClose }) {
    const { db, setDb, today } = api;
    const cur = db.COMPANY.currency;
    const sym = (LIB.currencyMap(db)[cur] || {}).symbol;
    const cats = expenseCats(db);
    const editExp = (seed && seed.edit) || null;
    const [view, setView] = useState(editExp ? "form" : seed && seed.mode === "scan" ? "pick" : seed && seed.mode === "manual" ? "form" : "choose");
    const [f, setF] = useState(() => editExp ? {
      merchant: editExp.merchant || "", date: editExp.date || today, amount: editExp.amount != null ? Number(editExp.amount).toFixed(2) : "",
      taxRate: editExp.taxRate != null ? editExp.taxRate : db.COMPANY.taxRate, category: editExp.category || "other",
      note: editExp.note || "", paymentMethod: editExp.paymentMethod || "card", billable: !!editExp.billable,
      clientId: editExp.clientId || "", projectId: editExp.projectId || "", markupPct: String(editExp.markupPct || 0),
      receipt: editExp.receipt || null, ocr: editExp.ocr || null,
    } : {
      merchant: "", date: today, amount: "", taxRate: db.COMPANY.taxRate, category: "other",
      note: "", paymentMethod: "card", billable: false, clientId: "", projectId: "", markupPct: "0",
      receipt: null, ocr: null,
    });
    const up = patch => setF(x => ({ ...x, ...patch }));
    const timer = useRef(null);
    useEffect(() => () => clearTimeout(timer.current), []);
    /* The receipt currently under the scanner — drives the scan-line preview
       so "Nita is reading your receipt" shows the actual image being read. */
    const [scanReceipt, setScanReceipt] = useState(null);

    const samples = (window.DATA && window.DATA.SAMPLE_RECEIPTS) || [];
    const scan = (rid) => {
      const sample = samples.find(s => s.id === rid);
      setScanReceipt((sample && sample.receipt) || null);
      setView("scanning");
      timer.current = setTimeout(() => {
        const res = window.AIPARSE.scanReceipt(rid);
        if (res) up({
          merchant: res.merchant, date: res.date, amount: Number(res.amount).toFixed(2), taxRate: res.taxRate,
          category: res.category, receipt: res.receipt, ocr: { confidence: res.confidence, scannedAt: res.scannedAt },
        });
        setView("form");
      }, 1250);
    };

    /* ── Real photo path ─────────────────────────────────────────
       Hidden <input type="file" capture="environment"> (camera on phones,
       picker for uploads) → FileReader.readAsDataURL → canvas downscale
       (max 1000px long edge, JPEG 0.8) → the dataURL is stored on the
       expense as its receipt. Local AI mode reads the photo through
       NITA_AI.parseReceipt; demo mode (or a failed local call) keeps the
       simulated prefill under the real image. */
    const rates = taxRates(db);
    const snapTax = v => {
      const n = Number(v);
      if (!isFinite(n) || n < 0) return null;
      return rates.reduce((b, r) => (Math.abs(r - n) < Math.abs(b - n) ? r : b), rates[0]);
    };
    const isoDate = s => { const d = new Date(String(s || "")); return isNaN(d.getTime()) ? today : d.toISOString(); };
    /* ISO/any → YYYY-MM-DD for the native <input type="date"> (local-date safe). */
    const dateInputValue = s => {
      const d = new Date(String(s || ""));
      if (isNaN(d.getTime())) return "";
      const p = n => String(n).padStart(2, "0");
      return d.getFullYear() + "-" + p(d.getMonth() + 1) + "-" + p(d.getDate());
    };

    const applyDemoOcr = (receipt) => {
      timer.current = setTimeout(() => {
        const res = window.AIPARSE.scanReceipt();
        if (res) up({
          merchant: res.merchant, date: res.date, amount: Number(res.amount).toFixed(2), taxRate: res.taxRate,
          category: res.category, receipt, ocr: { confidence: res.confidence, scannedAt: res.scannedAt },
        });
        else up({ receipt });
        setView("form");
      }, 900);
    };

    const handleCapture = (dataUrl, name) => {
      const receipt = { id: LIB.newId("rc"), name: name || "", src: dataUrl, w: 0, h: 0 };
      const cfg = window.NITA_AI && window.NITA_AI.getConfig();
      if (cfg && cfg.mode === "local" && window.NITA_AI.parseReceipt) {
        const model = (cfg.model || "Ollama").split(":")[0];
        window.NITA_AI.parseReceipt(dataUrl).then(res => {
          if (res && (String(res.vendor || "").trim() || Number(res.total) > 0)) {
            const tax = res.vat != null ? snapTax(res.vat) : null;
            up({
              merchant: String(res.vendor || "").trim() || f.merchant,
              date: res.date ? isoDate(res.date) : today,
              amount: Number(res.total) > 0 ? Number(res.total).toFixed(2) : f.amount,
              taxRate: tax != null ? tax : f.taxRate,
              category: cats.some(c => c.id === res.category) ? res.category : "other",
              receipt, ocr: { confidence: 0.9, scannedAt: new Date().toISOString() },
            });
            Q.toast(t("exp.scan.readby", { model }), "check");
            setView("form");
          } else {
            Q.toast(t("ai.local.fail"), "alert", "var(--color-accent)");
            applyDemoOcr(receipt);
          }
        }).catch(() => {
          Q.toast(t("ai.local.fail"), "alert", "var(--color-accent)");
          applyDemoOcr(receipt);
        });
      } else applyDemoOcr(receipt);
    };

    const onFile = (ev) => {
      const file = ev.target.files && ev.target.files[0];
      ev.target.value = "";
      if (!file) return;
      setView("scanning");
      const reader = new FileReader();
      reader.onerror = () => setView("pick");
      reader.onload = () => {
        const src = String(reader.result || "");
        const img = new Image();
        img.onerror = () => setView("pick");
        img.onload = () => {
          let out = src;
          try {
            const MAX = 1000;
            const k = Math.min(1, MAX / Math.max(img.width || 1, img.height || 1));
            const cv = document.createElement("canvas");
            cv.width = Math.max(1, Math.round((img.width || 1) * k));
            cv.height = Math.max(1, Math.round((img.height || 1) * k));
            cv.getContext("2d").drawImage(img, 0, 0, cv.width, cv.height);
            out = cv.toDataURL("image/jpeg", 0.8);
          } catch (e) {}
          setScanReceipt({ src: out });
          handleCapture(out, file.name);
        };
        img.src = src;
      };
      reader.readAsDataURL(file);
    };

    const selClient = f.clientId ? LIB.getClient(db, f.clientId) : null;
    const projects = (selClient && selClient.projects) || [];

    const save = () => {
      const base = {
        merchant: f.merchant.trim(), date: f.date, amount: Number(f.amount) || 0,
        currency: editExp ? (editExp.currency || cur) : cur,
        taxRate: Number(f.taxRate) || 0, category: f.category, note: f.note.trim(),
        paymentMethod: f.paymentMethod, billable: f.billable, markupPct: Number(f.markupPct) || 0,
        clientId: f.billable ? (f.clientId || null) : null,
        projectId: f.billable ? (f.projectId || null) : null,
        receipt: f.receipt || null, ocr: f.ocr || null,
      };
      if (editExp) {
        const status = editExp.status === "billed" ? "billed" : f.billable ? "unbilled" : "personal";
        setDb(d => ({ ...d, EXPENSES: d.EXPENSES.map(x => x.id === editExp.id ? { ...x, ...base, status } : x) }));
        Q.toast(t("toast.expenseUpdated"), "check");
      } else {
        const nx = { id: LIB.newId("exp"), ...base, status: f.billable ? "unbilled" : "personal" };
        const act = f.ocr ? LIB.activityEvent("expense.scanned", { merchant: base.merchant }, nx.id) : null;
        setDb(d => ({ ...d, EXPENSES: [nx, ...d.EXPENSES], ACTIVITY: act ? [act, ...d.ACTIVITY] : d.ACTIVITY }));
        Q.toast(t("toast.expenseAdded"), "check");
      }
      onClose();
    };

    const titles = {
      choose: t("exp.new"),
      pick: t("exp.scan"),
      scanning: t("exp.scan"),
      form: editExp ? t("g.edit") : t("exp.new"),
    };
    const canSave = f.merchant.trim() && (Number(f.amount) || 0) > 0;

    return (
      <Sheet open onClose={onClose} height={view === "form" ? "92%" : "auto"} title={titles[view]}
        footer={view === "form" ? <Button variant="primary" size="lg" full icon="check" disabled={!canSave} onClick={save}>{editExp ? t("g.save") : t("exp.save")}</Button> : undefined}>

        {view === "choose" && (
          <div style={{ padding: "2px 16px 26px", display: "flex", flexDirection: "column", gap: 10 }}>
            {/* scan hero — Nita's OCR is the AI path (sage + sparkle) */}
            <button onClick={() => setView("pick")} className="q-tap" style={{ textAlign: "left", cursor: "pointer", border: "1.4px solid var(--color-primary)", borderRadius: 16, padding: 16, background: "var(--color-primary-muted)", display: "flex", alignItems: "center", gap: 14 }}>
              <div style={{ width: 46, height: 46, borderRadius: 13, background: "var(--color-primary)", color: "var(--color-primary-fg)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, boxShadow: "0 4px 12px hsl(155 32% 34% / 0.3)" }}>
                <Sparkle size={13} color="var(--color-primary-fg)" />
                <Icon name="camera" size={19} style={{ marginLeft: -2 }} />
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 16, fontWeight: 700, color: "var(--color-text-1)" }}>{t("exp.scan")}</div>
                <div style={{ fontSize: 13, color: "var(--color-text-2)", marginTop: 2 }}>{t("exp.scan.sub")}</div>
              </div>
            </button>
            {/* manual */}
            <button onClick={() => setView("form")} className="q-tap" style={{ textAlign: "left", cursor: "pointer", border: "1.4px solid var(--color-border-strong)", borderRadius: 14, padding: "14px 16px", background: "var(--color-surface-0)", display: "flex", alignItems: "center", gap: 13 }}>
              <div style={{ width: 40, height: 40, borderRadius: 11, background: "var(--color-surface-1)", color: "var(--color-text-2)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
                <Icon name="keyboard" size={18} />
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 15, fontWeight: 600, color: "var(--color-text-1)" }}>{t("exp.manual")}</div>
                <div style={{ fontSize: 12.5, color: "var(--color-text-3)", marginTop: 1 }}>{t("exp.manual.sub")}</div>
              </div>
              <Icon name="chevronRight" size={18} color="var(--color-text-3)" />
            </button>
          </div>
        )}

        {view === "pick" && (
          <div style={{ padding: "2px 16px 26px" }}>
            {/* real capture: label-wrapped hidden file input (no programmatic click) */}
            <label className="q-tap" style={{ position: "relative", display: "flex", alignItems: "center", gap: 13, cursor: "pointer", border: "1.4px solid var(--color-primary)", borderRadius: 14, padding: "14px 16px", background: "var(--color-primary-muted)", marginBottom: 16 }}>
              <input type="file" accept="image/*" capture="environment" onChange={onFile}
                onFocus={e => { e.target.parentElement.style.boxShadow = "0 0 0 3px var(--color-primary-muted)"; e.target.parentElement.style.borderColor = "var(--color-primary)"; }}
                onBlur={e => { e.target.parentElement.style.boxShadow = "none"; e.target.parentElement.style.borderColor = "var(--color-primary)"; }}
                style={{ position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap", border: 0 }} />
              <div style={{ width: 40, height: 40, borderRadius: 11, background: "var(--color-primary)", color: "var(--color-primary-fg)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
                <Icon name="camera" size={18} />
              </div>
              <div style={{ flex: 1, fontSize: 15, fontWeight: 600, color: "var(--color-text-1)" }}>{t("exp.scan.upload")}</div>
              <Icon name="chevronRight" size={18} color="var(--color-text-3)" />
            </label>
            <Kicker style={{ marginBottom: 12 }}>{t("exp.scan.choose")}</Kicker>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
              {samples.map(s => (
                <div key={s.id} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 6, minWidth: 0 }}>
                  <ReceiptThumb receipt={s.receipt} size={86} onClick={() => scan(s.id)} ariaLabel={t("exp.scan") + ": " + s.merchant} />
                  <span style={{ fontFamily: "var(--font-mono)", fontSize: 9.5, color: "var(--color-text-3)", maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{s.merchant}</span>
                </div>
              ))}
            </div>
          </div>
        )}

        {view === "scanning" && (
          <div style={{ padding: "30px 24px 56px", display: "flex", flexDirection: "column", alignItems: "center", gap: 18 }}>
            {/* The receipt being read — a sage scan-line sweeps over it so the
               wait reads as Nita parsing THIS image, not a generic spinner. */}
            {scanReceipt && scanReceipt.src ? (
              <div style={{ position: "relative", width: 124, borderRadius: "var(--radius-lg)", overflow: "hidden", border: "1.4px solid var(--color-primary)", boxShadow: "0 10px 30px hsl(155 32% 34% / 0.22)" }}>
                <img src={scanReceipt.src} alt="" style={{ display: "block", width: "100%", maxHeight: 168, objectFit: "cover", objectPosition: "top" }} />
                {/* sage scan sweep (reuses q-shimmer's X drift; reduced-motion-safe globally) */}
                <div style={{ position: "absolute", inset: 0, pointerEvents: "none",
                  backgroundImage: "linear-gradient(100deg, transparent 38%, color-mix(in oklab, var(--color-primary) 34%, transparent) 49%, color-mix(in oklab, var(--color-primary) 60%, transparent) 50%, color-mix(in oklab, var(--color-primary) 34%, transparent) 51%, transparent 62%)",
                  backgroundSize: "220% 100%", animation: "q-shimmer 1.25s linear infinite" }} />
                <div style={{ position: "absolute", top: 8, right: 8, width: 24, height: 24, borderRadius: 999, background: "var(--color-primary)", display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 2px 8px hsl(155 32% 34% / 0.4)" }}>
                  <Sparkle size={13} color="var(--color-primary-fg)" style={{ animation: "q-pulse 1.6s ease-in-out infinite" }} />
                </div>
              </div>
            ) : (
              <div style={{ position: "relative", width: 56, height: 56 }}>
                <div style={{ position: "absolute", inset: 0, border: "3px solid var(--color-surface-2)", borderTopColor: "var(--color-primary)", borderRadius: "50%", animation: "q-spin 0.8s linear infinite" }} />
                <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }}><Sparkle size={18} /></div>
              </div>
            )}
            <div className="q-shimmer-text" style={{ fontSize: 14.5, fontWeight: 600 }}>{t("exp.scanning")}</div>
          </div>
        )}

        {view === "form" && (
          <div className={f.ocr && !editExp ? "q-stagger" : undefined} style={{ padding: "4px 16px 20px", display: "flex", flexDirection: "column", gap: 14 }}>
            {/* OCR result cue (sage, sparkle) */}
            {f.ocr && (
              <div className="q-fade" style={{ display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", background: "var(--color-primary-muted)", border: "1.4px solid var(--color-primary)", borderRadius: 12 }}>
                {f.receipt && <ReceiptThumb receipt={f.receipt} size={32} />}
                <div style={{ flex: 1, minWidth: 0 }}>
                  <OcrCue confidence={f.ocr.confidence} />
                  <div style={{ fontSize: 12, color: "var(--color-text-2)", marginTop: 3, lineHeight: 1.4 }}>{t("exp.review")}</div>
                </div>
                {!editExp && (
                  <button onClick={() => setView("pick")} className="q-tap" style={{ border: "none", background: "transparent", color: "var(--color-text-2)", fontSize: 12, fontWeight: 600, cursor: "pointer", padding: "12px 6px", margin: "-8px -4px", flexShrink: 0 }}>
                    {t("exp.scan.retake")}
                  </button>
                )}
              </div>
            )}

            <Field label={t("exp.field.merchant")}><Input value={f.merchant} onChange={e => up({ merchant: e.target.value })} placeholder={t("exp.field.merchant.ph")} /></Field>
            <div style={{ display: "flex", gap: 12 }}>
              <Field label={t("exp.field.date")} style={{ flex: 1 }}>
                <Input type="date" mono value={dateInputValue(f.date)} max={dateInputValue(today)}
                  onChange={e => up({ date: e.target.value ? isoDate(e.target.value) : today })} />
              </Field>
              <Field label={t("exp.field.amount")} style={{ flex: 1 }}>
                <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={f.amount} onChange={e => up({ amount: e.target.value.replace(/[^\d.]/g, "") })} mono inputMode="decimal" style={{ paddingLeft: 34 }} />
                </div>
              </Field>
            </div>
            <div style={{ display: "flex", gap: 12 }}>
              <Field label={t("exp.field.tax")} style={{ flex: 1 }}>
                <Select value={String(f.taxRate)} onChange={e => up({ taxRate: Number(e.target.value) })}
                  options={(rates.indexOf(Number(f.taxRate)) < 0 && f.taxRate != null && f.taxRate !== "" ? rates.concat(Number(f.taxRate)).sort((a, b) => a - b) : rates).map(r => ({ value: String(r), label: r + "%" }))} />
              </Field>
              <Field label={t("exp.field.category")} style={{ flex: 1.4 }}>
                <Select value={f.category} onChange={e => up({ category: e.target.value })}
                  options={cats.map(c => ({ value: c.id, label: t(c.label) }))} />
              </Field>
            </div>
            <Field label={t("exp.field.note")}><Input value={f.note} onChange={e => up({ note: e.target.value })} placeholder={t("exp.field.note.ph")} /></Field>
            <div>
              <Kicker style={{ marginBottom: 8 }}>{t("exp.field.payment")}</Kicker>
              <Segmented value={f.paymentMethod} onChange={v => up({ paymentMethod: v })}
                options={PAY_METHODS.map(m => ({ value: m, label: t("exp.pay." + m) }))} />
            </div>

            {/* billable routing */}
            <div style={{ border: "1.4px solid var(--color-border-strong)", borderRadius: 12, overflow: "hidden" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "13px 14px" }}>
                <div style={{ flex: 1, fontSize: 14, fontWeight: 600 }}>{t("exp.field.billable")}</div>
                <Toggle checked={f.billable} onChange={v => up({ billable: v })} label={t("exp.field.billable")} />
              </div>
              {f.billable && (
                <div className="q-fade" style={{ padding: "12px 14px 14px", borderTop: "1px solid var(--color-border)", display: "flex", flexDirection: "column", gap: 12 }}>
                  <Field label={t("exp.field.client")}>
                    <Select value={f.clientId} onChange={e => up({ clientId: e.target.value, projectId: "" })}
                      options={[{ value: "", label: t("g.none") }].concat(db.CLIENTS.map(c => ({ value: c.id, label: c.name })))} />
                  </Field>
                  {projects.length > 0 && (
                    <Field label={t("exp.field.project")}>
                      <Select value={f.projectId} onChange={e => up({ projectId: e.target.value })}
                        options={[{ value: "", label: t("g.none") }].concat(projects.map(p => ({ value: p.id, label: p.name })))} />
                    </Field>
                  )}
                  <Field label={t("exp.field.markup")} hint={t("exp.markup.hint")}>
                    <div style={{ position: "relative" }}>
                      <Input value={f.markupPct} onChange={e => up({ markupPct: e.target.value.replace(/[^\d.]/g, "") })} mono inputMode="decimal" style={{ paddingRight: 32 }} />
                      <span style={{ position: "absolute", right: 13, top: "50%", transform: "translateY(-50%)", fontFamily: "var(--font-mono)", fontSize: 13, color: "var(--color-text-3)", pointerEvents: "none" }}>%</span>
                    </div>
                  </Field>
                  {!f.clientId && (
                    <div style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--color-accent)", fontWeight: 500 }}>
                      <Icon name="alert" size={13} />{t("exp.billable.unassigned")}
                    </div>
                  )}
                </div>
              )}
            </div>
          </div>
        )}
      </Sheet>
    );
  }

  window.QScreens = Object.assign(window.QScreens || {}, { Expenses, ExpenseDetail, ExpenseSheet });
})();
