/* Modals — Add transaction, Connect account, Transaction detail, Export */

const AddTransactionModal = ({ open, onClose, onAdd }) => {
  const [type, setType] = React.useState("expense");
  const [vendor, setVendor] = React.useState("");
  const [amount, setAmount] = React.useState("");
  const [currency, setCurrency] = React.useState(() => window.CB_DATA?.profile?.baseCurrency || "USD");
  const [date, setDate] = React.useState(new Date().toISOString().slice(0, 10));
  const [category, setCategory] = React.useState("software");
  const [account, setAccount] = React.useState("card");
  const [note, setNote] = React.useState("");

  React.useEffect(() => {
    if (open) {
      setType("expense"); setVendor(""); setAmount("");
      setCurrency(window.CB_DATA?.profile?.baseCurrency || "USD");
      setDate(new Date().toISOString().slice(0, 10));
      setCategory("software"); setAccount("card"); setNote("");
    }
  }, [open]);

  const getCats = () => type === "income"
    ? (window.CB_DATA?.categories?.income || [])
    : (window.CB_DATA?.categories?.expense || []);

  // Reset category to first of the new type when type changes
  React.useEffect(() => {
    if (open) {
      const list = getCats();
      setCategory(list[0]?.id || "");
    }
  }, [type]);

  const baseCurAdd = window.CB_DATA?.profile?.baseCurrency || "USD";
  const parsedAmtAdd = parseFloat(amount) || 0;
  const showConvAdd = currency !== baseCurAdd && parsedAmtAdd > 0;
  const convertedAdd = showConvAdd ? CB.convert(parsedAmtAdd, currency, baseCurAdd) : null;

  const submit = (e) => {
    e?.preventDefault();
    if (!vendor || !amount) return;
    onAdd({
      id: "tx_" + Date.now(),
      date, type, category,
      vendor, amount: parsedAmtAdd, currency, account,
      note, status: "cleared", receipt: false,
    });
  };

  return (
    <Modal open={open} onClose={onClose}
      title="Add transaction"
      subtitle="Manually log income or an expense"
      footer={
        <>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" onClick={submit} disabled={!vendor || !amount}>
            Save transaction
          </button>
        </>
      }>
      <form onSubmit={submit}>
        {/* Type toggle */}
        <div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
          <button type="button"
            className={"chip" + (type === "expense" ? " on" : "")}
            style={{ flex: 1, justifyContent: "center", padding: 10 }}
            onClick={() => setType("expense")}>
            <Icon name="arrowUp" size={14} /> Expense
          </button>
          <button type="button"
            className={"chip" + (type === "income" ? " on" : "")}
            style={{ flex: 1, justifyContent: "center", padding: 10 }}
            onClick={() => setType("income")}>
            <Icon name="arrowDown" size={14} /> Income
          </button>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "1fr", gap: 12 }}>
          <div>
            <label className="label">Vendor / source</label>
            <input className="input" value={vendor} onChange={(e) => setVendor(e.target.value)}
              placeholder={type === "income" ? "e.g. Squarespace" : "e.g. B&H Photo"} autoFocus />
          </div>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginTop: 12 }}>
          <div>
            <label className="label">Amount</label>
            <input className="input" type="number" step="0.01" value={amount}
              onChange={(e) => setAmount(e.target.value)} placeholder="0.00" />
          </div>
          <div>
            <label className="label">Currency</label>
            <select className="input" value={currency} onChange={(e) => setCurrency(e.target.value)}>
              {COMMON_CURRENCIES.map(c => {
                const meta = window.CB_DATA?.currencies?.[c];
                return <option key={c} value={c}>{c}{meta ? ` — ${meta.name}` : ""}</option>;
              })}
            </select>
          </div>
        </div>

        {showConvAdd && (
          <div style={{ marginTop: 8, padding: "7px 12px", background: "var(--surface-2)", borderRadius: 8, fontSize: 12.5, color: "var(--ink-soft)" }}>
            ≈ {CB.fmtCur(convertedAdd, baseCurAdd, {})} {baseCurAdd} at today's rate
          </div>
        )}

        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginTop: 12 }}>
          <div>
            <label className="label">Date</label>
            <input className="input" type="date" value={date} onChange={(e) => setDate(e.target.value)} />
          </div>
          <div>
            <label className="label">Account</label>
            <select className="input" value={account} onChange={(e) => setAccount(e.target.value)}>
              {CB_DATA.accounts.filter(a => a.status === "connected").map(a => (
                <option key={a.id} value={a.id}>{a.name}</option>
              ))}
            </select>
          </div>
        </div>

        <div style={{ marginTop: 12 }}>
          <label className="label">Category</label>
          {(() => {
            const cats = getCats();
            const selected = cats.find(c => c.id === category);
            return (
              <div style={{ position: "relative" }}>
                {selected && (
                  <span style={{
                    position: "absolute", left: 11, top: "50%", transform: "translateY(-50%)",
                    width: 10, height: 10, borderRadius: "50%",
                    background: selected.color || "var(--surface-3)",
                    pointerEvents: "none", zIndex: 1,
                  }} />
                )}
                <select className="input" value={category}
                  onChange={(e) => setCategory(e.target.value)}
                  style={{ paddingLeft: selected ? 28 : 12 }}>
                  {cats.map(c => (
                    <option key={c.id} value={c.id}>{c.name}</option>
                  ))}
                </select>
              </div>
            );
          })()}
        </div>

        <div style={{ marginTop: 12 }}>
          <label className="label">Note (optional)</label>
          <input className="input" value={note} onChange={(e) => setNote(e.target.value)}
            placeholder="Project, episode, or reference" />
        </div>
      </form>
    </Modal>
  );
};

// ─────────────────────────────────────────────────────────────
// Connect Account Modal — multi-step Plaid-style flow

const PROVIDERS = [
  // US banks
  { id: "chase",    name: "Chase",            cat: "bank",     bg: "#117ACA", text: "CH" },
  { id: "bofa",     name: "Bank of America",  cat: "bank",     bg: "#012169", text: "B" },
  { id: "wells",    name: "Wells Fargo",      cat: "bank",     bg: "#D71E2B", text: "WF" },
  { id: "citi",     name: "Citi",             cat: "bank",     bg: "#003B70", text: "C" },
  { id: "capone",   name: "Capital One",      cat: "bank",     bg: "#D03027", text: "C1" },
  { id: "usbank",   name: "US Bank",          cat: "bank",     bg: "#0E2D6B", text: "US" },
  { id: "pnc",      name: "PNC",              cat: "bank",     bg: "#F58025", text: "PN" },
  { id: "discover", name: "Discover",         cat: "bank",     bg: "#F47216", text: "D" },
  { id: "schwab",   name: "Charles Schwab",   cat: "bank",     bg: "#00A0DF", text: "CS" },
  { id: "ally",     name: "Ally",             cat: "bank",     bg: "#6F2C91", text: "AL" },

  // Business / neo-banks
  { id: "mercury",  name: "Mercury",          cat: "business", bg: "#1F2937", text: "M" },
  { id: "brex",     name: "Brex",             cat: "business", bg: "#F46E20", text: "BX" },
  { id: "ramp",     name: "Ramp",             cat: "business", bg: "#F5C418", text: "R" },
  { id: "novo",     name: "Novo",             cat: "business", bg: "#222222", text: "N" },
  { id: "relay",    name: "Relay",            cat: "business", bg: "#4F46E5", text: "RL" },
  { id: "bluevine", name: "Bluevine",         cat: "business", bg: "#0D9488", text: "BV" },

  // Credit cards
  { id: "amex",     name: "American Express", cat: "card",     bg: "#1A4480", text: "AX" },

  // Global / online banks
  { id: "wise",         name: "Wise",         cat: "global",   bg: "#163300", text: "W" },
  { id: "worldfirst",   name: "WorldFirst",   cat: "global",   bg: "#FF4D00", text: "WF" },
  { id: "revolut",      name: "Revolut",      cat: "global",   bg: "#191C1F", text: "R" },
  { id: "n26",          name: "N26",          cat: "global",   bg: "#26D07C", text: "N26" },
  { id: "monzo",        name: "Monzo",        cat: "global",   bg: "#FF4F40", text: "M" },
  { id: "starling",     name: "Starling",     cat: "global",   bg: "#7433FF", text: "S" },
  { id: "currenxie",    name: "Currenxie",    cat: "global",   bg: "#0033A0", text: "CX" },

  // Romanian banks
  { id: "raiffeisen_ro", name: "Raiffeisen Bank",      cat: "romania", bg: "#FFE600", text: "RZ" },
  { id: "bcr",           name: "BCR",                   cat: "romania", bg: "#003366", text: "BCR" },
  { id: "brd",           name: "BRD — Société Générale", cat: "romania", bg: "#E2001A", text: "BRD" },
  { id: "ing_ro",        name: "ING Bank România",      cat: "romania", bg: "#FF6200", text: "ING" },
  { id: "banca_trans",   name: "Banca Transilvania",    cat: "romania", bg: "#FFCB05", text: "BT" },
  { id: "unicredit_ro",  name: "UniCredit Bank",        cat: "romania", bg: "#C8102E", text: "UC" },

  // Payment platforms
  { id: "paypal",   name: "PayPal",           cat: "payment",  bg: "#003087", text: "PP" },
  { id: "venmo",    name: "Venmo",            cat: "payment",  bg: "#008CFF", text: "V" },
  { id: "cashapp",  name: "Cash App",         cat: "payment",  bg: "#00C853", text: "$" },
  { id: "zelle",    name: "Zelle",            cat: "payment",  bg: "#6D1ED4", text: "Z" },
  { id: "stripe",   name: "Stripe",           cat: "payment",  bg: "#635BFF", text: "S" },
  { id: "square",   name: "Square",           cat: "payment",  bg: "#000000", text: "□" },
  { id: "applepay", name: "Apple Pay",        cat: "payment",  bg: "#0A0A0A", text: "" },

  // Creator platforms
  { id: "youtube",     name: "YouTube",          cat: "creator", bg: "#FF0033", text: "▶" },
  { id: "patreon",     name: "Patreon",          cat: "creator", bg: "#FF424D", text: "P" },
  { id: "kofi",        name: "Ko-fi",            cat: "creator", bg: "#FF5E5B", text: "K" },
  { id: "bmac",        name: "Buy Me a Coffee",  cat: "creator", bg: "#FFDD00", text: "☕" },
  { id: "gumroad",     name: "Gumroad",          cat: "creator", bg: "#FF90E8", text: "G" },
  { id: "substack",    name: "Substack",         cat: "creator", bg: "#FF6719", text: "S" },
  { id: "twitch",      name: "Twitch",           cat: "creator", bg: "#9146FF", text: "Tw" },
  { id: "tiktok",      name: "TikTok",           cat: "creator", bg: "#010101", text: "TT" },
  { id: "kickstarter", name: "Kickstarter",      cat: "creator", bg: "#05CE78", text: "K" },
  { id: "shopify",     name: "Shopify",          cat: "creator", bg: "#7AB55C", text: "Sh" },
  { id: "amazon_assoc", name: "Amazon Associates", cat: "creator", bg: "#FF9900", text: "A" },
];

const PROVIDER_CATEGORIES = [
  { id: "all",      name: "All" },
  { id: "bank",     name: "US Banks" },
  { id: "business", name: "Business banking" },
  { id: "global",   name: "Global / online" },
  { id: "romania",  name: "🇷🇴 Romania" },
  { id: "payment",  name: "Payments" },
  { id: "card",     name: "Cards" },
  { id: "creator",  name: "Creator platforms" },
];

const ConnectAccountModal = ({ open, onClose, onConnect }) => {
  const [step, setStep] = React.useState(1);
  const [picked, setPicked] = React.useState(null);
  const [search, setSearch] = React.useState("");
  const [catFilter, setCatFilter] = React.useState("all");
  const [user, setUser] = React.useState("");
  const [pw, setPw] = React.useState("");
  const [progress, setProgress] = React.useState(0);

  React.useEffect(() => {
    if (open) { setStep(1); setPicked(null); setSearch(""); setCatFilter("all"); setUser(""); setPw(""); setProgress(0); }
  }, [open]);

  // Step 3: progress
  React.useEffect(() => {
    if (step !== 3) return;
    setProgress(0);
    const t = setInterval(() => {
      setProgress(p => {
        if (p >= 100) { clearInterval(t); setStep(4); return 100; }
        return p + 8;
      });
    }, 150);
    return () => clearInterval(t);
  }, [step]);

  const visible = PROVIDERS.filter(p => {
    if (catFilter !== "all" && p.cat !== catFilter) return false;
    if (search && !p.name.toLowerCase().includes(search.toLowerCase())) return false;
    return true;
  });

  const finish = () => {
    const kindByCat = {
      bank: "bank", business: "bank", global: "bank", romania: "bank",
      card: "card", payment: "platform", creator: "platform",
    };
    const kind = kindByCat[picked.cat] || "platform";
    const showBalance = picked.cat === "bank" || picked.cat === "business" || picked.cat === "global" || picked.cat === "romania" || picked.cat === "card";
    const last4 = showBalance ? String(1000 + Math.floor(Math.random() * 9000)) : "—";

    const labelByCat = {
      bank: " Checking",
      business: " Business",
      global: " Multi-currency",
      romania: " · Cont curent",
      card: " Card",
      payment: "",
      creator: picked.id === "youtube" ? " — Channel" : "",
    };

    const newAcc = {
      id: picked.id + "_" + Date.now(),
      name: picked.name + (labelByCat[picked.cat] || ""),
      kind,
      bank: picked.id,
      last4,
      status: "connected",
      balance: showBalance
        ? (picked.cat === "card" ? -Math.round(Math.random() * 4000 + 200) : Math.round(Math.random() * 30000 + 2000))
        : null,
      color: picked.bg,
      logo: picked.text,
    };
    onConnect(newAcc);
  };

  return (
    <Modal open={open} onClose={onClose}
      title={step === 1 ? "Connect an account" : step === 2 ? "Sign in to " + picked?.name : step === 3 ? "Connecting…" : "Connected!"}
      subtitle={step === 1 ? "Bank-grade encryption · read-only access via Plaid · 12,000+ institutions" : ""}
      maxWidth={600}
      footer={
        step === 1 ? <button className="btn" onClick={onClose}>Cancel</button> :
        step === 2 ? (
          <>
            <button className="btn" onClick={() => setStep(1)}>Back</button>
            <button className="btn btn-primary" disabled={!user || !pw} onClick={() => setStep(3)}>
              Sign in
            </button>
          </>
        ) :
        step === 4 ? <button className="btn btn-primary" onClick={finish}>Done</button> : null
      }>

      {step === 1 && (
        <>
          <div className="search" style={{ width: "100%", marginBottom: 12 }}>
            <Icon name="search" size={15} color="var(--muted)" />
            <input placeholder="Search 12,000+ institutions" value={search} onChange={(e) => setSearch(e.target.value)} autoFocus />
          </div>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 14 }}>
            {PROVIDER_CATEGORIES.map(c => (
              <button key={c.id}
                className={"chip" + (catFilter === c.id ? " on" : "")}
                onClick={() => setCatFilter(c.id)}>
                {c.name}
                <span style={{ opacity: 0.55, marginLeft: 2 }}>
                  {c.id === "all" ? PROVIDERS.length : PROVIDERS.filter(p => p.cat === c.id).length}
                </span>
              </button>
            ))}
          </div>
          <div style={{ maxHeight: 380, overflowY: "auto", paddingRight: 4, margin: "0 -4px" }}>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8, padding: "0 4px" }}>
              {visible.map(p => (
                <button key={p.id}
                  onClick={() => { setPicked(p); setStep(2); }}
                  style={{
                    display: "flex", alignItems: "center", gap: 10,
                    padding: 11, borderRadius: 12,
                    border: "1px solid var(--hairline)",
                    background: "var(--surface)", cursor: "pointer", textAlign: "left",
                    transition: "background 0.15s, transform 0.05s",
                  }}
                  onMouseDown={(e) => e.currentTarget.style.transform = "scale(0.98)"}
                  onMouseUp={(e) => e.currentTarget.style.transform = ""}
                  onMouseLeave={(e) => e.currentTarget.style.transform = ""}>
                  <div style={{
                    width: 34, height: 34, borderRadius: 9, background: p.bg,
                    display: "grid", placeItems: "center", color: "white", fontWeight: 700, fontSize: 12, flexShrink: 0,
                  }}>{p.text}</div>
                  <span style={{ fontWeight: 500, fontSize: 12.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.name}</span>
                </button>
              ))}
              {visible.length === 0 && <div className="empty" style={{ gridColumn: "1 / -1" }}>
                No matches for "{search}". Try a different search or category.
              </div>}
            </div>
          </div>
        </>
      )}

      {step === 2 && (
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: 14, marginBottom: 18 }}>
            <div style={{ width: 56, height: 56, borderRadius: 14, background: picked.bg,
              display: "grid", placeItems: "center", color: "white", fontWeight: 700, fontSize: 18 }}>
              {picked.text}
            </div>
            <div>
              <div style={{ fontFamily: "var(--font-display)", fontSize: 20 }}>{picked.name}</div>
              <div style={{ fontSize: 12, color: "var(--muted)" }}>Read-only access · disconnect anytime</div>
            </div>
          </div>
          <div style={{ marginBottom: 12 }}>
            <label className="label">Username</label>
            <input className="input" value={user} onChange={(e) => setUser(e.target.value)} placeholder="you@example.com" />
          </div>
          <div>
            <label className="label">Password</label>
            <input className="input" type="password" value={pw} onChange={(e) => setPw(e.target.value)} placeholder="••••••••" />
          </div>
          <div style={{ marginTop: 14, padding: 12, background: "var(--surface-2)", borderRadius: 10, fontSize: 12, color: "var(--ink-soft)", display: "flex", gap: 8 }}>
            <Icon name="link" size={14} />
            <div>CreatorBoard never stores your credentials. We use Plaid's bank-grade tokenized link.</div>
          </div>
        </div>
      )}

      {step === 3 && (
        <div style={{ padding: "30px 0", textAlign: "center" }}>
          <div style={{ width: 64, height: 64, borderRadius: 18, background: picked.bg,
            display: "grid", placeItems: "center", color: "white", fontWeight: 700, fontSize: 22, margin: "0 auto 14px" }}>
            {picked.text}
          </div>
          <div style={{ fontFamily: "var(--font-display)", fontSize: 20 }}>Pulling 90 days of transactions</div>
          <div style={{ height: 6, background: "var(--surface-2)", borderRadius: 3, overflow: "hidden", marginTop: 18, maxWidth: 280, marginInline: "auto" }}>
            <div style={{ height: "100%", background: "var(--primary)", width: progress + "%", transition: "width 0.2s" }} />
          </div>
          <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 10 }}>
            {progress < 30 ? "Verifying identity…" : progress < 70 ? "Importing transactions…" : "Auto-categorizing…"}
          </div>
        </div>
      )}

      {step === 4 && (
        <div style={{ padding: "20px 0", textAlign: "center" }}>
          <div style={{ width: 64, height: 64, borderRadius: 50, background: "var(--income-soft)",
            display: "grid", placeItems: "center", color: "var(--income-text)", margin: "0 auto 14px" }}>
            <Icon name="check" size={28} />
          </div>
          <div style={{ fontFamily: "var(--font-display)", fontSize: 22 }}>{picked.name} connected</div>
          <div style={{ fontSize: 13, color: "var(--muted)", marginTop: 6 }}>
            Imported 84 transactions · auto-categorized 71 · review the rest in your ledger
          </div>
        </div>
      )}
    </Modal>
  );
};

// ─────────────────────────────────────────────────────────────
// Transaction detail modal

const COMMON_CURRENCIES = ["USD","EUR","GBP","RON","CAD","AUD","CHF","JPY","SGD","SEK","NOK","DKK","PLN","HUF","CZK","AED","INR","BRL","MXN","ZAR"];

const TransactionDetailModal = ({ tx, onClose, onEdit, goTo, onAttachReceipt }) => {
  const [uploading, setUploading] = React.useState(false);
  const receiptInputRef = React.useRef(null);

  // Reset uploading state when tx changes
  React.useEffect(() => { setUploading(false); }, [tx?.id]);

  const handleAttachReceipt = async (file) => {
    if (!file || !tx || !window.CB_API) return;
    setUploading(true);
    try {
      const fd = new FormData();
      fd.append("file", file);
      fd.append("text", `[Receipt file: "${file.name}", ${(file.size / 1024).toFixed(0)} KB]`);
      let res = await window.CB_API.uploadReceipt(fd);

      // Exact duplicate — ask user
      if (res?.duplicate && res?.duplicateType === "exact") {
        if (!confirm(res.message + "\n\nUpload it again anyway?")) {
          setUploading(false);
          if (receiptInputRef.current) receiptInputRef.current.value = "";
          return;
        }
        fd.append("force", "true");
        res = await window.CB_API.uploadReceipt(fd);
      }

      if (!res?.ok || !res?.receiptId) throw new Error(res?.error || res?.message || "Upload failed");

      // Fuzzy duplicate warning
      if (res.duplicateWarning) {
        if (!confirm(res.duplicateWarning.message + "\n\nKeep this receipt anyway?")) {
          try { await window.CB_API.deleteReceiptAndTx(res.receiptId); } catch {}
          setUploading(false);
          if (receiptInputRef.current) receiptInputRef.current.value = "";
          return;
        }
      }

      const updated = await window.CB_API.updateTransaction(tx.id, {
        receiptId: res.receiptId,
        hasReceipt: true,
      });
      onAttachReceipt?.({ ...updated, receipt: true });
      window.toast?.({ message: `Receipt attached to "${tx.vendor}"` });
    } catch (e) {
      console.error("[Attach receipt]", e);
      window.toast?.({ type: "error", message: e?.message || "Failed to attach receipt" });
    }
    setUploading(false);
    if (receiptInputRef.current) receiptInputRef.current.value = "";
  };

  if (!tx) return null;
  const cat = CB.catById(tx.category);
  const acc = CB.accountById(tx.account);
  const txCur = tx.currency || "USD";
  const baseCur = window.CB_DATA?.profile?.baseCurrency || "USD";
  const showConversion = txCur !== baseCur && tx.amount;
  const converted = showConversion ? CB.txToUSD(tx) : null;
  const curMeta = window.CB_DATA?.currencies?.[txCur];
  const fmtNative = (n) => CB.fmtCur(n, txCur, {});
  return (
    <Modal open={!!tx} onClose={onClose}
      title={tx.vendor}
      subtitle={new Date(tx.date).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })}
      maxWidth={460}
      footer={<>
        {tx.type === "expense" && !tx.receipt && (
          <button className="btn" onClick={() => receiptInputRef.current?.click()} disabled={uploading}
            style={{ marginRight: "auto" }}>
            {uploading ? <Spinner /> : <Icon name="receipt" size={13} />}
            {uploading ? " Uploading…" : " Attach receipt"}
          </button>
        )}
        {tx.type === "income" && (
          <button className="btn" onClick={() => {
            onClose();
            goTo?.("invoices", { createFrom: tx });
          }} style={{ marginRight: "auto" }}>
            <Icon name="file" size={13} /> Issue invoice
          </button>
        )}
        <button className="btn" onClick={onClose}>Close</button>
        <button className="btn btn-primary" onClick={onEdit}>Edit</button>
      </>}>
      <div style={{ textAlign: "center", padding: "8px 0 20px", borderBottom: "1px solid var(--hairline)" }}>
        <div className="bignum" style={{ color: tx.type === "income" ? "var(--income-text)" : "var(--ink)" }}>
          {tx.type === "income" ? "+" : "−"}{fmtNative(tx.amount)}
        </div>
        {showConversion && (
          <div style={{ fontSize: 13, color: "var(--muted)", marginTop: 4 }}>
            ≈ {CB.fmtCur(converted, baseCur, {})} {baseCur}
          </div>
        )}
        <div style={{ marginTop: 6, display: "flex", gap: 6, justifyContent: "center", flexWrap: "wrap" }}>
          <Pill kind={tx.type === "income" ? "income" : "expense"}>
            {cat && <span className="cat-swatch" style={{ background: cat.color, width: 7, height: 7 }} />}
            {cat?.name ?? "Uncategorized"}
          </Pill>
          {txCur !== "USD" && <Pill>{txCur}</Pill>}
        </div>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 12, padding: "16px 0", fontSize: 13.5 }}>
        <div style={{ color: "var(--muted)" }}>Currency</div>
        <div>{curMeta ? `${txCur} — ${curMeta.name}` : txCur}</div>
        <div style={{ color: "var(--muted)" }}>Account</div>
        <div>{acc?.name || "—"}</div>
        <div style={{ color: "var(--muted)" }}>Status</div>
        <div>{tx.status === "pending" ? <Pill>Pending</Pill> : "Cleared"}</div>
        <div style={{ color: "var(--muted)" }}>Schedule C</div>
        <div>{cat?.tax ?? "—"}</div>
        {tx.note && <>
          <div style={{ color: "var(--muted)" }}>Note</div>
          <div>{tx.note}</div>
        </>}
        <div style={{ color: "var(--muted)" }}>Receipt</div>
        <div>
          {tx.receipt
            ? <button
                onClick={() => { window.__openReceiptTxId = tx.id; onClose(); goTo?.("receipts"); }}
                style={{ background: "none", border: "none", padding: 0, cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 6, color: "var(--income-text)", fontFamily: "inherit", fontSize: "inherit" }}>
                <Icon name="receipt" size={14} color="var(--income-text)" /> Attached
              </button>
            : <span style={{ color: "var(--muted)" }}>None</span>
          }
        </div>
      </div>
      <input ref={receiptInputRef} type="file"
        accept="application/pdf,image/*,.heic,.heif"
        style={{ display: "none" }}
        onChange={(e) => handleAttachReceipt(e.target.files?.[0])} />
    </Modal>
  );
};

// ── Edit transaction modal ────────────────────────────────────────────────────
const EditTransactionModal = ({ tx, onClose, onSave }) => {
  if (!tx) return null;
  const allCats = [...(window.CB_DATA?.categories?.income || []), ...(window.CB_DATA?.categories?.expense || [])];

  const [vendor,   setVendor]   = React.useState(tx.vendor || "");
  const [date,     setDate]     = React.useState(tx.date || "");
  const [type,     setType]     = React.useState(tx.type || "expense");
  const [category, setCategory] = React.useState(tx.category || "");
  const [amount,   setAmount]   = React.useState(String(tx.amount || ""));
  const [currency, setCurrency] = React.useState(tx.currency || "USD");
  const [account,  setAccount]  = React.useState(tx.account || "");
  const [status,   setStatus]   = React.useState(tx.status || "cleared");
  const [note,     setNote]     = React.useState(tx.note || "");
  const [saving,   setSaving]   = React.useState(false);

  const baseCur = window.CB_DATA?.profile?.baseCurrency || "USD";
  const parsedAmt = parseFloat(amount) || 0;
  const showConversion = currency !== baseCur && parsedAmt > 0;
  const converted = showConversion ? CB.convert(parsedAmt, currency, baseCur) : null;

  const catsForType = type === "income"
    ? (window.CB_DATA?.categories?.income || [])
    : (window.CB_DATA?.categories?.expense || []);

  const handleSave = async () => {
    if (!vendor.trim() || !date || !parsedAmt) return;
    setSaving(true);
    await onSave({
      vendor: vendor.trim(),
      date,
      type,
      categoryId: category,
      amount: parsedAmt,
      currency,
      accountId: account || null,
      status,
      note: note.trim(),
    });
    setSaving(false);
  };

  return (
    <Modal open={!!tx} onClose={onClose}
      title="Edit transaction"
      maxWidth={500}
      footer={<>
        <button className="btn" onClick={onClose}>Cancel</button>
        <button className="btn btn-primary" onClick={handleSave}
          disabled={!vendor.trim() || !date || !parsedAmt || saving}>
          {saving ? "Saving…" : "Save changes"}
        </button>
      </>}>

      <div style={{ display: "grid", gap: 14 }}>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <div style={{ gridColumn: "1 / -1" }}>
            <label className="label">Vendor / description</label>
            <input className="input" value={vendor} onChange={e => setVendor(e.target.value)} placeholder="e.g. Adobe Creative Cloud" autoFocus />
          </div>

          <div>
            <label className="label">Date</label>
            <input className="input" type="date" value={date} onChange={e => setDate(e.target.value)} />
          </div>

          <div>
            <label className="label">Type</label>
            <div style={{ display: "flex", gap: 6, marginTop: 4 }}>
              {["expense","income"].map(t => (
                <button key={t} className={"chip" + (type === t ? " on" : "")}
                  onClick={() => { setType(t); setCategory(""); }}
                  style={{ flex: 1, justifyContent: "center", textTransform: "capitalize" }}>{t}</button>
              ))}
            </div>
          </div>

          <div>
            <label className="label">Amount</label>
            <input className="input" type="number" min="0" step="0.01"
              value={amount} onChange={e => setAmount(e.target.value)} placeholder="0.00" />
          </div>

          <div>
            <label className="label">Currency</label>
            <select className="input" value={currency} onChange={e => setCurrency(e.target.value)}>
              {COMMON_CURRENCIES.map(c => {
                const meta = window.CB_DATA?.currencies?.[c];
                return <option key={c} value={c}>{c}{meta ? ` — ${meta.name}` : ""}</option>;
              })}
            </select>
          </div>

          {showConversion && (
            <div style={{ gridColumn: "1 / -1", padding: "8px 12px", background: "var(--surface-2)", borderRadius: 8, fontSize: 12.5, color: "var(--ink-soft)" }}>
              ≈ {CB.fmtCur(converted, baseCur, {})} {baseCur} at today's rate
            </div>
          )}

          <div style={{ gridColumn: "1 / -1" }}>
            <label className="label">Category</label>
            <select className="input" value={category} onChange={e => setCategory(e.target.value)}>
              <option value="">— Uncategorized —</option>
              {catsForType.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
            </select>
          </div>

          <div>
            <label className="label">Status</label>
            <select className="input" value={status} onChange={e => setStatus(e.target.value)}>
              <option value="cleared">Cleared</option>
              <option value="pending">Pending</option>
            </select>
          </div>

          <div>
            <label className="label">Account</label>
            <select className="input" value={account} onChange={e => setAccount(e.target.value)}>
              <option value="">— None —</option>
              {(window.CB_DATA?.accounts || []).map(a => <option key={a.id} value={a.id}>{a.name}</option>)}
            </select>
          </div>

          <div style={{ gridColumn: "1 / -1" }}>
            <label className="label">Note <span style={{ fontWeight: 400, color: "var(--muted)" }}>(optional)</span></label>
            <input className="input" value={note} onChange={e => setNote(e.target.value)} placeholder="Optional context" />
          </div>
        </div>
      </div>
    </Modal>
  );
};

// ─────────────────────────────────────────────────────────────
// Export modal

const ExportModal = ({ open, onClose, txs }) => {
  const [format, setFormat] = React.useState("csv");
  const [range, setRange] = React.useState("month");
  const [tab, setTab] = React.useState("transactions"); // transactions | pnl | tax
  const [exporting, setExporting] = React.useState(false);
  const [done, setDone] = React.useState(false);
  const [result, setResult] = React.useState(null);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    if (open) { setExporting(false); setDone(false); setResult(null); setError(null); }
  }, [open]);

  // Preview count of what'll be exported
  const previewCount = React.useMemo(() => {
    if (!txs || !window.CB_EXPORT) return null;
    return window.CB_EXPORT.filterTxs(txs, range).length;
  }, [txs, range, open]);

  const previewLabel = window.CB_EXPORT?.rangeFor(range).label || "";

  const go = () => {
    setExporting(true); setError(null);
    // small delay so the spinner shows for a beat
    setTimeout(() => {
      try {
        const res = window.CB_EXPORT.doExport({
          txs, range, format,
          kind: tab, // "transactions" | "pnl" | "tax"
        });
        setResult(res);
        setExporting(false);
        setDone(true);
      } catch (e) {
        console.error(e);
        setError(e.message || "Export failed");
        setExporting(false);
      }
    }, 350);
  };

  // For tax bundle, format is auto
  const effectiveFormat = tab === "tax" ? "bundle" : format;

  return (
    <Modal open={open} onClose={onClose}
      title="Export data"
      subtitle="CSV for spreadsheets · XLSX for Excel · PDF for accountants"
      footer={
        done
          ? <><span style={{ marginRight: "auto", fontSize: 12, color: "var(--income-text)", display: "flex", alignItems: "center", gap: 6 }}>
              <Icon name="check" size={14} /> Downloaded · {result?.count} transaction{result?.count === 1 ? "" : "s"} from {result?.label}
            </span><button className="btn btn-primary" onClick={onClose}>Done</button></>
          : <><button className="btn" onClick={onClose}>Cancel</button>
             <button className="btn btn-primary" onClick={go} disabled={exporting || !previewCount}>
               {exporting ? "Exporting…" : `Export ${previewCount || 0} transaction${previewCount === 1 ? "" : "s"}`}
             </button></>
      }>
      <div className="segmented" style={{ marginBottom: 14, display: "flex" }}>
        <button className={tab === "transactions" ? "on" : ""} style={{ flex: 1 }} onClick={() => setTab("transactions")}>Transactions ledger</button>
        <button className={tab === "pnl" ? "on" : ""} style={{ flex: 1 }} onClick={() => setTab("pnl")}>P&L report</button>
        <button className={tab === "tax" ? "on" : ""} style={{ flex: 1 }} onClick={() => setTab("tax")}>Tax bundle</button>
      </div>

      {tab !== "tax" && <>
        <label className="label">Format</label>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8, marginBottom: 16 }}>
          {[
            { id: "csv", name: "CSV", sub: "Universal · spreadsheets" },
            { id: "xlsx", name: "Excel", sub: ".xlsx multi-sheet" },
            { id: "pdf", name: "PDF", sub: "Print-ready statement" },
          ].map(f => (
            <button key={f.id}
              onClick={() => setFormat(f.id)}
              style={{
                padding: 14, borderRadius: 12, textAlign: "left", cursor: "pointer",
                border: "1px solid " + (format === f.id ? "var(--primary)" : "var(--hairline)"),
                background: format === f.id ? "var(--primary-soft)" : "var(--surface)",
                color: format === f.id ? "var(--primary-ink)" : "var(--ink)",
              }}>
              <Icon name="file" size={18} />
              <div style={{ fontWeight: 500, marginTop: 8 }}>{f.name}</div>
              <div style={{ fontSize: 11, color: "var(--muted)", marginTop: 2 }}>{f.sub}</div>
            </button>
          ))}
        </div>
      </>}

      <label className="label">Date range</label>
      <select className="input" value={range} onChange={(e) => setRange(e.target.value)}>
        <option value="month">This month</option>
        <option value="quarter">This quarter</option>
        <option value="ytd">Year to date</option>
        <option value="last-year">Last full year</option>
        <option value="all">All time</option>
      </select>

      <div style={{ marginTop: 12, padding: 12, background: "var(--surface-2)", borderRadius: 10, fontSize: 12.5, color: "var(--ink-soft)" }}>
        <b style={{ color: "var(--ink)" }}>{previewLabel}</b> · {previewCount ?? 0} transaction{previewCount === 1 ? "" : "s"} in range
      </div>

      {tab === "tax" && (
        <div style={{ marginTop: 14, padding: 14, background: "var(--surface-2)", borderRadius: 12 }}>
          <div style={{ fontSize: 13, fontWeight: 500, marginBottom: 6 }}>Tax bundle includes:</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-soft)", display: "grid", gap: 4 }}>
            <div>✓ Schedule C-mapped P&L statement (PDF)</div>
            <div>✓ Full transaction ledger (CSV)</div>
            <div>✓ Both files download automatically</div>
          </div>
        </div>
      )}

      {error && (
        <div style={{ marginTop: 12, padding: 12, background: "var(--expense-soft)", borderRadius: 10, fontSize: 12.5, color: "var(--expense-text-strong)" }}>
          {error}
        </div>
      )}
    </Modal>
  );
};

// ─────────────────────────────────────────────────────────────
// Import transactions — paste CSV / drop file → Claude parses → review → confirm

// Normalise a vendor string for fuzzy duplicate matching
function normVendor(s) {
  return (s || "").toLowerCase().replace(/[^a-z0-9]/g, "");
}

// Returns true if two transactions are likely duplicates:
// same date + same amount (within $0.01) + similar vendor name
function likelyDuplicate(a, b) {
  if (a.date !== b.date) return false;
  if (Math.abs((parseFloat(a.amount) || 0) - (parseFloat(b.amount) || 0)) > 0.01) return false;
  const na = normVendor(a.vendor), nb = normVendor(b.vendor);
  if (!na || !nb) return false;
  // exact normalised match, or one contains the other (handles "Adobe" vs "Adobe Creative Cloud")
  return na === nb || na.includes(nb) || nb.includes(na);
}

const ImportTransactionsModal = ({ open, onClose, onImport, existingTx = [] }) => {
  const [step, setStep] = React.useState(1); // 1 source, 2 parsing, 3 review
  const [source, setSource] = React.useState(""); // raw CSV text
  const [fileName, setFileName] = React.useState("");
  const [error, setError] = React.useState(null);
  const [rows, setRows] = React.useState([]); // parsed [{ checked, ...tx }]
  const [stmtCurrency, setStmtCurrency] = React.useState(window.CB_DATA?.profile?.baseCurrency || "USD");

  React.useEffect(() => {
    if (open) { setStep(1); setSource(""); setFileName(""); setError(null); setRows([]); setStmtCurrency(window.CB_DATA?.profile?.baseCurrency || "USD"); }
  }, [open]);

  const samples = {
    "Mercury Business": `Date,Description,Amount,Status
2026-05-19,"ADOBE *CREATIVE CLOUD",-82.99,Posted
2026-05-18,"B&H PHOTO #BH-44219",-1284.50,Posted
2026-05-17,"VENMO CASHOUT - MAYA CHEN",-980.00,Posted
2026-05-16,"STRIPE TRANSFER - LUMENLAB",215.40,Posted
2026-05-15,"WEWORK 250 BOWERY",-299.00,Posted
2026-05-12,"SQUARESPACE INC AP",3200.00,Posted`,
    "Chase": `Posting Date,Description,Amount,Type
05/19/2026,"AMAZON.COM*RT4SK2",-48.22,DEBIT
05/18/2026,"DELTA AIR LINES",-412.60,DEBIT
05/16/2026,"YOUTUBE PARTNER PAYMENT",5240.18,CREDIT
05/15/2026,"VERIZON FIOS",-89.99,DEBIT`,
    "Revolut": `Started Date,Description,Amount,Currency
2026-05-18 12:03,"Wise transfer - NordVPN",2800.00,USD
2026-05-15 09:21,"Joe's Coffee",-6.50,USD
2026-05-14 14:55,"Uber Trip",-32.40,USD`,
  };

  const handleFile = async (file) => {
    if (!file) return;
    setFileName(file.name);
    setError(null);
    try {
      const ext = (file.name.split(".").pop() || "").toLowerCase();
      const isExcel = ext === "xlsx" || ext === "xls" ||
        file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
        file.type === "application/vnd.ms-excel";

      if (isExcel) {
        if (!window.XLSX) {
          setError("Excel library failed to load. Try refreshing the page, then re-upload.");
          return;
        }
        const buf = await file.arrayBuffer();
        const wb = window.XLSX.read(buf, { type: "array" });
        const firstSheet = wb.Sheets[wb.SheetNames[0]];
        const csv = window.XLSX.utils.sheet_to_csv(firstSheet);
        setSource(csv);
      } else {
        const text = await file.text();
        setSource(text);
      }
    } catch (e) {
      console.error(e);
      setError("Couldn't read that file. Try a CSV, XLSX, or plain-text export — and make sure the file isn't password-protected.");
    }
  };

  const parse = async () => {
    if (!source.trim()) return;
    setStep(2); setError(null);

    const prompt = `You are a bank-statement parser. The user pasted CSV or transaction-list data below. Parse each transaction row and return ONLY a valid JSON array (no markdown, no commentary). Each item:

{
  "date": "<YYYY-MM-DD>",
  "vendor": "<cleaned vendor name, e.g. 'Adobe Creative Cloud', not 'ADOBE *CREATIVE CLOUD'>",
  "amount": <positive number, no sign>,
  "type": "<income | expense>",
  "category": "<one of: adsense, sponsorship, brand, merch, affiliate, members, contractors, equipment, software, travel, music, marketing, office, meals, fees>",
  "note": "<optional 1-line context, or empty>",
  "confidence": "<high|medium|low>"
}

Rules:
- Negative amounts in the source are expenses; positive are income. Always output positive amounts and the right "type".
- Clean up the vendor (remove transaction IDs, "* ", payment-processor prefixes, all-caps to title case).
- Pick the best category from the allowed list above. Use "fees" for payment-processor charges.
- Detect AdSense/YouTube payouts as category "adsense", brand-deal income as "sponsorship" or "brand".
- Skip header rows. Skip rows with no amount.

Data:
"""
${source.slice(0, 12000)}
"""`;

    try {
      if (!window.CB_API) throw new Error("Backend not available");
      const res = await window.CB_API.parseStatement(source, stmtCurrency);
      if (!res?.ok || !Array.isArray(res?.rows)) throw new Error(res?.error || "Empty response from server");
      const parsed = res.rows;
      const seeded = parsed.map((r, i) => {
        const isDupe = existingTx.some(ex => likelyDuplicate(ex, r));
        return {
          ...r,
          _idx: i,
          checked: !isDupe, // pre-uncheck duplicates
          duplicate: isDupe,
          type: r.type === "income" ? "income" : "expense",
          amount: parseFloat(r.amount) || 0,
          currency: r.currency || stmtCurrency,
          category: r.category || (r.type === "income" ? "sponsorship" : "software"),
          confidence: r.confidence || "medium",
          account: "chk",
          status: "cleared",
        };
      });
      setRows(seeded);
      setStep(3);
    } catch (e) {
      console.error(e);
      const msg = e?.data?.error || e?.message || "Parse failed";
      setError(
        msg.includes("Backend not available") || msg.includes("offline")
          ? "Not connected to backend. Make sure you're logged in."
          : "Couldn't parse the data. Try a cleaner CSV export, paste a shorter sample, or make sure the file isn't password-protected."
      );
      setStep(1);
    }
  };

  const toggleRow = (idx) => setRows(rs => rs.map(r => r._idx === idx ? { ...r, checked: !r.checked } : r));
  const setRowField = (idx, key, value) => setRows(rs => rs.map(r => r._idx === idx ? { ...r, [key]: value } : r));
  const toggleAll = (val) => setRows(rs => rs.map(r => ({ ...r, checked: val })));

  const importPicked = () => {
    const out = rows.filter(r => r.checked).map(r => ({
      id: "tx_imp_" + Date.now() + "_" + r._idx,
      date: r.date,
      type: r.type,
      category: r.category,
      vendor: r.vendor,
      amount: r.amount,
      currency: r.currency || stmtCurrency,
      account: r.account || "chk",
      note: r.note || "",
      status: r.status || "cleared",
      receipt: false,
      autoCategorized: true,
    }));
    onImport(out);
  };

  const pickedCount = rows.filter(r => r.checked).length;
  const dupeCount = rows.filter(r => r.duplicate).length;
  const baseCurImp = window.CB_DATA?.profile?.baseCurrency || "USD";
  const toBaseCurImp = (r) => {
    const cur = r.currency || stmtCurrency;
    return cur !== baseCurImp ? CB.convert(r.amount || 0, cur, baseCurImp) : (r.amount || 0);
  };
  const totalIn = rows.filter(r => r.checked && r.type === "income").reduce((s, r) => s + toBaseCurImp(r), 0);
  const totalOut = rows.filter(r => r.checked && r.type === "expense").reduce((s, r) => s + toBaseCurImp(r), 0);

  return (
    <Modal open={open} onClose={onClose}
      maxWidth={step === 3 ? 880 : 600}
      title={step === 3 ? "Review imported transactions" : "Import transactions"}
      subtitle={
        step === 1 ? "Paste CSV or drop a bank statement — Claude auto-categorizes everything" :
        step === 2 ? "Parsing with AI…" :
        `${rows.length} parsed · ${pickedCount} selected · +${CB.fmt(totalIn, { cents: false })} / −${CB.fmt(totalOut, { cents: false })}` + (dupeCount ? ` · ⚠ ${dupeCount} likely duplicate${dupeCount > 1 ? "s" : ""} unchecked` : "")
      }
      footer={
        step === 1 ? <>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" onClick={parse} disabled={!source.trim()}>
            <Icon name="sparkles" size={14} /> Scan with AI
          </button>
        </> :
        step === 3 ? <>
          <button className="btn" onClick={() => setStep(1)}>Back</button>
          <span style={{ marginRight: "auto", fontSize: 12, color: "var(--muted)" }}>
            {pickedCount === rows.length ? "All " : ""}{pickedCount} of {rows.length} selected
          </span>
          <button className="btn btn-primary" onClick={importPicked} disabled={pickedCount === 0}>
            Import {pickedCount} transaction{pickedCount === 1 ? "" : "s"}
          </button>
        </> : null
      }>

      {step === 1 && (
        <>
          <ImportFileDrop onFile={handleFile} fileName={fileName} />

          <label className="label" style={{ marginTop: 14 }}>
            Or paste CSV / transaction list
          </label>
          <textarea className="input" rows="10" value={source}
            onChange={(e) => setSource(e.target.value)}
            placeholder="Date,Description,Amount,Status&#10;2026-05-19,&quot;ADOBE *CREATIVE CLOUD&quot;,-82.99,Posted&#10;..."
            style={{ fontFamily: "var(--font-mono)", fontSize: 12, resize: "vertical", lineHeight: 1.5 }} />

          <div style={{ marginTop: 12, display: "flex", alignItems: "center", gap: 10 }}>
            <label className="label" style={{ margin: 0, whiteSpace: "nowrap" }}>Statement currency</label>
            <select className="input" style={{ width: "auto", minWidth: 160 }}
              value={stmtCurrency} onChange={e => setStmtCurrency(e.target.value)}>
              {COMMON_CURRENCIES.map(c => {
                const meta = window.CB_DATA?.currencies?.[c];
                return <option key={c} value={c}>{c}{meta ? ` — ${meta.name}` : ""}</option>;
              })}
            </select>
            <span style={{ fontSize: 11.5, color: "var(--muted)" }}>Used when the CSV has no currency column</span>
          </div>

          <div style={{ marginTop: 10, display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
            <span style={{ fontSize: 11.5, color: "var(--muted)" }}>Try a sample:</span>
            {Object.entries(samples).map(([label, csv]) => (
              <button key={label} className="chip btn-sm"
                onClick={() => { setSource(csv); setFileName(label + ".csv"); }}>
                {label}
              </button>
            ))}
          </div>

          {error && (
            <div style={{ marginTop: 12, padding: 12, background: "var(--expense-soft)", borderRadius: 10, fontSize: 12.5, color: "var(--expense-text-strong)" }}>
              {error}
            </div>
          )}

          <div style={{ marginTop: 14, padding: 12, background: "var(--surface-2)", borderRadius: 10, fontSize: 12, color: "var(--ink-soft)", display: "flex", gap: 8 }}>
            <Icon name="sparkles" size={13} />
            <div>
              Works with any bank CSV format. Claude cleans vendor names, infers expense vs. income from
              the amount sign, and picks the right category — you confirm before saving.
            </div>
          </div>
        </>
      )}

      {step === 2 && (
        <div style={{ padding: "40px 20px", textAlign: "center" }}>
          <Spinner large />
          <div style={{ marginTop: 16, fontFamily: "var(--font-display)", fontSize: 22 }}>
            Reading your statement
          </div>
          <div style={{ fontSize: 13, color: "var(--muted)", marginTop: 6 }}>
            Cleaning vendor names · classifying each line · suggesting categories
          </div>
        </div>
      )}

      {step === 3 && (
        <>
          {dupeCount > 0 && (
            <div style={{ marginBottom: 10, padding: "10px 14px", background: "oklch(0.97 0.04 85)", borderRadius: 10, fontSize: 12.5, color: "oklch(0.45 0.1 60)", display: "flex", gap: 8, alignItems: "center" }}>
              <span>⚠</span>
              <span><b>{dupeCount} transaction{dupeCount > 1 ? "s" : ""}</b> look like {dupeCount > 1 ? "duplicates" : "a duplicate"} already in your ledger — unchecked by default. Review and re-check if you still want to import {dupeCount > 1 ? "them" : "it"}.</span>
            </div>
          )}
          <ImportReviewTable rows={rows} stmtCurrency={stmtCurrency} toggleRow={toggleRow} toggleAll={toggleAll} setRowField={setRowField} />
        </>
      )}
    </Modal>
  );
};

const ImportFileDrop = ({ onFile, fileName }) => {
  const inputRef = React.useRef(null);
  const [drag, setDrag] = React.useState(false);

  return (
    <div>
      <input ref={inputRef} type="file"
        accept=".csv,.txt,.tsv,.xls,.xlsx,text/csv,text/plain,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        style={{ display: "none" }}
        onChange={(e) => onFile(e.target.files?.[0])} />
      <button type="button"
        onClick={() => inputRef.current?.click()}
        onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
        onDragLeave={() => setDrag(false)}
        onDrop={(e) => { e.preventDefault(); setDrag(false); onFile(e.dataTransfer.files?.[0]); }}
        style={{
          width: "100%", padding: "22px 18px", borderRadius: 14,
          background: drag ? "var(--primary-soft)" : "var(--surface-2)",
          border: "2px dashed " + (drag ? "var(--primary)" : "var(--hairline-strong)"),
          color: drag ? "var(--primary-ink)" : "var(--ink)",
          cursor: "pointer",
          display: "flex", alignItems: "center", gap: 14,
        }}>
        <div style={{
          width: 44, height: 44, borderRadius: 11,
          background: "var(--surface)", color: "var(--primary)",
          display: "grid", placeItems: "center", flexShrink: 0,
        }}>
          <Icon name="upload" size={20} />
        </div>
        <div style={{ textAlign: "left", flex: 1 }}>
          <div style={{ fontWeight: 500, fontSize: 14 }}>
            {fileName ? fileName : "Drop a CSV or click to upload"}
          </div>
          <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 2 }}>
            CSV, XLSX, XLS, or plain text — any bank export works
          </div>
        </div>
      </button>
    </div>
  );
};

const ImportReviewTable = ({ rows, stmtCurrency, toggleRow, toggleAll, setRowField }) => {
  if (rows.length === 0) {
    return <div className="empty">Nothing parsed. Try a different snippet.</div>;
  }
  const allChecked = rows.every(r => r.checked);
  const allCats = [...window.CB_DATA.categories.income, ...window.CB_DATA.categories.expense];

  return (
    <div style={{ maxHeight: "60vh", overflowY: "auto", border: "1px solid var(--hairline)", borderRadius: 10 }}>
      <table className="tbl" style={{ fontSize: 12.5 }}>
        <thead style={{ position: "sticky", top: 0, background: "var(--surface)", zIndex: 1 }}>
          <tr>
            <th style={{ width: 32 }}>
              <input type="checkbox" checked={allChecked} onChange={(e) => toggleAll(e.target.checked)} />
            </th>
            <th style={{ width: 90 }}>Date</th>
            <th>Vendor</th>
            <th style={{ width: 150 }}>Category</th>
            <th style={{ width: 90 }}>Status</th>
            <th style={{ width: 100, textAlign: "right" }}>Amount</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(r => {
            const cat = CB.catById(r.category);
            const catsForType = r.type === "income"
              ? window.CB_DATA.categories.income
              : window.CB_DATA.categories.expense;
            return (
              <tr key={r._idx} style={{ opacity: r.checked ? 1 : 0.45 }}>
                <td>
                  <input type="checkbox" checked={r.checked} onChange={() => toggleRow(r._idx)} />
                </td>
                <td style={{ fontFamily: "var(--font-mono)", fontSize: 11.5 }}>
                  {new Date(r.date).toLocaleDateString("en-US", { month: "short", day: "2-digit" })}
                </td>
                <td>
                  <input className="input" value={r.vendor}
                    onChange={(e) => setRowField(r._idx, "vendor", e.target.value)}
                    style={{ border: "0", padding: "4px 6px", background: "transparent", fontSize: 13 }} />
                </td>
                <td>
                  <select className="input" value={r.category}
                    onChange={(e) => setRowField(r._idx, "category", e.target.value)}
                    style={{ border: "0", padding: "4px 6px", background: "var(--surface-2)", fontSize: 12 }}>
                    {catsForType.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
                  </select>
                </td>
                <td>
                  {r.duplicate ? (
                    <Pill kind="expense" style={{ fontSize: 11 }}>⚠ duplicate</Pill>
                  ) : (
                    <Pill kind={r.confidence === "high" ? "income" : r.confidence === "low" ? "expense" : ""}>
                      {r.confidence}
                    </Pill>
                  )}
                </td>
                <td className="num" style={{ color: r.type === "income" ? "var(--income-text)" : "var(--ink)" }}>
                  {(() => {
                    const baseCur = window.CB_DATA?.profile?.baseCurrency || "USD";
                    const txCur = r.currency || stmtCurrency || baseCur;
                    const sign = r.type === "income" ? "+" : "−";
                    const converted = txCur !== baseCur ? CB.convert(r.amount, txCur, baseCur) : null;
                    return (
                      <>
                        <span>{sign}{CB.fmtCur(r.amount, txCur, {})}</span>
                        {converted != null && (
                          <div style={{ fontSize: 10.5, color: "var(--muted)", fontWeight: 400 }}>
                            ≈ {CB.fmt(converted)}
                          </div>
                        )}
                      </>
                    );
                  })()}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
};

const Spinner = ({ large }) => (
  <span style={{
    display: "inline-block",
    width: large ? 32 : 12, height: large ? 32 : 12,
    border: "2px solid var(--hairline-strong)",
    borderTopColor: "var(--primary)",
    borderRadius: "50%",
    animation: "spin 0.7s linear infinite",
    verticalAlign: "middle",
  }} />
);

Object.assign(window, { AddTransactionModal, ConnectAccountModal, TransactionDetailModal, EditTransactionModal, ExportModal, ImportTransactionsModal });
